From 309c1646314fca6530b2d89372d84b35e905d083 Mon Sep 17 00:00:00 2001 From: Vadim Kazakov Date: Tue, 9 Jun 2026 17:46:09 +0300 Subject: [PATCH 1/2] feat: generate compare reports in web --- src/imgtests/database/database.py | 12 +++ src/imgtests/reporting/html_report.py | 21 ----- src/imgtests/runner.py | 6 +- src/imgtests/web/static/js/html_reports.js | 79 ++++++++++++++++++ .../tests_interface/reports_list.html | 82 +++++++++++++++++++ src/imgtests/web/tests_interface/urls.py | 10 +++ src/imgtests/web/tests_interface/views.py | 55 +++++++++++++ 7 files changed, 239 insertions(+), 26 deletions(-) create mode 100644 src/imgtests/web/static/js/html_reports.js diff --git a/src/imgtests/database/database.py b/src/imgtests/database/database.py index dff57f2e..4e878c7f 100644 --- a/src/imgtests/database/database.py +++ b/src/imgtests/database/database.py @@ -14,6 +14,8 @@ from imgtests.database.models.util_run_result import UtilRunResult, UtilType if TYPE_CHECKING: + from collections.abc import Sequence + from imgtests.sysrep import SystemInfo from imgtests.types import TestsCounts @@ -217,6 +219,16 @@ def return_table(self, table_name: Table) -> list[Any]: return session.query(models[table_name]).all() + def list_experiments(self) -> Sequence[ExperimentBase]: + self._check_session() + with self.session() as session: + return ( + session.query(ExperimentBase) + .options(selectinload(ExperimentBase.configuration)) + .order_by(ExperimentBase.experiment_id.desc()) + .all() + ) + def get_experiment_with_details(self, experiment_id: int) -> ExperimentBase: """Gets a single experiment with all related entities. diff --git a/src/imgtests/reporting/html_report.py b/src/imgtests/reporting/html_report.py index 1b510312..085e1173 100644 --- a/src/imgtests/reporting/html_report.py +++ b/src/imgtests/reporting/html_report.py @@ -223,27 +223,6 @@ def generate_compare_html_report( ) return report_path - def generate_last_two_experiments_report(self, out_dir: Path) -> Path | None: - from sqlalchemy import desc # noqa: PLC0415 - - from imgtests.database.models.experiment import ExperimentBase # noqa: PLC0415 - - self._database._check_session() # noqa: SLF001 - with self._database.session() as session: - experiments = ( - session.query(ExperimentBase) - .order_by(desc(ExperimentBase.started_at)) - .limit(2) - .all() - ) - ids = [exp.experiment_id for exp in experiments] - if len(ids) != 2: # noqa: PLR2004 - self._logger.error( - "Couldn't build a report: there are incorrect amount of experiments.", - ) - return None - return self.generate_compare_html_report(sorted(ids), out_dir) - def __distribute_metrics( self, metrics_by_exp: list[list[MetricSample]], diff --git a/src/imgtests/runner.py b/src/imgtests/runner.py index 3ed3c107..61fb9af8 100644 --- a/src/imgtests/runner.py +++ b/src/imgtests/runner.py @@ -709,8 +709,7 @@ def _get_clients(distro: str) -> tuple[SSHClient | None, SSHClient | None]: return suse_client, poky_client -def _run_single(distro: Distro, mode: Runner, config: dict[str, Any]) -> None: # noqa: PLR0912, PLR0915, C901 - from imgtests.reporting.html_report import ReportGenerator # noqa: PLC0415 +def _run_single(distro: Distro, mode: Runner, config: dict[str, Any]) -> None: # noqa: PLR0912, C901 from imgtests.suites.map import ( # noqa: PLC0415 ALL_SUBSYSTEMS_SUITE, ALL_SUITES, @@ -785,9 +784,6 @@ def _run_single(distro: Distro, mode: Runner, config: dict[str, Any]) -> None: if suse_client: run_profiled(suse_client, database) - report_generator = ReportGenerator(database) - report_generator.generate_last_two_experiments_report(out_dir=REPORTS_DIR) - database.session.close_all() diff --git a/src/imgtests/web/static/js/html_reports.js b/src/imgtests/web/static/js/html_reports.js new file mode 100644 index 00000000..b251697c --- /dev/null +++ b/src/imgtests/web/static/js/html_reports.js @@ -0,0 +1,79 @@ +const select1 = document.getElementById("exp-select-1"); +const select2 = document.getElementById("exp-select-2"); +const genBtn = document.getElementById("generate-btn"); +const resultDiv = document.getElementById("compare-result"); + +function fillSelect(select, data) { + data.forEach((exp) => { + const opt = document.createElement("option"); + opt.value = exp.id; + opt.textContent = `#${exp.id} | ${exp.os} | ${exp.description || "(no description)"} | ${exp.started_at || "?"}`; + select.appendChild(opt); + }); +} + +function updateButton() { + const v1 = select1.value; + const v2 = select2.value; + genBtn.disabled = !v1 || !v2 || v1 === v2; +} + +fetch("/api/experiments/") + .then((r) => r.json()) + .then((data) => { + if (data.experiments) { + fillSelect(select1, data.experiments); + fillSelect(select2, data.experiments); + } + }) + .catch(() => {}); + +select1.addEventListener("change", updateButton); +select2.addEventListener("change", updateButton); + +genBtn.addEventListener("click", function () { + const id1 = parseInt(select1.value, 10); + const id2 = parseInt(select2.value, 10); + genBtn.disabled = true; + genBtn.textContent = "Generating..."; + resultDiv.style.display = "none"; + + fetch("/api/generate-compare-report/", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": "{{ csrf_token }}", + }, + body: JSON.stringify({ + experiment_id_1: id1, + experiment_id_2: id2, + }), + }) + .then((r) => r.json()) + .then((data) => { + if (data.success) { + const reportUrl = + "/reports/view/" + data.report_url.replace(/\\/g, "/"); + resultDiv.innerHTML = + 'Report generated! Reload page to see it'; + resultDiv.style.display = "block"; + } else { + resultDiv.innerHTML = + 'Error: ' + + (data.error || "unknown") + + ""; + resultDiv.style.display = "block"; + } + }) + .catch((err) => { + resultDiv.innerHTML = + 'Request failed: ' + + err.message + + ""; + resultDiv.style.display = "block"; + }) + .finally(() => { + genBtn.disabled = false; + genBtn.textContent = "Generate"; + }); +}); diff --git a/src/imgtests/web/tests_interface/templates/tests_interface/reports_list.html b/src/imgtests/web/tests_interface/templates/tests_interface/reports_list.html index 01a93013..c0e9c83c 100644 --- a/src/imgtests/web/tests_interface/templates/tests_interface/reports_list.html +++ b/src/imgtests/web/tests_interface/templates/tests_interface/reports_list.html @@ -11,6 +11,85 @@

Test Reports

+
+

+ Generate Comparison Report +

+
+
+ + +
+
+ + +
+
+ +
+
+ +
+ {% if reports %}
{% for report in reports %} @@ -97,6 +176,9 @@

No Reports Found

{% endif %}
+{% load static %} + +