Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete Report Schedules #4089

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1202f38
Create delete recipe modal and handler
Rieven Feb 7, 2025
c9d4da5
delete schedule in rocky
Rieven Feb 12, 2025
8e9a88f
Merge branch 'main' of github.com:minvws/nl-kat-coordination into fea…
Rieven Feb 12, 2025
1af552d
fix lang
Rieven Feb 12, 2025
ceaa364
add test and fix some things
Rieven Feb 12, 2025
02e00a6
fix lang
Rieven Feb 12, 2025
5668b62
Merge branch 'main' into feature/delete-report-recipes
Rieven Feb 12, 2025
7c00404
Merge branch 'main' into feature/delete-report-recipes
underdarknl Feb 13, 2025
fcce766
Update rocky/reports/views/report_overview.py
Rieven Feb 13, 2025
2f91ba3
changes for review
Rieven Feb 13, 2025
ad5fcb2
Merge branch 'feature/delete-report-recipes' of github.com:minvws/nl-…
Rieven Feb 13, 2025
9fbc064
fix lang
Rieven Feb 13, 2025
a243270
Merge branch 'main' into feature/delete-report-recipes
jpbruinsslot Feb 18, 2025
5c7e255
Merge branch 'main' into feature/delete-report-recipes
Rieven Feb 19, 2025
fe35d63
Merge branch 'main' into feature/delete-report-recipes
stephanie0x00 Feb 20, 2025
77d7bb7
fixes for review
Rieven Feb 21, 2025
ff44191
Merge branch 'feature/delete-report-recipes' of github.com:minvws/nl-…
Rieven Feb 21, 2025
5f9f465
Merge branch 'main' into feature/delete-report-recipes
Rieven Feb 21, 2025
8fc46e7
fix lang
Rieven Feb 21, 2025
dde2413
fix test
Rieven Feb 25, 2025
dcdaba0
fix lang
Rieven Feb 25, 2025
bf2eda5
Merge branch 'main' of github.com:minvws/nl-kat-coordination into fea…
Rieven Feb 25, 2025
6d8e050
Merge branch 'main' into feature/delete-report-recipes
Rieven Feb 26, 2025
47ce492
Merge branch 'main' into feature/delete-report-recipes
Rieven Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ <h5>{% translate "Recent reports" %}</h5>
<a class="button ghost"
href="{% ooi_url "ooi_edit" schedule.recipe organization.code %}"><span aria-hidden="true" class="icon ti-edit action-button"></span>{% translate "Edit report recipe" %}</a>
{% endif %}
{% include "report_schedules/delete_recipe_modal.html" with schedule_id=schedule.schedule_id recipe_id=schedule.recipe.primary_key %}

<a class="button ghost destructive" href="#delete-recipe-modal">{% translate "Delete" %}</a>
</div>
</div>
</td>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% load i18n %}

{% component 'modal' size='dialog-small' modal_id='delete-recipe-modal' %}
{% fill 'header' %}{% translate "Delete report recipe" %}{% endfill %}
{% fill 'content' %}
<p>
{% blocktranslate %}
Deleting this report recipe means it will be permanently deleted.
It will not be possible anymore to see or enable the schedule.
You will find previously generated reports in the report history tab.
{% endblocktranslate %}
</p>
<form id="delete-form" class="inline" method="post">
{% csrf_token %}
<input type="hidden" name="schedule_id" value="{{ schedule_id }}" />
<input type="hidden" name="report_recipe" value="{{ recipe_id }}" />
</form>
{% endfill %}
{% fill "footer_buttons" %}
<button type="submit"
form="delete-form"
class="destructive"
name="action"
value="delete">{% translate "Delete report recipe" %}</button>
<button class="ghost close-modal-button">{% translate "Cancel" %}</button>
{% endfill %}
{% endcomponent %}
{% component_css_dependencies %}
19 changes: 19 additions & 0 deletions rocky/reports/views/report_overview.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,25 @@ def get_queryset(self) -> list[dict[str, Any]]:

return recipes

def post(self, request, *args, **kwargs):
recipe_pk = request.POST.get("report_recipe", "")
schedule_id = request.POST.get("schedule_id", "")

if recipe_pk and schedule_id:
self.delete_report_schedule(schedule_id)
try:
self.octopoes_api_connector.delete(
Reference.from_str(f"{recipe_pk}"), valid_time=datetime.now(timezone.utc)
)
messages.success(self.request, _("Recipe '{}' deleted successfully").format(recipe_pk))
except ObjectNotFoundException:
messages.error(self.request, _("Recipe not found."))

else:
messages.error(self.request, _("No schedule or recipe selected"))

return redirect(reverse("scheduled_reports", kwargs={"organization_code": self.organization.code}))

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["total_report_schedules"] = len(self.object_list)
Expand Down
30 changes: 29 additions & 1 deletion rocky/rocky/locale/django.pot
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-13 15:25+0000\n"
"POT-Creation-Date: 2025-02-25 14:59+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -587,6 +587,7 @@ msgstr ""
#: katalogus/templates/confirmation_clone_settings.html
#: katalogus/templates/plugin_settings_delete.html
#: reports/templates/report_overview/modal_partials/enable_disable_schedule_modal.html
#: reports/templates/report_schedules/delete_recipe_modal.html
#: rocky/templates/oois/ooi_delete.html
#: rocky/templates/oois/ooi_mute_finding.html
#: rocky/templates/organizations/organization_edit.html
Expand Down Expand Up @@ -1389,6 +1390,7 @@ msgstr ""
#: katalogus/views/plugin_settings_delete.py
#: reports/templates/report_overview/modal_partials/delete_modal.html
#: reports/templates/report_overview/report_history_table.html
#: reports/templates/report_overview/scheduled_reports_table.html
#: rocky/templates/admin/delete_confirmation.html rocky/views/ooi_delete.py
msgid "Delete"
msgstr ""
Expand Down Expand Up @@ -4380,6 +4382,20 @@ msgstr ""
msgid "Input Object"
msgstr ""

#: reports/templates/report_schedules/delete_recipe_modal.html
msgid "Delete report recipe"
msgstr ""

#: reports/templates/report_schedules/delete_recipe_modal.html
msgid ""
"\n"
" Deleting this report recipe means it will be permanently deleted.\n"
" It will not be possible anymore to see or enable the schedule.\n"
" You will find previously generated reports in the report history "
"tab.\n"
" "
msgstr ""

#: reports/templates/summary/report_asset_overview.html
msgid ""
"The objects listed in the table below were used to generate this report. For "
Expand Down Expand Up @@ -4500,6 +4516,18 @@ msgstr ""
msgid "View report"
msgstr ""

#: reports/views/report_overview.py
msgid "Recipe '{}' deleted successfully"
msgstr ""

#: reports/views/report_overview.py
msgid "Recipe not found."
msgstr ""

#: reports/views/report_overview.py
msgid "No schedule or recipe selected"
msgstr ""

#: reports/views/report_overview.py
msgid "Schedule {}"
msgstr ""
Expand Down
6 changes: 6 additions & 0 deletions rocky/rocky/views/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ def create_report_schedule(self, report_recipe: ReportRecipe, deadline_at: datet
except SchedulerError as error:
return messages.error(self.request, error.message)

def delete_report_schedule(self, schedule_id: str) -> None:
try:
self.scheduler_client.delete_schedule(schedule_id)
except SchedulerError as error:
return messages.error(self.request, error.message)

def edit_report_schedule(self, schedule_id: str, params):
self.scheduler_client.patch_schedule(schedule_id=schedule_id, params=params)

Expand Down
57 changes: 56 additions & 1 deletion rocky/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from octopoes.models.tree import ReferenceTree
from octopoes.models.types import OOIType
from rocky.health import ServiceHealth
from rocky.scheduler import PaginatedTasksResponse, ReportTask, Task, TaskStatus
from rocky.scheduler import PaginatedTasksResponse, ReportTask, ScheduleResponse, Task, TaskStatus

LANG_LIST = [code for code, _ in settings.LANGUAGES]

Expand Down Expand Up @@ -1872,3 +1872,58 @@ def reports_task_list():
),
],
)


@pytest.fixture
def scheduled_report_recipe():
return ReportRecipe(
object_type="ReportRecipe",
scan_profile=EmptyScanProfile(
scan_profile_type="empty",
reference=Reference("ReportRecipe|3fed7d00-6261-4ad1-b08f-9b91434aa41e"),
level=ScanLevel.L0,
user_id=None,
),
user_id=None,
primary_key="ReportRecipe|3fed7d00-6261-4ad1-b08f-9b91434aa41e",
recipe_id=UUID("3fed7d00-6261-4ad1-b08f-9b91434aa41e"),
report_name_format="${report_type} for ${oois_count} objects",
input_recipe={"input_oois": ["Hostname|internet|mispo.es"]},
report_type="concatenated-report",
asset_report_types=[
"dns-report",
"findings-report",
"ipv6-report",
"mail-report",
"name-server-report",
"open-ports-report",
"rpki-report",
"safe-connections-report",
"systems-report",
"vulnerability-report",
"web-system-report",
],
cron_expression=None,
)


@pytest.fixture
def scheduled_reports_list():
return [
ScheduleResponse(
id=UUID("7706ebc1-b24b-44fb-a7b3-9a44d80b2644"),
scheduler_id="report",
organisation="test",
hash="bb5708d2f82e11cc5cda3aef54190f2e",
data={
"type": "report",
"organisation_id": "_rieven",
"report_recipe_id": "3fed7d00-6261-4ad1-b08f-9b91434aa41e",
},
enabled=True,
schedule=None,
deadline_at=None,
created_at=datetime(2025, 2, 12, 16, 1, 19, 951925),
modified_at=datetime(2025, 2, 12, 16, 1, 19, 951925),
)
]
79 changes: 79 additions & 0 deletions rocky/tests/reports/test_report_schedules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from reports.views.report_overview import ScheduledReportsView

from octopoes.models.exception import ObjectNotFoundException
from rocky.scheduler import SchedulerError
from tests.conftest import setup_request


def test_delete_schedule(
rf, client_member, mock_organization_view_octopoes, mock_scheduler, scheduled_report_recipe, scheduled_reports_list
):
mock_scheduler.get_scheduled_reports.return_value = scheduled_reports_list
mock_organization_view_octopoes().get.return_value = scheduled_report_recipe

schedule_id = scheduled_reports_list[0].id
recipe_id = "ReportRecipe|" + scheduled_reports_list[0].data["report_recipe_id"]

request = setup_request(
rf.post("scheduled_reports", {"report_recipe": recipe_id, "schedule_id": schedule_id}), client_member.user
)

response = ScheduledReportsView.as_view()(request, organization_code=client_member.organization.code)

assert response.status_code == 302
assert list(request._messages)[0].message == f"Recipe '{recipe_id}' deleted successfully"


def test_delete_schedule_no_recipe(
rf, client_member, mock_organization_view_octopoes, mock_scheduler, scheduled_report_recipe, scheduled_reports_list
):
mock_scheduler.get_scheduled_reports.return_value = scheduled_reports_list
mock_organization_view_octopoes().get.return_value = scheduled_report_recipe

schedule_id = scheduled_reports_list[0].id

request = setup_request(
rf.post("scheduled_reports", {"report_recipe": "", "schedule_id": schedule_id}), client_member.user
)

response = ScheduledReportsView.as_view()(request, organization_code=client_member.organization.code)

assert response.status_code == 302
assert list(request._messages)[0].message == "No schedule or recipe selected"


def test_delete_schedule_object_not_found(
rf, client_member, mock_organization_view_octopoes, mock_scheduler, scheduled_report_recipe, scheduled_reports_list
):
mock_scheduler.get_scheduled_reports.side_effect = scheduled_reports_list
mock_organization_view_octopoes().get.return_value = scheduled_report_recipe
mock_organization_view_octopoes().delete.side_effect = ObjectNotFoundException("Not found")

request = setup_request(
rf.post("scheduled_reports", {"report_recipe": "recipeNone", "schedule_id": "ScheduleNone"}), client_member.user
)

response = ScheduledReportsView.as_view()(request, organization_code=client_member.organization.code)

assert response.status_code == 302
assert list(request._messages)[0].message == "Recipe not found."


def test_delete_schedule_schedule_not_found(
rf, client_member, mock_organization_view_octopoes, mock_scheduler, scheduled_report_recipe, scheduled_reports_list
):
mock_scheduler.get_scheduled_reports.side_effect = scheduled_reports_list
mock_organization_view_octopoes().get.return_value = scheduled_report_recipe
mock_scheduler.delete_schedule.side_effect = SchedulerError

request = setup_request(
rf.post("scheduled_reports", {"report_recipe": "recipeNone", "schedule_id": "ScheduleNone"}), client_member.user
)

response = ScheduledReportsView.as_view()(request, organization_code=client_member.organization.code)

assert response.status_code == 302
assert (
list(request._messages)[0].message
== "The Scheduler has an unexpected error. Check the Scheduler logs for further details."
)
Loading