Skip to content

Commit

Permalink
Add start date to report schedule (#3701)
Browse files Browse the repository at this point in the history
Co-authored-by: stephanie0x00 <[email protected]>
  • Loading branch information
madelondohmen and stephanie0x00 authored Oct 28, 2024
1 parent a2f8f00 commit 75de529
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 49 deletions.
9 changes: 5 additions & 4 deletions rocky/reports/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ class ReportScheduleStartDateChoiceForm(BaseRockyForm):

class ReportScheduleStartDateForm(BaseRockyForm):
start_date = forms.DateField(
label="",
widget=DateInput(format="%Y-%m-%d"),
label=_("Start date"),
widget=DateInput(format="%Y-%m-%d", attrs={"form": "generate_report"}),
initial=lambda: datetime.now(tz=timezone.utc).date(),
required=True,
input_formats=["%Y-%m-%d"],
)


Expand All @@ -59,8 +60,8 @@ class ReportRecurrenceChoiceForm(BaseRockyForm):

class ReportScheduleRecurrenceForm(BaseRockyForm):
recurrence = forms.ChoiceField(
label="",
required=False,
label=_("Recurrence"),
required=True,
widget=forms.Select(attrs={"form": "generate_report"}),
choices=[("daily", _("Daily")), ("weekly", _("Weekly")), ("monthly", _("Monthly")), ("yearly", _("Yearly"))],
)
Expand Down
25 changes: 17 additions & 8 deletions rocky/reports/templates/partials/export_report_settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,42 @@ <h2>{% translate "Report schedule" %}</h2>
or monthly. If you need the report just for a single occasion, select the one-time option.
{% endblocktranslate %}
</p>
{% include "partials/return_button.html" with btn_text="Change selection" %}

<form class="inline" method="post" action="{{ current }}">
{% csrf_token %}
{% include "forms/report_form_fields.html" %}

<h3>{% translate "Recurrence" %}</h3>
{% include "partials/form/fieldset.html" with fields=report_schedule_form_recurrence_choice %}

{% if is_scheduled_report %}
{% include "partials/form/fieldset.html" with fields=report_schedule_form_recurrence %}
<p>
{% blocktranslate trimmed %}
The date you select will be the reference date for the data set for your report.
Please allow for up to 24 hours for your report to be ready.
{% endblocktranslate %}
</p>
<div class="column-2">
<div>
{% include "partials/form/fieldset.html" with fields=report_schedule_form_start_date %}

</div>
<div>
{% include "partials/form/fieldset.html" with fields=report_schedule_form_recurrence %}

</div>
</div>
{% endif %}
</form>
<form id="generate_report" class="inline" method="post" action="{{ next }}">
{% csrf_token %}
{% include "forms/report_form_fields.html" %}

{% if show_listed_report_names %}
{% if not is_scheduled_report %}
{% include "partials/report_names_header.html" %}
{% include "partials/report_names_form.html" %}

<button type="submit" form="generate_report">
{% translate "Generate report" %}<span class="icon ti-chevron-right" aria-hidden="true"></span>
</button>
{% endif %}
{% if is_scheduled_report %}
{% else %}
{% include "partials/report_names_header.html" with recurrence=True %}
{% include "partials/form/fieldset.html" with fields=report_parent_name_form %}

Expand Down
15 changes: 5 additions & 10 deletions rocky/reports/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,8 @@ def get_available_report_types(self) -> tuple[list[dict[str, str]] | dict[str, l
def get_observed_at(self):
return self.observed_at if self.observed_at < datetime.now(timezone.utc) else datetime.now(timezone.utc)

def show_report_names(self) -> bool:
recurrence_choice = self.request.POST.get("choose_recurrence", "once")
return recurrence_choice == "once"

def is_scheduled_report(self) -> bool:
recurrence_choice = self.request.POST.get("choose_recurrence", "")
recurrence_choice = self.request.POST.get("choose_recurrence", "once")
return recurrence_choice == "repeat"

def create_report_recipe(self, report_name_format: str, subreport_name_format: str, schedule: str) -> ReportRecipe:
Expand Down Expand Up @@ -442,15 +438,13 @@ class ReportFinalSettingsView(BaseReportView, ReportBreadcrumbs, SchedulerView,
report_type: type[BaseReport] | None = None
task_type = "report"
is_a_scheduled_report = False
show_listes_report_names = False

def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
if not self.get_report_type_ids():
messages.error(request, self.NONE_REPORT_TYPE_SELECTION_MESSAGE)
return PostRedirect(self.get_previous())

self.is_a_scheduled_report = self.is_scheduled_report()
self.show_listes_report_names = self.show_report_names()

return super().get(request, *args, **kwargs)

Expand Down Expand Up @@ -496,13 +490,13 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["reports"] = self.get_report_names()

context["report_schedule_form_start_date"] = self.get_report_schedule_form_start_date()
context["report_schedule_form_recurrence_choice"] = self.get_report_schedule_form_recurrence_choice()
context["report_schedule_form_recurrence"] = self.get_report_schedule_form_recurrence()

context["report_parent_name_form"] = self.get_report_parent_name_form()
context["report_child_name_form"] = self.get_report_child_name_form()

context["show_listed_report_names"] = self.show_listes_report_names
context["is_scheduled_report"] = self.is_a_scheduled_report

context["created_at"] = datetime.now()
Expand All @@ -517,7 +511,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
report_names = request.POST.getlist("report_name", [])
reference_dates = request.POST.getlist("reference_date")

if self.show_report_names() and report_names:
if not self.is_scheduled_report() and report_names:
final_report_names = list(zip(old_report_names, self.finalise_report_names(report_names, reference_dates)))
report_ooi = self.save_report(final_report_names)

Expand All @@ -531,12 +525,13 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
subreport_name_format = request.POST.get("child_report_name", "")

recurrence = request.POST.get("recurrence", "")
deadline_at = request.POST.get("start_date", datetime.now(timezone.utc).date())

schedule = self.convert_recurrence_to_cron_expressions(recurrence)

report_recipe = self.create_report_recipe(report_name_format, subreport_name_format, schedule)

self.create_report_schedule(report_recipe)
self.create_report_schedule(report_recipe, deadline_at)

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

Expand Down
31 changes: 19 additions & 12 deletions rocky/rocky/locale/django.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,10 @@ msgstr ""
msgid "Different date"
msgstr ""

#: reports/forms.py
msgid "Start date"
msgstr ""

#: reports/forms.py
msgid "No, just once"
msgstr ""
Expand All @@ -2404,6 +2408,11 @@ msgstr ""
msgid "Yes, repeat"
msgstr ""

#: reports/forms.py
#: reports/templates/report_overview/scheduled_reports_table.html
msgid "Recurrence"
msgstr ""

#: reports/forms.py
msgid "Daily"
msgstr ""
Expand All @@ -2420,10 +2429,6 @@ msgstr ""
msgid "Yearly"
msgstr ""

#: reports/forms.py
msgid "Start date"
msgstr ""

#: reports/forms.py
msgid "day"
msgstr ""
Expand Down Expand Up @@ -3675,8 +3680,9 @@ msgid ""
msgstr ""

#: reports/templates/partials/export_report_settings.html
#: reports/templates/report_overview/scheduled_reports_table.html
msgid "Recurrence"
msgid ""
"The date you select will be the reference date for the data set for your "
"report. Please allow for up to 24 hours for your report to be ready."
msgstr ""

#: reports/templates/partials/export_report_settings.html
Expand Down Expand Up @@ -3747,6 +3753,7 @@ msgid "Report names:"
msgstr ""

#: reports/templates/partials/report_names_form.html
#: rocky/templates/partials/form/field_input.html
msgid "(Required)"
msgstr ""

Expand Down Expand Up @@ -6125,12 +6132,6 @@ msgstr ""
msgid "Please enable plugin to start scanning."
msgstr ""

#: rocky/templates/partials/form/field_input.html
#: rocky/templates/partials/form/field_input_checkbox.html
#: rocky/templates/partials/form/field_input_radio.html
msgid "This field is required"
msgstr ""

#: rocky/templates/partials/form/field_input.html
msgid "Not set"
msgstr ""
Expand All @@ -6143,6 +6144,12 @@ msgstr ""
msgid "Forgot password"
msgstr ""

#: rocky/templates/partials/form/field_input.html
#: rocky/templates/partials/form/field_input_checkbox.html
#: rocky/templates/partials/form/field_input_radio.html
msgid "This field is required"
msgstr ""

#: rocky/templates/partials/form/field_input_errors.html
#: rocky/templates/partials/form/form_errors.html
#: rocky/templates/partials/notifications_block.html
Expand Down
1 change: 1 addition & 0 deletions rocky/rocky/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class ScheduleRequest(BaseModel):
scheduler_id: str
data: dict
schedule: str
deadline_at: str


class ScheduleResponse(BaseModel):
Expand Down
3 changes: 1 addition & 2 deletions rocky/rocky/templates/oois/ooi_add.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
<div class="layout-form">
<h1>{% blocktranslate %}Add a {{ display_type }}{% endblocktranslate %}</h1>
<p>{% translate "Here you can add the asset of the client. Findings can be added to these in the findings page." %}</p>
<form method="post" class="horizontal-view">
<form method="post">
{% csrf_token %}
{% if form.non_field_errors %}<div class="warning">{{ form.non_field_errors }}</div>{% endif %}
<fieldset>
<legend>{% translate type %}</legend>
<div>
<label for="ooi_type">Type</label>
<input id="ooi_type" type="text" name="ooi_type" value="{{ type }}" readonly>
Expand Down
3 changes: 1 addition & 2 deletions rocky/rocky/templates/oois/ooi_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
<div class="layout-form">
<h1>{% blocktranslate %}Edit {{ type }}: {{ ooi_human_readable }}{% endblocktranslate %}</h1>
<p>{% blocktranslate %}Primary key fields cannot be edited.{% endblocktranslate %}</p>
<form method="post" class="horizontal-view">
<form method="post">
{% csrf_token %}
{% if form.non_field_errors %}<div class="warning">{{ form.non_field_errors }}</div>{% endif %}
<fieldset>
<legend>{% translate type %}</legend>
<div>
<label for="ooi_type">Type</label>
<input id="ooi_type" type="text" name="ooi_type" value="{{ type }}" readonly>
Expand Down
15 changes: 10 additions & 5 deletions rocky/rocky/templates/partials/form/field_input.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{% load i18n %}

<div {% if field.field.required %}class="required"{% endif %}>
{{ field.label_tag }}
{% if field.field.required %}
<label>
{{ field.label_tag }} <span class="nota-bene" aria-hidden>{% translate "(Required)" %}</span>
</label>
{% else %}
{{ field.label_tag }}
{% endif %}
<p id="input-description" class="nota-bene">{{ field.field.widget.attrs.description }}</p>
{% if form_view != "vertical" %}
<div>
{% if field.field.required %}
<span class="nota-bene">{% translate "This field is required" %}</span>
{% endif %}
<div>
{% if not field.field.widget.attrs.fixed_paws %}
{{ field }}
Expand Down Expand Up @@ -35,7 +38,9 @@
{% else %}
<div>
{% if field.field.required %}
<span class="nota-bene">{% translate "This field is required" %}</span>
<label>
{{ field.label_tag }} <span class="nota-bene" aria-hidden>{% translate "(Required)" %}</span>
</label>
{% endif %}
<div>
{{ field }}
Expand Down
11 changes: 7 additions & 4 deletions rocky/rocky/views/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ def get_report_schedule_form_start_date_choice(self):
return self.report_schedule_form_start_date_choice(self.request.POST)

def get_report_schedule_form_start_date(self):
return self.report_schedule_form_start_date(self.request.POST)
return self.report_schedule_form_start_date()

def get_report_schedule_form_recurrence_choice(self):
return self.report_schedule_form_recurrence_choice(self.request.POST)

def get_report_schedule_form_recurrence(self):
return self.report_schedule_form_recurrence(self.request.POST)
return self.report_schedule_form_recurrence()

def get_report_parent_name_form(self):
return self.report_parent_name_form()
Expand All @@ -120,14 +120,17 @@ def get_task_details(self, task_id: str) -> Task | None:

return task

def create_report_schedule(self, report_recipe: ReportRecipe) -> ScheduleResponse | None:
def create_report_schedule(self, report_recipe: ReportRecipe, deadline_at: str) -> ScheduleResponse | None:
try:
report_task = ReportTask(
organisation_id=self.organization.code, report_recipe_id=str(report_recipe.recipe_id)
).model_dump()

schedule_request = ScheduleRequest(
scheduler_id=self.scheduler_id, data=report_task, schedule=report_recipe.cron_expression
scheduler_id=self.scheduler_id,
data=report_task,
schedule=report_recipe.cron_expression,
deadline_at=deadline_at,
)

submit_schedule = self.scheduler_client.post_schedule(schedule=schedule_request)
Expand Down
51 changes: 50 additions & 1 deletion rocky/tests/reports/test_aggregate_report_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def test_save_aggregate_report_view(
mock_bytes_client,
):
"""
Will send data through post to aggregate report.
Will send data through post to aggregate report and immediately creates a report (not scheduled).
"""

katalogus_mocker = mocker.patch("reports.views.base.get_katalogus")()
Expand Down Expand Up @@ -235,6 +235,55 @@ def test_save_aggregate_report_view(
assert "report_id=Report" in response.url


def test_save_aggregate_report_view_scheduled(
rf,
client_member,
valid_time,
mock_organization_view_octopoes,
listed_hostnames,
rocky_health,
mocker,
boefje_dns_records,
mock_bytes_client,
):
"""
Will send data through post to aggregate report and creates a scheduled aggregate report.
"""

katalogus_mocker = mocker.patch("reports.views.base.get_katalogus")()
katalogus_mocker.get_plugins.return_value = [boefje_dns_records]

rocky_health_mocker = mocker.patch("reports.report_types.aggregate_organisation_report.report.get_rocky_health")()
rocky_health_mocker.return_value = rocky_health

mock_bytes_client().upload_raw.return_value = "Report|1730b72f-b115-412e-ad44-dae6ab3edff9"

mock_organization_view_octopoes().list_objects.return_value = Paginated[OOIType](
count=len(listed_hostnames), items=listed_hostnames
)

request = setup_request(
rf.post(
"aggregate_report_save",
{
"observed_at": valid_time.strftime("%Y-%m-%d"),
"ooi": listed_hostnames,
"report_type": ["systems-report", "vulnerability-report"],
"choose_recurrence": "repeat",
"start_date": "2024-01-01",
"recurrence": "weekly",
"parent_report_name": ["Scheduled Aggregate Report %x"],
},
),
client_member.user,
)

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

assert response.status_code == 302 # after post follows redirect, this to first create report ID
assert response.url == f"/en/{client_member.organization.code}/reports/scheduled-reports/"


def test_json_download_aggregate_report(
rf,
client_member,
Expand Down
Loading

0 comments on commit 75de529

Please sign in to comment.