From 193764c28bf1904be2c7ca4d55e0884f7936b92e Mon Sep 17 00:00:00 2001 From: Jeroen Dekkers Date: Thu, 31 Oct 2024 09:17:22 +0100 Subject: [PATCH 01/64] Fix auth token middleware with wrong format header (#3755) Co-authored-by: Jan Klopper --- rocky/rocky/middleware/auth_token.py | 7 ++++--- rocky/tests/test_api.py | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/rocky/rocky/middleware/auth_token.py b/rocky/rocky/middleware/auth_token.py index 3916bde4cd7..97907aa0bb4 100644 --- a/rocky/rocky/middleware/auth_token.py +++ b/rocky/rocky/middleware/auth_token.py @@ -9,12 +9,13 @@ def middleware(request): if not request.user.is_authenticated and "authorization" in request.headers: authenticator = TokenAuthentication() try: - user, token = authenticator.authenticate(request) + user_and_token = authenticator.authenticate(request) except APIException: return HttpResponseForbidden("Invalid token\n") else: - request.user = user - structlog.contextvars.bind_contextvars(auth_method="token") + if user_and_token: + request.user = user_and_token[0] + structlog.contextvars.bind_contextvars(auth_method="token") return get_response(request) diff --git a/rocky/tests/test_api.py b/rocky/tests/test_api.py index 5ff09ae06f0..536210ceb5e 100644 --- a/rocky/tests/test_api.py +++ b/rocky/tests/test_api.py @@ -11,3 +11,9 @@ def test_api_2fa_enabled(client, settings, admin_user): response = client.get("/api/v1/organization/", headers={"Authorization": f"Token {token}"}) assert response.status_code == 200 + + +# Regression test for https://github.com/minvws/nl-kat-coordination/issues/3754 +def test_auth_header_wrong_format(client, settings, admin_user): + response = client.get("/api/v1/organization/", headers={"Authorization": "Not a token"}) + assert response.status_code == 401 From 14dc3b12d3d5711b17d76c730c4ae2723bdd60eb Mon Sep 17 00:00:00 2001 From: Madelon Dohmen <99282220+madelondohmen@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:01:04 +0100 Subject: [PATCH 02/64] Fix vulnerability chapters in Aggregate table of content (#3780) Co-authored-by: ammar92 --- rocky/reports/templates/partials/report_sidemenu.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocky/reports/templates/partials/report_sidemenu.html b/rocky/reports/templates/partials/report_sidemenu.html index bd7803cd88e..7e617dfce29 100644 --- a/rocky/reports/templates/partials/report_sidemenu.html +++ b/rocky/reports/templates/partials/report_sidemenu.html @@ -59,8 +59,8 @@

{% translate "Table of contents" %}

{{ vulnerabilities.title }} {% endif %} - - {% endfor %} + {% endfor %} + {% endif %} From 8937ec884d629008b1f17b3b06d11c9239e4da78 Mon Sep 17 00:00:00 2001 From: Jeroen Dekkers Date: Thu, 31 Oct 2024 15:07:30 +0100 Subject: [PATCH 03/64] Make systemctl call for kat-rocky-worker conditional (#3782) Co-authored-by: Jan Klopper --- scripts/installation/openkat-empty-job-queue.sh | 14 ++++++++++++-- scripts/installation/openkat-install.sh | 14 ++++++++++++-- scripts/installation/openkat-reset.sh | 14 ++++++++++++-- scripts/installation/openkat-restart.sh | 7 ++++++- scripts/installation/openkat-start.sh | 7 ++++++- scripts/installation/openkat-stop.sh | 7 ++++++- scripts/installation/openkat-update.sh | 7 ++++++- 7 files changed, 60 insertions(+), 10 deletions(-) diff --git a/scripts/installation/openkat-empty-job-queue.sh b/scripts/installation/openkat-empty-job-queue.sh index 7d85f5014f2..72452970756 100644 --- a/scripts/installation/openkat-empty-job-queue.sh +++ b/scripts/installation/openkat-empty-job-queue.sh @@ -2,7 +2,12 @@ # Stop openKAT echo "Stopping openKAT processes" -sudo systemctl stop xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl stop xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl stop kat-rocky-worker +fi # Start postgres, switch to the mula_db and empty the job queue echo "Emptying job queue" @@ -13,6 +18,11 @@ EOF # Start openKAT echo "Starting openKAT processes" -sudo systemctl start xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl start xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl stop kat-rocky-worker +fi echo "End of script. It might take a few more seconds for OpenKAT to be fully started and available." diff --git a/scripts/installation/openkat-install.sh b/scripts/installation/openkat-install.sh index b19f7b8d8ae..20b8c3183ac 100644 --- a/scripts/installation/openkat-install.sh +++ b/scripts/installation/openkat-install.sh @@ -285,9 +285,19 @@ echo "Step 6.11 - Set kat permissions in rabbitmq" sudo rabbitmqctl set_permissions -p "kat" "kat" ".*" ".*" ".*" echo "Step 7 - Configure start at system boot" -sudo systemctl enable kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl enable kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl enable kat-rocky-worker +fi echo "Step 8 - Restart OpenKAT" -sudo systemctl restart kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl restart kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl restart kat-rocky-worker +fi echo "Step 9 - End of OpenKAT install script" diff --git a/scripts/installation/openkat-reset.sh b/scripts/installation/openkat-reset.sh index df71266d6a4..c9c09b50a59 100644 --- a/scripts/installation/openkat-reset.sh +++ b/scripts/installation/openkat-reset.sh @@ -29,7 +29,12 @@ fi pushd / echo "Stop OpenKAT" -sudo systemctl stop xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl stop xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl stop kat-rocky-worker +fi echo "Delete XTDB databases" sudo rm -rf /var/lib/xtdb/* @@ -89,6 +94,11 @@ if [[ ${1} != "no_super_user" ]]; then fi echo "Start OpenKAT" -sudo systemctl start xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl start xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl start kat-rocky-worker +fi popd diff --git a/scripts/installation/openkat-restart.sh b/scripts/installation/openkat-restart.sh index 5cecf4547d3..ae4a505f594 100644 --- a/scripts/installation/openkat-restart.sh +++ b/scripts/installation/openkat-restart.sh @@ -1,4 +1,9 @@ #!/bin/bash echo "Restarting openKAT..." -sudo systemctl restart xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl restart xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl restart kat-rocky-worker +fi diff --git a/scripts/installation/openkat-start.sh b/scripts/installation/openkat-start.sh index 96df24dcc6a..dd0bcc6b9f9 100644 --- a/scripts/installation/openkat-start.sh +++ b/scripts/installation/openkat-start.sh @@ -1,4 +1,9 @@ #!/bin/bash echo "Starting openKAT..." -sudo systemctl start xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl start xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl start kat-rocky-worker +fi diff --git a/scripts/installation/openkat-stop.sh b/scripts/installation/openkat-stop.sh index f20d71cdfcb..4182ac767da 100644 --- a/scripts/installation/openkat-stop.sh +++ b/scripts/installation/openkat-stop.sh @@ -1,4 +1,9 @@ #!/bin/bash echo "Stopping openKAT..." -sudo systemctl stop xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl stop xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl stop kat-rocky-worker +fi diff --git a/scripts/installation/openkat-update.sh b/scripts/installation/openkat-update.sh index b8ef24b80aa..5c25e44b1ef 100644 --- a/scripts/installation/openkat-update.sh +++ b/scripts/installation/openkat-update.sh @@ -82,6 +82,11 @@ sudo -u kat update-katalogus-db sudo -u kat update-mula-db echo "Step 7 - Restart OpenKAT" -sudo systemctl restart xtdb-http-multinode kat-rocky kat-rocky-worker kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker +sudo systemctl restart xtdb-http-multinode kat-rocky kat-mula kat-bytes kat-boefjes kat-normalizers kat-katalogus kat-keiko kat-octopoes kat-octopoes-worker + +# Kat-rocky-worker service was introduced in OpenKAT 1.18 +if [ -f /usr/lib/systemd/system/kat-rocky-worker.service ]; then + sudo systemctl restart kat-rocky-worker +fi echo "End of OpenKAT update script" From 1f42bae445761f0b69e3c826f0786d8a21c3fa2d Mon Sep 17 00:00:00 2001 From: Madelon Dohmen <99282220+madelondohmen@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:28:48 +0100 Subject: [PATCH 04/64] Fix scheduled Aggregate Report naming (#3748) --- octopoes/octopoes/models/ooi/reports.py | 1 + rocky/reports/runner/report_runner.py | 128 ++++++++++++------ .../scheduled_reports_table.html | 22 +-- rocky/reports/views/base.py | 16 ++- rocky/reports/views/mixins.py | 111 +++++++++------ 5 files changed, 182 insertions(+), 96 deletions(-) diff --git a/octopoes/octopoes/models/ooi/reports.py b/octopoes/octopoes/models/ooi/reports.py index e3af38fe23e..525ad009b57 100644 --- a/octopoes/octopoes/models/ooi/reports.py +++ b/octopoes/octopoes/models/ooi/reports.py @@ -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 diff --git a/rocky/reports/runner/report_runner.py b/rocky/reports/runner/report_runner.py index aaaf3992e6a..9fa6454f576 100644 --- a/rocky/reports/runner/report_runner.py +++ b/rocky/reports/runner/report_runner.py @@ -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 @@ -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 diff --git a/rocky/reports/templates/report_overview/scheduled_reports_table.html b/rocky/reports/templates/report_overview/scheduled_reports_table.html index ced610163e0..b301a50e253 100644 --- a/rocky/reports/templates/report_overview/scheduled_reports_table.html +++ b/rocky/reports/templates/report_overview/scheduled_reports_table.html @@ -25,14 +25,20 @@ {{ schedule.recipe.report_name_format }}
    - {% for report_type in schedule.recipe.report_types %} - {% if forloop.counter0 < 2 %} -
  • {{ report_type|get_report_type_name }}
  • - {% endif %} - {% if forloop.counter0 == 2 %} -
  • + {{ schedule.recipe.report_types|length|add:"-2" }}
  • - {% endif %} - {% endfor %} + {% if schedule.recipe.parent_report_type == "aggregate-organisation-report" %} +
  • + {{ schedule.recipe.parent_report_type|get_report_type_name }} +
  • + {% else %} + {% for report_type in schedule.recipe.report_types %} + {% if forloop.counter0 < 2 %} +
  • {{ report_type|get_report_type_name }}
  • + {% endif %} + {% if forloop.counter0 == 2 %} +
  • + {{ schedule.recipe.report_types|length|add:"-2" }}
  • + {% endif %} + {% endfor %} + {% endif %}
{{ schedule.deadline_at }} diff --git a/rocky/reports/views/base.py b/rocky/reports/views/base.py index a9866b8db63..2d8514a3f18 100644 --- a/rocky/reports/views/base.py +++ b/rocky/reports/views/base.py @@ -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, ) @@ -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) diff --git a/rocky/reports/views/mixins.py b/rocky/reports/views/mixins.py index 46c31a1dffb..259c691a68e 100644 --- a/rocky/reports/views/mixins.py +++ b/rocky/reports/views/mixins.py @@ -1,4 +1,5 @@ from datetime import datetime, timezone +from string import Template from typing import Any from uuid import uuid4 @@ -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 @@ -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 @@ -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( @@ -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(), @@ -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): From 31a20dad0f0e1b62740da7ed5477710820c718d9 Mon Sep 17 00:00:00 2001 From: Rieven Date: Fri, 1 Nov 2024 11:35:59 +0100 Subject: [PATCH 05/64] Fixes for dropdowns (#3732) Co-authored-by: Peter-Paul van Gemerden Co-authored-by: stephanie0x00 <9821756+stephanie0x00@users.noreply.github.com> Co-authored-by: Jan Klopper --- rocky/assets/css/components/dropdown.scss | 169 +++++++++--------- rocky/assets/js/dropdown.js | 49 +++-- .../introduction.html | 6 +- .../templates/partials/report_header.html | 6 +- rocky/rocky/locale/django.pot | 13 +- .../organization_member_list.html | 4 +- .../templates/partials/language-switcher.html | 52 +++--- .../templates/partials/ooi_list_toolbar.html | 12 +- .../partials/organizations_menu_dropdown.html | 6 +- 9 files changed, 156 insertions(+), 161 deletions(-) diff --git a/rocky/assets/css/components/dropdown.scss b/rocky/assets/css/components/dropdown.scss index dd80fa95389..8f4dcee6022 100644 --- a/rocky/assets/css/components/dropdown.scss +++ b/rocky/assets/css/components/dropdown.scss @@ -12,10 +12,12 @@ overflow: hidden; min-width: 0; display: flex; + width: 100%; } .dropdown-list { display: none; + left: 0; position: absolute; top: 3.25rem; z-index: 2; @@ -29,112 +31,111 @@ border: 1px solid var(--colors-grey-200); background-color: var(--colors-white); - &[aria-expanded="true"] { - display: flex; - flex-direction: column; - min-width: 100%; - - > ul { - width: 100%; - padding: 0; - background-color: var(--colors-white); - list-style-type: disc; - gap: 0; - border-radius: var(--border-radius-s); + > ul { + margin: 0; + width: 100%; + padding: 0; + background-color: var(--colors-white); + list-style-type: disc; + gap: 0; + border-radius: var(--border-radius-s); + + &:not(:last-child) { + border-bottom: 1px solid var(--colors-grey-200); + } - &:not(:last-child) { - border-bottom: 1px solid var(--colors-grey-200); - } + li { + $icon-width: 1.25rem; - li { - $icon-width: 1.25rem; + border-top: 1px $offwhite solid; + padding: 0; + width: 100%; - border-top: 1px $offwhite solid; - padding: 0; + > a { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + gap: var(--spacing-grid-100); + color: var(--text-color-dark); + margin: 0; + min-height: 2.75rem; + padding: var(--spacing-grid-150); + text-decoration: none; width: 100%; - > a { + .icon { display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - gap: var(--spacing-grid-100); - color: var(--text-color-dark); - margin: 0; - min-height: 2.75rem; - padding: var(--spacing-grid-150); - text-decoration: none; - width: 100%; - - .icon { - display: flex; - width: $icon-width; - max-height: $icon-width; - - &::before { - color: var(--text-color-dark); - } - } + width: $icon-width; + max-height: $icon-width; - &:hover { + &::before { color: var(--text-color-dark); } - - /* Styling with subtitle */ - &:has(> div) { - flex-direction: column; - align-items: flex-start; - gap: var(--spacing-grid-0); - } - - > div { - display: flex; - flex-direction: row; - gap: var(--spacing-grid-100); - align-items: center; - } - - .nota-bene { - padding-left: calc($icon-width + var(--spacing-grid-100)); - } } - &[aria-current="true"] { - a::before { - content: "\ea5e"; - font-family: var( - --language-selector-list-button-icon-font-family - ); - margin-left: var( - --language-selector-list-button-icon-margin-left - ); - font-size: var(--language-selector-list-button-icon-font-size); - color: var(--language-selector-list-button-icon-text-color); - margin-right: 0.5rem; - width: 1.25rem; - } + &:hover { + color: var(--text-color-dark); } - &::marker { - content: none; + /* Styling with subtitle */ + &:has(> div) { + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-grid-0); } - &:first-child { - border-top: none; + > div { + display: flex; + flex-direction: row; + gap: var(--spacing-grid-100); + align-items: center; } - &:last-child { - // border-top should still inherit - border-left: none; - border-right: none; - border-bottom: none; + .nota-bene { + padding-left: calc($icon-width + var(--spacing-grid-100)); } + } - &:hover { - background-color: #f2f2f2; + &[aria-current="true"] { + a::before { + content: "\ea5e"; + font-family: var(--language-selector-list-button-icon-font-family); + margin-left: var(--language-selector-list-button-icon-margin-left); + font-size: var(--language-selector-list-button-icon-font-size); + color: var(--language-selector-list-button-icon-text-color); + margin-right: 0.5rem; + width: 1.25rem; } } + + &::marker { + content: none; + } + + &:first-child { + border-top: none; + } + + &:last-child { + // border-top should still inherit + border-left: none; + border-right: none; + border-bottom: none; + } + + &:hover { + background-color: #f2f2f2; + } } } } } + +.dropdown-list { + [aria-expanded="true"] + & { + display: flex; + flex-direction: column; + min-width: 100%; + } +} diff --git a/rocky/assets/js/dropdown.js b/rocky/assets/js/dropdown.js index 7cfe9da1e5b..9fdad876fd9 100644 --- a/rocky/assets/js/dropdown.js +++ b/rocky/assets/js/dropdown.js @@ -1,30 +1,29 @@ -const dropdowns = document.querySelectorAll(".dropdown"); +function toggleAriaExpanded(event) { + const currentButton = event.target; + const isExpanded = currentButton.getAttribute("aria-expanded") === "true"; + const activeButton = document.querySelector( + ".dropdown-button[aria-expanded='true']", + ); -dropdowns.forEach((dropdown) => { - const dropdownButton = dropdown.querySelector(".dropdown-button"); - const dropdownList = dropdown.querySelector(".dropdown-list"); + if (activeButton && currentButton !== activeButton) { + activeButton.setAttribute("aria-expanded", "false"); + } - const toggle = () => { - if (dropdownList.getAttribute("aria-expanded") == "true") { - closeDropdown(); - } else { - dropdownList.setAttribute("aria-expanded", "true"); - document.addEventListener("click", handleClose); - } - }; - - const handleClose = (event) => { - if (event.target == dropdownButton) { - return; - } + currentButton.setAttribute("aria-expanded", !isExpanded); +} - closeDropdown(); - }; +document.addEventListener("click", (event) => { + const isDropdownButtonClicked = + event.target.classList.contains("dropdown-button"); - const closeDropdown = () => { - document.removeEventListener("click", handleClose); - dropdownList.setAttribute("aria-expanded", "false"); - }; - - dropdownButton.addEventListener("click", () => toggle()); + if (isDropdownButtonClicked) { + toggleAriaExpanded(event); + } else { + activeButton = document.querySelector( + ".dropdown-button[aria-expanded='true']", + ); + if (activeButton) { + activeButton.setAttribute("aria-expanded", "false"); + } + } }); diff --git a/rocky/reports/report_types/aggregate_organisation_report/introduction.html b/rocky/reports/report_types/aggregate_organisation_report/introduction.html index abd58a3dafb..6d1b32433d9 100644 --- a/rocky/reports/report_types/aggregate_organisation_report/introduction.html +++ b/rocky/reports/report_types/aggregate_organisation_report/introduction.html @@ -5,10 +5,12 @@

{{ report_ooi.name }}