From a0427066c4362fb42fcaf753dfeeebe44e8486f0 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 18 Sep 2025 14:45:36 +0200 Subject: [PATCH] fix: add error handling for report fetching Currently if fetching data fails, the UI just shows a spinner. These changes add some proper error reporting. --- report-app/src/app/app.html | 9 ++- report-app/src/app/app.ts | 1 + .../pages/report-viewer/report-viewer.html | 2 + .../app/pages/report-viewer/report-viewer.ts | 1 + .../src/app/services/reports-fetcher.ts | 62 ++++++++++++------- .../src/app/shared/styles/callouts.scss | 6 ++ 6 files changed, 57 insertions(+), 24 deletions(-) diff --git a/report-app/src/app/app.html b/report-app/src/app/app.html index 3876348..2b0a412 100644 --- a/report-app/src/app/app.html +++ b/report-app/src/app/app.html @@ -32,8 +32,13 @@

Web Codegen Scorer

alt="Loading Web Codegen Scorer Logo" class="loading-logo no-animation" /> -
No reports available
-
Run web-codegen-scorer eval to generate a report
+ + @if (groupsError()) { +
{{groupsError()}}
+ } @else { +
No reports available
+
Run web-codegen-scorer eval to generate a report
+ } } } diff --git a/report-app/src/app/app.ts b/report-app/src/app/app.ts index 5163570..ef754c7 100644 --- a/report-app/src/app/app.ts +++ b/report-app/src/app/app.ts @@ -18,6 +18,7 @@ export class App { protected isLoading = this.reportsFetcher.isLoadingReportsList; protected isServer = isPlatformServer(inject(PLATFORM_ID)); protected colorMode = this.colorModeService.colorMode; + protected groupsError = this.reportsFetcher.reportGroupsError; protected toggleColorMode() { this.colorModeService.setColorMode( diff --git a/report-app/src/app/pages/report-viewer/report-viewer.html b/report-app/src/app/pages/report-viewer/report-viewer.html index eea2a87..80f9e19 100644 --- a/report-app/src/app/pages/report-viewer/report-viewer.html +++ b/report-app/src/app/pages/report-viewer/report-viewer.html @@ -137,6 +137,8 @@

Usage Details

@if (isLoading()) { + } @else if (error()) { +
{{error()?.stack}}
} @else if (report) { @let details = report.details; diff --git a/report-app/src/app/pages/report-viewer/report-viewer.ts b/report-app/src/app/pages/report-viewer/report-viewer.ts index a81bbe7..82f3989 100644 --- a/report-app/src/app/pages/report-viewer/report-viewer.ts +++ b/report-app/src/app/pages/report-viewer/report-viewer.ts @@ -75,6 +75,7 @@ export class ReportViewer { protected reportGroupId = input.required({ alias: 'id' }); protected formatted = signal>(new Map()); protected formatScore = formatScore; + protected error = computed(() => this.selectedReport.error()); private selectedReport = resource({ params: () => ({ groupId: this.reportGroupId() }), diff --git a/report-app/src/app/services/reports-fetcher.ts b/report-app/src/app/services/reports-fetcher.ts index 1187a73..d4c3b79 100644 --- a/report-app/src/app/services/reports-fetcher.ts +++ b/report-app/src/app/services/reports-fetcher.ts @@ -19,15 +19,19 @@ export class ReportsFetcher { if (!isPlatformBrowser(this.platformId)) { return []; } - return fetch('/api/reports') - .then((r) => r.json() as Promise) - .then((groups) => - groups.sort((a, b) => { - return ( - new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() - ); - }) - ); + + const response = await fetch('/api/reports'); + + if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } + + const groups = (await response.json()) as RunGroup[]; + + return groups.sort( + (a, b) => + new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() + ); }, }); @@ -35,6 +39,8 @@ export class ReportsFetcher { return this.groupsResource.hasValue() ? this.groupsResource.value() : []; }); + readonly reportGroupsError = computed(() => this.groupsResource.error()); + readonly isLoadingSingleReport = computed(() => this.pendingFetches() > 0); readonly isLoadingReportsList = computed(() => this.groupsResource.isLoading() @@ -44,19 +50,31 @@ export class ReportsFetcher { if (!this.runCache.has(groupId)) { this.pendingFetches.update((current) => current + 1); - const allRuns = await fetch(`/api/reports/${groupId}`).then( - (r) => r.json() as Promise - ); - const firstRun = allRuns[0]; - const combined = { - id: firstRun.id, - group: firstRun.group, - details: firstRun.details, - results: allRuns.flatMap((run) => run.results), - } satisfies RunInfo; - - this.runCache.set(groupId, combined); - this.pendingFetches.update((current) => current - 1); + try { + const response = await fetch(`/api/reports/${groupId}`); + + if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } + + const allRuns = (await response.json()) as RunInfo[]; + + if (!Array.isArray(allRuns) || allRuns.length === 0) { + throw new Error(`Could not find report with id: ${groupId}`); + } + + const firstRun = allRuns[0]; + const combined = { + id: firstRun.id, + group: firstRun.group, + details: firstRun.details, + results: allRuns.flatMap((run) => run.results), + } satisfies RunInfo; + + this.runCache.set(groupId, combined); + } finally { + this.pendingFetches.update((current) => current - 1); + } } return this.runCache.get(groupId)!; diff --git a/report-app/src/app/shared/styles/callouts.scss b/report-app/src/app/shared/styles/callouts.scss index deaff0e..84bc52c 100644 --- a/report-app/src/app/shared/styles/callouts.scss +++ b/report-app/src/app/shared/styles/callouts.scss @@ -24,5 +24,11 @@ background-color: #fffbeb; border-color: #fef3c7; } + + &.error { + color: #cd0000; + background-color: #ffe7e7; + border-color: #df9e9e; + } } }