Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions src/imgtests/reporting/excel_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def configuration_id_from_os(
) -> int | str:
distribution_name = distribution_name_from_os(os_name)

if not distribution_name:
if not distribution_name or distribution_name not in distributions:
return ""

return int(distributions[distribution_name]["configuration_id"])
Expand Down Expand Up @@ -268,23 +268,13 @@ def build_distribution_records(
msg = f"Unsupported distributions: {joined_distributions}. Available: {valid_names}."
raise ValueError(msg)

missing_distributions = [
distribution_name
for distribution_name in DISTRIBUTION_DESCRIPTIONS
if distribution_name not in ids
]

if missing_distributions:
joined_distributions = ", ".join(missing_distributions)
msg = f"Missing configuration ids for distributions: {joined_distributions}."
raise ValueError(msg)

return {
distribution_name: DistributionRecord(
configuration_id=ids[distribution_name],
distribution_description=distribution_description,
distribution_description=DISTRIBUTION_DESCRIPTIONS[distribution_name],
)
for distribution_name, distribution_description in DISTRIBUTION_DESCRIPTIONS.items()
for distribution_name in ids
if distribution_name in DISTRIBUTION_DESCRIPTIONS
}


Expand Down
23 changes: 21 additions & 2 deletions src/imgtests/web/static/js/excel_reports.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,26 @@ function getCookie(name) {
return cookieValue;
}

function getCheckedValues(selector) {
return Array.from(document.querySelectorAll(selector))
.filter((cb) => cb.checked)
.map((cb) => cb.value);
}

document.getElementById("exportBtn").addEventListener("click", function () {
const btn = this;
const tables = getCheckedValues(".table-checkbox");
const distributions = getCheckedValues(".distro-checkbox");

if (tables.length === 0) {
alert("Please select at least one table.");
return;
}
if (distributions.length === 0) {
alert("Please select at least one distribution.");
return;
}

btn.disabled = true;
btn.textContent = "Generating...";

Expand All @@ -26,6 +44,7 @@ document.getElementById("exportBtn").addEventListener("click", function () {
"X-CSRFToken": getCookie("csrftoken"),
"Content-Type": "application/json",
},
body: JSON.stringify({ tables: tables, distributions: distributions }),
})
.then((response) => response.json())
.then((data) => {
Expand All @@ -35,12 +54,12 @@ document.getElementById("exportBtn").addEventListener("click", function () {
} else {
alert("Error: " + data.error);
btn.disabled = false;
btn.textContent = "Export to Excel";
btn.textContent = "Generate New Excel Report";
}
})
.catch((error) => {
alert("Error: " + error);
btn.disabled = false;
btn.textContent = "Export to Excel";
btn.textContent = "Generate New Excel Report";
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,29 @@ <h1>Excel Reports</h1>
</div>
</div>

<div class="controls" style="margin-bottom: 20px">
<div class="controls" style="margin-bottom: 20px; flex-wrap: wrap;">
<div style="width: 100%;">
<div style="margin-bottom: 8px">
<strong style="font-size: 0.9rem; color: #5d4037; margin-right: 12px;">Distributions:</strong>
{% for key, desc in distributions.items %}
<label style="margin-right: 16px; font-size: 0.9rem; color: #5d4037;">
<input type="checkbox" class="distro-checkbox" value="{{ key }}" checked>
{{ key }}
</label>
{% endfor %}
</div>
<div style="margin-bottom: 12px">
<strong style="font-size: 0.9rem; color: #5d4037; margin-right: 12px;">Tables:</strong>
<label style="margin-right: 16px; font-size: 0.9rem; color: #5d4037;">
<input type="checkbox" class="table-checkbox" value="experiment" checked>
experiment
</label>
<label style="font-size: 0.9rem; color: #5d4037;">
<input type="checkbox" class="table-checkbox" value="util_run_result" checked>
util_run_result
</label>
</div>
</div>
<button id="exportBtn" class="btn btn-success">
Generate New Excel Report
</button>
Expand Down
59 changes: 51 additions & 8 deletions src/imgtests/web/tests_interface/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import re
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from django.core.management import call_command
from django.http import FileResponse, Http404, HttpRequest, JsonResponse
Expand All @@ -13,11 +13,12 @@
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from pydantic_core._pydantic_core import ValidationError
from sqlalchemy import create_engine
from sqlalchemy import Engine, create_engine, text

from imgtests.constant import CONFIG_DIR, EXCEL_REPORTS_DIR, REPORTS_DIR
from imgtests.constant import CONFIG_DIR, DISTRIBUTION_DESCRIPTIONS, EXCEL_REPORTS_DIR, REPORTS_DIR
from imgtests.database.database import ImgtestsDatabase, PostgresCreds
from imgtests.reporting.excel_export import export_database_to_excel
from imgtests.reporting.cli import EXPORT_TABLES
from imgtests.reporting.excel_export import distribution_name_from_os, export_database_to_excel
from imgtests.reporting.html_report import ReportGenerator
from imgtests.runner import get_test_name
from imgtests.suites.map import ALL_SUITES
Expand Down Expand Up @@ -375,9 +376,21 @@ def __safe_relative_path(*parts: str) -> Path:

@csrf_exempt
@require_http_methods(["POST"])
def api_export_excel(request: HttpRequest) -> JsonResponse: # noqa: ARG001
def api_export_excel(request: HttpRequest) -> JsonResponse:
try:
body = json.loads(request.body) if request.body else {}
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid request body"}, status=400)

tables = body.get("tables", list(EXPORT_TABLES))
selected_distributions = body.get("distributions")

if not tables:
return JsonResponse({"error": "At least one table must be selected"}, status=400)

timestamp = timezone.now().strftime("%Y%m%d_%H%M%S")
output_path = EXCEL_REPORTS_DIR / f"export_{timestamp}.xlsx"

try:
db_creds = PostgresCreds()
except ValidationError:
Expand All @@ -391,10 +404,12 @@ def api_export_excel(request: HttpRequest) -> JsonResponse: # noqa: ARG001

try:
engine = create_engine(db_url)
configuration_ids = _resolve_configuration_ids(engine, selected_distributions)
export_database_to_excel(
engine=engine,
output_path=output_path,
configuration_ids={"poky": 1, "suse": 2},
configuration_ids=configuration_ids,
tables=tables,
)
except Exception as err: # noqa: BLE001
return JsonResponse({"error": f"Export failed: {err}"}, status=500)
Expand All @@ -410,10 +425,37 @@ def api_export_excel(request: HttpRequest) -> JsonResponse: # noqa: ARG001
)


def _resolve_configuration_ids(
engine: Engine,
selected_distributions: list[str] | None = None,
) -> dict[str, int]:

with engine.connect() as conn:
result = conn.execute(text("SELECT config_id, os FROM configuration"))
configs = result.all()

all_distros: dict[str, int] = {}
for config_id, os_name in configs:
distro = distribution_name_from_os(os_name)
if distro and distro not in all_distros:
all_distros[distro] = config_id

if selected_distributions is not None:
missing = [d for d in selected_distributions if d not in all_distros]
if missing:
msg = f"No configuration found for: {', '.join(missing)}."
raise ValueError(msg)
return {d: all_distros[d] for d in selected_distributions if d in all_distros}

return all_distros


def excel_report_list(request: HttpRequest) -> HttpResponse:
context: dict[str, Any] = {"distributions": DISTRIBUTION_DESCRIPTIONS}
reports: list[dict[str, str | float]] = []
if not EXCEL_REPORTS_DIR.exists():
return render(request, "tests_interface/excel_reports.html", {"reports": reports})
context["reports"] = reports
return render(request, "tests_interface/excel_reports.html", context)

for file_path in sorted(EXCEL_REPORTS_DIR.glob("*.xlsx"), reverse=True):
stat = file_path.stat()
Expand All @@ -425,7 +467,8 @@ def excel_report_list(request: HttpRequest) -> HttpResponse:
},
)

return render(request, "tests_interface/excel_reports.html", {"reports": reports})
context["reports"] = reports
return render(request, "tests_interface/excel_reports.html", context)


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