Skip to content

Commit

Permalink
Fix scheduled Aggregate Report naming (#3748)
Browse files Browse the repository at this point in the history
  • Loading branch information
madelondohmen authored Nov 1, 2024
1 parent 8937ec8 commit 1f42bae
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 96 deletions.
1 change: 1 addition & 0 deletions octopoes/octopoes/models/ooi/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class ReportRecipe(OOI):
subreport_name_format: str | None = None

input_recipe: dict[str, Any] # can contain a query which maintains a live set of OOIs or manually picked OOIs.
parent_report_type: str | None = None
report_types: list[str]

cron_expression: str
Expand Down
128 changes: 86 additions & 42 deletions rocky/reports/runner/report_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

from octopoes.connector.octopoes import OctopoesAPIConnector
from octopoes.models import Reference
from reports.report_types.aggregate_organisation_report.report import AggregateOrganisationReport, aggregate_reports
from reports.report_types.definitions import report_plugins_union
from reports.report_types.helpers import get_report_by_id
from reports.runner.models import ReportRunner
from reports.views.mixins import collect_reports, save_report_data
from reports.views.mixins import collect_reports, save_aggregate_report_data, save_report_data
from rocky.bytes_client import BytesClient
from rocky.scheduler import ReportTask

Expand All @@ -24,51 +25,94 @@ def run(self, report_task: ReportTask) -> None:

connector = OctopoesAPIConnector(settings.OCTOPOES_API, report_task.organisation_id)
recipe = connector.get(Reference.from_str(f"ReportRecipe|{report_task.report_recipe_id}"), valid_time)

report_types = [get_report_by_id(report_type_id) for report_type_id in recipe.report_types]
oois_count = len(recipe.input_recipe["input_oois"])
oois = []
now = datetime.now(timezone.utc)

error_reports, report_data = collect_reports(
valid_time, connector, recipe.input_recipe["input_oois"], report_types
)
for ooi_id in recipe.input_recipe["input_oois"]:
ooi = connector.get(Reference.from_str(ooi_id), valid_time)
oois.append(ooi)

self.bytes_client.organization = report_task.organisation_id
subreport_names = []
oois_count = len(recipe.input_recipe["input_oois"])

for report_type_id, data in report_data.items():
report_type = get_report_by_id(report_type_id)
if recipe.parent_report_type == AggregateOrganisationReport.id:
parent_report_name = now.strftime(
Template(recipe.report_name_format).safe_substitute(
report_type=str(AggregateOrganisationReport.name), oois_count=str(oois_count)
)
)
report_type_ids = [report.id for report in report_types]

for ooi in data:
if "${ooi}" in parent_report_name and oois_count == 1:
ooi = recipe.input_recipe["input_oois"][0]
ooi_human_readable = Reference.from_str(ooi).human_readable
subreport_name = Template(recipe.subreport_name_format).safe_substitute(
ooi=ooi_human_readable, report_type=str(report_type.name)
)
subreport_names.append((subreport_name, subreport_name))

parent_report_name = Template(recipe.report_name_format).safe_substitute(oois_count=str(oois_count))

if "${ooi}" in parent_report_name and oois_count == 1:
ooi = recipe.input_recipe["input_oois"][0]
ooi_human_readable = Reference.from_str(ooi).human_readable
parent_report_name = Template(parent_report_name).safe_substitute(ooi=ooi_human_readable)
if "${report_type}" in parent_report_name and len(report_types) == 1:
report_type = get_report_by_id(recipe.report_types[0])
parent_report_name = Template(parent_report_name).safe_substitute(report_type=str(report_type.name))

save_report_data(
self.bytes_client,
valid_time,
connector,
Organization.objects.get(code=report_task.organisation_id),
{
"input_data": {
"input_oois": recipe.input_recipe["input_oois"],
"report_types": recipe.report_types,
"plugins": report_plugins_union(report_types),
}
},
report_data,
subreport_names,
parent_report_name,
)

self.bytes_client.organization = None
parent_report_name = Template(parent_report_name).safe_substitute(ooi=ooi_human_readable)

aggregate_report, post_processed_data, report_data, report_errors = aggregate_reports(
connector, oois, report_type_ids, valid_time, report_task.organisation_id
)
save_aggregate_report_data(
self.bytes_client,
connector,
Organization.objects.get(code=report_task.organisation_id),
valid_time,
recipe.input_recipe["input_oois"],
{
"input_data": {
"input_oois": recipe.input_recipe["input_oois"],
"report_types": recipe.report_types,
"plugins": report_plugins_union(report_types),
}
},
parent_report_name,
report_data,
post_processed_data,
aggregate_report,
)
else:
subreport_names = []
error_reports, report_data = collect_reports(
valid_time, connector, recipe.input_recipe["input_oois"], report_types
)

for report_type_id, data in report_data.items():
report_type = get_report_by_id(report_type_id)

for ooi in data:
ooi_human_readable = Reference.from_str(ooi).human_readable
subreport_name = now.strftime(
Template(recipe.subreport_name_format).safe_substitute(
ooi=ooi_human_readable, report_type=str(report_type.name)
)
)
subreport_names.append((subreport_name, subreport_name))

parent_report_name = now.strftime(
Template(recipe.report_name_format).safe_substitute(oois_count=str(oois_count))
)

if "${ooi}" in parent_report_name and oois_count == 1:
ooi = recipe.input_recipe["input_oois"][0]
ooi_human_readable = Reference.from_str(ooi).human_readable
parent_report_name = Template(parent_report_name).safe_substitute(ooi=ooi_human_readable)

save_report_data(
self.bytes_client,
valid_time,
connector,
Organization.objects.get(code=report_task.organisation_id),
{
"input_data": {
"input_oois": recipe.input_recipe["input_oois"],
"report_types": recipe.report_types,
"plugins": report_plugins_union(report_types),
}
},
report_data,
subreport_names,
parent_report_name,
)

self.bytes_client.organization = None
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@
<td class="nowrap">{{ schedule.recipe.report_name_format }}</td>
<td>
<ul class="tags horizontal-view">
{% for report_type in schedule.recipe.report_types %}
{% if forloop.counter0 < 2 %}
<li class="label tags-color-{{ report_type|get_report_type_label_style }}">{{ report_type|get_report_type_name }}</li>
{% endif %}
{% if forloop.counter0 == 2 %}
<li class="label tags-color-grey-2">+ {{ schedule.recipe.report_types|length|add:"-2" }}</li>
{% endif %}
{% endfor %}
{% if schedule.recipe.parent_report_type == "aggregate-organisation-report" %}
<li class="label tags-color-{{ schedule.recipe.parent_report_type|get_report_type_label_style }}">
{{ schedule.recipe.parent_report_type|get_report_type_name }}
</li>
{% else %}
{% for report_type in schedule.recipe.report_types %}
{% if forloop.counter0 < 2 %}
<li class="label tags-color-{{ report_type|get_report_type_label_style }}">{{ report_type|get_report_type_name }}</li>
{% endif %}
{% if forloop.counter0 == 2 %}
<li class="label tags-color-grey-2">+ {{ schedule.recipe.report_types|length|add:"-2" }}</li>
{% endif %}
{% endfor %}
{% endif %}
</ul>
</td>
<td class="nowrap">{{ schedule.deadline_at }}</td>
Expand Down
16 changes: 13 additions & 3 deletions rocky/reports/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,15 @@ def is_scheduled_report(self) -> bool:
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:
def create_report_recipe(
self, report_name_format: str, subreport_name_format: str, parent_report_type: str | None, schedule: str
) -> ReportRecipe:
report_recipe = ReportRecipe(
recipe_id=uuid4(),
report_name_format=report_name_format,
subreport_name_format=subreport_name_format,
input_recipe={"input_oois": self.get_ooi_pks()},
parent_report_type=parent_report_type,
report_types=self.get_report_type_ids(),
cron_expression=schedule,
)
Expand Down Expand Up @@ -532,13 +535,20 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
elif self.is_scheduled_report():
report_name_format = request.POST.get("parent_report_name", "")
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())

parent_report_type = None
if self.report_type is not None:
parent_report_type = self.report_type.id
elif not self.report_type and subreport_name_format:
parent_report_type = ConcatenatedReport.id

schedule = self.convert_recurrence_to_cron_expressions(recurrence)

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

self.create_report_schedule(report_recipe, deadline_at)

Expand Down
111 changes: 68 additions & 43 deletions rocky/reports/views/mixins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime, timezone
from string import Template
from typing import Any
from uuid import uuid4

Expand Down Expand Up @@ -76,7 +77,8 @@ def save_report_data(
raw_id = bytes_client.upload_raw(
raw=ReportDataDict(input_data).model_dump_json().encode(), manual_mime_types={"openkat/report"}
)
name = now.strftime(parent_report_name.replace("${report_type}", str(ConcatenatedReport.name)))

name = now.strftime(Template(parent_report_name).safe_substitute(report_type=str(ConcatenatedReport.name)))

if not name or name.isspace():
name = ConcatenatedReport.name
Expand Down Expand Up @@ -164,7 +166,7 @@ def save_report_data(
manual_mime_types={"openkat/report"},
)
report_type = get_report_by_id(report_type_id)
name = now.strftime(parent_report_name.replace("${report_type}", str(report_type.name)))
name = now.strftime(Template(parent_report_name).safe_substitute(report_type=str(report_type.name)))

if not name or name.isspace():
name = ConcatenatedReport.name
Expand All @@ -189,6 +191,59 @@ def save_report_data(
return parent_report_ooi


def save_aggregate_report_data(
bytes_client,
octopoes_api_connector,
organization,
get_observed_at,
ooi_pks,
input_data: dict,
parent_report_name,
report_data,
post_processed_data,
aggregate_report,
) -> Report:
observed_at = get_observed_at

now = datetime.utcnow()

# Create the report
report_data_raw_id = bytes_client.upload_raw(
raw=ReportDataDict(post_processed_data | input_data).model_dump_json().encode(),
manual_mime_types={"openkat/report"},
)
report_type = type(aggregate_report)
name = now.strftime(parent_report_name)
if not name or name.isspace():
name = report_type.name

report_ooi = Report(
name=str(name),
report_type=str(report_type.id),
template=report_type.template_path,
report_id=uuid4(),
organization_code=organization.code,
organization_name=organization.name,
organization_tags=list(organization.tags.all()),
data_raw_id=report_data_raw_id,
date_generated=datetime.now(timezone.utc),
input_oois=ooi_pks,
observed_at=observed_at,
parent_report=None,
has_parent=False,
)
create_ooi(octopoes_api_connector, bytes_client, report_ooi, observed_at)

# Save the child reports to bytes
for ooi, types in report_data.items():
for report_type, data in types.items():
bytes_client.upload_raw(
raw=ReportDataDict(data | input_data).model_dump_json().encode(), manual_mime_types={"openkat/report"}
)

return report_ooi


class SaveGenerateReportMixin(BaseReportView):
def save_report(self, report_names: list) -> Report | None:
error_reports, report_data = collect_reports(
Expand Down Expand Up @@ -224,7 +279,6 @@ def save_report(self, report_names: list) -> Report | None:

class SaveAggregateReportMixin(BaseReportView):
def save_report(self, report_names: list) -> Report:
organization = self.organization
aggregate_report, post_processed_data, report_data, report_errors = aggregate_reports(
self.octopoes_api_connector,
self.get_oois(),
Expand All @@ -243,47 +297,18 @@ def save_report(self, report_names: list) -> Report:
}
messages.add_message(self.request, messages.ERROR, error_message)

observed_at = self.get_observed_at()

now = datetime.utcnow()
bytes_client = self.bytes_client

# Create the report
report_data_raw_id = bytes_client.upload_raw(
raw=ReportDataDict(post_processed_data | self.get_input_data()).model_dump_json().encode(),
manual_mime_types={"openkat/report"},
)
report_type = type(aggregate_report)
name = now.strftime(report_names[0][1])
if not name or name.isspace():
name = report_type.name

report_ooi = Report(
name=str(name),
report_type=str(report_type.id),
template=report_type.template_path,
report_id=uuid4(),
organization_code=organization.code,
organization_name=organization.name,
organization_tags=list(organization.tags.all()),
data_raw_id=report_data_raw_id,
date_generated=datetime.now(timezone.utc),
input_oois=self.get_ooi_pks(),
observed_at=observed_at,
parent_report=None,
has_parent=False,
return save_aggregate_report_data(
self.bytes_client,
self.octopoes_api_connector,
self.organization,
self.get_observed_at(),
self.get_ooi_pks(),
self.get_input_data(),
report_names[0][1],
report_data,
post_processed_data,
aggregate_report,
)
create_ooi(self.octopoes_api_connector, bytes_client, report_ooi, observed_at)

# Save the child reports to bytes
for ooi, types in report_data.items():
for report_type, data in types.items():
bytes_client.upload_raw(
raw=ReportDataDict(data | self.get_input_data()).model_dump_json().encode(),
manual_mime_types={"openkat/report"},
)

return report_ooi


class SaveMultiReportMixin(BaseReportView):
Expand Down

0 comments on commit 1f42bae

Please sign in to comment.