Skip to content

Commit 965350e

Browse files
authored
feat: add distro and table selection to excel report page in UI (#413)
1 parent 8e978c3 commit 965350e

4 files changed

Lines changed: 101 additions & 25 deletions

File tree

‎src/imgtests/reporting/excel_export.py‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def configuration_id_from_os(
238238
) -> int | str:
239239
distribution_name = distribution_name_from_os(os_name)
240240

241-
if not distribution_name:
241+
if not distribution_name or distribution_name not in distributions:
242242
return ""
243243

244244
return int(distributions[distribution_name]["configuration_id"])
@@ -268,23 +268,12 @@ def build_distribution_records(
268268
msg = f"Unsupported distributions: {joined_distributions}. Available: {valid_names}."
269269
raise ValueError(msg)
270270

271-
missing_distributions = [
272-
distribution_name
273-
for distribution_name in DISTRIBUTION_DESCRIPTIONS
274-
if distribution_name not in ids
275-
]
276-
277-
if missing_distributions:
278-
joined_distributions = ", ".join(missing_distributions)
279-
msg = f"Missing configuration ids for distributions: {joined_distributions}."
280-
raise ValueError(msg)
281-
282271
return {
283272
distribution_name: DistributionRecord(
284273
configuration_id=ids[distribution_name],
285-
distribution_description=distribution_description,
274+
distribution_description=DISTRIBUTION_DESCRIPTIONS[distribution_name],
286275
)
287-
for distribution_name, distribution_description in DISTRIBUTION_DESCRIPTIONS.items()
276+
for distribution_name in ids
288277
}
289278

290279

‎src/imgtests/web/static/js/excel_reports.js‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,26 @@ function getCookie(name) {
1515
return cookieValue;
1616
}
1717

18+
function getCheckedValues(selector) {
19+
return Array.from(document.querySelectorAll(selector))
20+
.filter((cb) => cb.checked)
21+
.map((cb) => cb.value);
22+
}
23+
1824
document.getElementById("exportBtn").addEventListener("click", function () {
1925
const btn = this;
26+
const tables = getCheckedValues(".table-checkbox");
27+
const distributions = getCheckedValues(".distro-checkbox");
28+
29+
if (tables.length === 0) {
30+
alert("Please select at least one table.");
31+
return;
32+
}
33+
if (distributions.length === 0) {
34+
alert("Please select at least one distribution.");
35+
return;
36+
}
37+
2038
btn.disabled = true;
2139
btn.textContent = "Generating...";
2240

@@ -26,6 +44,7 @@ document.getElementById("exportBtn").addEventListener("click", function () {
2644
"X-CSRFToken": getCookie("csrftoken"),
2745
"Content-Type": "application/json",
2846
},
47+
body: JSON.stringify({ tables: tables, distributions: distributions }),
2948
})
3049
.then((response) => response.json())
3150
.then((data) => {
@@ -35,12 +54,12 @@ document.getElementById("exportBtn").addEventListener("click", function () {
3554
} else {
3655
alert("Error: " + data.error);
3756
btn.disabled = false;
38-
btn.textContent = "Export to Excel";
57+
btn.textContent = "Generate New Excel Report";
3958
}
4059
})
4160
.catch((error) => {
4261
alert("Error: " + error);
4362
btn.disabled = false;
44-
btn.textContent = "Export to Excel";
63+
btn.textContent = "Generate New Excel Report";
4564
});
4665
});

‎src/imgtests/web/tests_interface/templates/tests_interface/excel_reports.html‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,29 @@ <h1>{% translate "Excel Reports" %}</h1>
1212
</div>
1313
</div>
1414

15-
<div class="controls" style="margin-bottom: 20px">
15+
<div class="controls" style="margin-bottom: 20px; flex-wrap: wrap;">
16+
<div style="width: 100%;">
17+
<div style="margin-bottom: 8px">
18+
<strong style="font-size: 0.9rem; color: #5d4037; margin-right: 12px;">Distributions:</strong>
19+
{% for key, desc in distributions.items %}
20+
<label style="margin-right: 16px; font-size: 0.9rem; color: #5d4037;">
21+
<input type="checkbox" class="distro-checkbox" value="{{ key }}" checked>
22+
{{ key }}
23+
</label>
24+
{% endfor %}
25+
</div>
26+
<div style="margin-bottom: 12px">
27+
<strong style="font-size: 0.9rem; color: #5d4037; margin-right: 12px;">Tables:</strong>
28+
<label style="margin-right: 16px; font-size: 0.9rem; color: #5d4037;">
29+
<input type="checkbox" class="table-checkbox" value="experiment" checked>
30+
experiment
31+
</label>
32+
<label style="font-size: 0.9rem; color: #5d4037;">
33+
<input type="checkbox" class="table-checkbox" value="util_run_result" checked>
34+
util_run_result
35+
</label>
36+
</div>
37+
</div>
1638
<button id="exportBtn" class="btn btn-success">
1739
{% translate "Generate New Excel Report" %}
1840
</button>

‎src/imgtests/web/tests_interface/views.py‎

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import re
44
from pathlib import Path
5-
from typing import TYPE_CHECKING
5+
from typing import TYPE_CHECKING, Any
66

77
from django.core.management import call_command
88
from django.http import FileResponse, Http404, HttpRequest, JsonResponse
@@ -13,11 +13,14 @@
1313
from django.views.decorators.csrf import csrf_exempt
1414
from django.views.decorators.http import require_http_methods
1515
from pydantic_core._pydantic_core import ValidationError
16-
from sqlalchemy import create_engine
16+
from sqlalchemy import Engine, create_engine, select
17+
from sqlalchemy.orm import Session
1718

18-
from imgtests.constant import CONFIG_DIR, EXCEL_REPORTS_DIR, REPORTS_DIR
19+
from imgtests.constant import CONFIG_DIR, DISTRIBUTION_DESCRIPTIONS, EXCEL_REPORTS_DIR, REPORTS_DIR
1920
from imgtests.database.database import ImgtestsDatabase, PostgresCreds
20-
from imgtests.reporting.excel_export import export_database_to_excel
21+
from imgtests.database.models.configuration import ConfigurationBase
22+
from imgtests.reporting.cli import EXPORT_TABLES
23+
from imgtests.reporting.excel_export import distribution_name_from_os, export_database_to_excel
2124
from imgtests.reporting.html_report import ReportGenerator
2225
from imgtests.runner import get_test_name
2326
from imgtests.suites.map import ALL_SUITES
@@ -375,9 +378,21 @@ def __safe_relative_path(*parts: str) -> Path:
375378

376379
@csrf_exempt
377380
@require_http_methods(["POST"])
378-
def api_export_excel(request: HttpRequest) -> JsonResponse: # noqa: ARG001
381+
def api_export_excel(request: HttpRequest) -> JsonResponse:
382+
try:
383+
body = json.loads(request.body) if request.body else {}
384+
except json.JSONDecodeError:
385+
return JsonResponse({"error": "Invalid request body"}, status=400)
386+
387+
tables = body.get("tables", list(EXPORT_TABLES))
388+
selected_distributions = body.get("distributions")
389+
390+
if not tables:
391+
return JsonResponse({"error": "At least one table must be selected"}, status=400)
392+
379393
timestamp = timezone.now().strftime("%Y%m%d_%H%M%S")
380394
output_path = EXCEL_REPORTS_DIR / f"export_{timestamp}.xlsx"
395+
381396
try:
382397
db_creds = PostgresCreds()
383398
except ValidationError:
@@ -391,10 +406,12 @@ def api_export_excel(request: HttpRequest) -> JsonResponse: # noqa: ARG001
391406

392407
try:
393408
engine = create_engine(db_url)
409+
configuration_ids = _resolve_configuration_ids(engine, selected_distributions)
394410
export_database_to_excel(
395411
engine=engine,
396412
output_path=output_path,
397-
configuration_ids={"poky": 1, "suse": 2},
413+
configuration_ids=configuration_ids,
414+
tables=tables,
398415
)
399416
except Exception as err: # noqa: BLE001
400417
return JsonResponse({"error": f"Export failed: {err}"}, status=500)
@@ -410,10 +427,38 @@ def api_export_excel(request: HttpRequest) -> JsonResponse: # noqa: ARG001
410427
)
411428

412429

430+
def _resolve_configuration_ids(
431+
engine: Engine,
432+
selected_distributions: list[str] | None = None,
433+
) -> dict[str, int]:
434+
435+
with Session(engine) as session:
436+
query = select(ConfigurationBase.config_id, ConfigurationBase.os)
437+
configs = session.execute(query)
438+
439+
# TODO: if a single distro has multiple config ids the first one is selected
440+
all_distros: dict[str, int] = {}
441+
for config_id, os_name in configs:
442+
distro = distribution_name_from_os(os_name)
443+
if distro and distro not in all_distros:
444+
all_distros[distro] = config_id
445+
446+
if selected_distributions is not None:
447+
missing = [d for d in selected_distributions if d not in all_distros]
448+
if missing:
449+
msg = f"No configuration found for: {', '.join(missing)}."
450+
raise ValueError(msg)
451+
return {d: all_distros[d] for d in selected_distributions}
452+
453+
return all_distros
454+
455+
413456
def excel_report_list(request: HttpRequest) -> HttpResponse:
457+
context: dict[str, Any] = {"distributions": DISTRIBUTION_DESCRIPTIONS}
414458
reports: list[dict[str, str | float]] = []
415459
if not EXCEL_REPORTS_DIR.exists():
416-
return render(request, "tests_interface/excel_reports.html", {"reports": reports})
460+
context["reports"] = reports
461+
return render(request, "tests_interface/excel_reports.html", context)
417462

418463
for file_path in sorted(EXCEL_REPORTS_DIR.glob("*.xlsx"), reverse=True):
419464
stat = file_path.stat()
@@ -425,7 +470,8 @@ def excel_report_list(request: HttpRequest) -> HttpResponse:
425470
},
426471
)
427472

428-
return render(request, "tests_interface/excel_reports.html", {"reports": reports})
473+
context["reports"] = reports
474+
return render(request, "tests_interface/excel_reports.html", context)
429475

430476

431477
def download_excel_report(request: HttpRequest, filename: str) -> FileResponse: # noqa: ARG001

0 commit comments

Comments
 (0)