From af54c03f53f030072826bb8c62971ffe167ccb79 Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Tue, 1 Sep 2026 00:18:55 +0200 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20Show=20actionable=20feedback=20?= =?UTF-8?q?when=20the=20All=20Messages=20query=20fails=20or=20times=20out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ServiceControl now terminates audit queries that exceed the configured query time limit. Previously any failed query left the view silently empty (the error became an unhandled watcher rejection). The store now records the failure and the view shows an alert explaining that the instance might be too busy, with concrete suggestions: retry off-peak, reduce the maximum results, or narrow the date range. --- .../src/components/audit/AuditList.spec.ts | 34 ++++++++++ .../src/components/audit/AuditList.vue | 19 +++++- src/Frontend/src/stores/AuditStore.spec.ts | 63 +++++++++++++++++++ src/Frontend/src/stores/AuditStore.ts | 11 +++- 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 src/Frontend/src/stores/AuditStore.spec.ts diff --git a/src/Frontend/src/components/audit/AuditList.spec.ts b/src/Frontend/src/components/audit/AuditList.spec.ts index 622a6ca165..a5309ba572 100644 --- a/src/Frontend/src/components/audit/AuditList.spec.ts +++ b/src/Frontend/src/components/audit/AuditList.spec.ts @@ -314,6 +314,40 @@ describe("FEATURE: Audit Messages Query State", () => { }); }); + describe("RULE: A failed query tells the user what happened and what to try", () => { + test("EXAMPLE: The error banner is shown after a failed query", async () => { + const { store } = await renderAuditList([]); + + await waitForFirstLoadToComplete(); + + store.queryFailed = true; + await nextTick(); + + expect(screen.getByTestId("query-error")).toBeInTheDocument(); + }); + + test("EXAMPLE: The error banner is not shown while a retry is in flight", async () => { + const { store, isRefreshing } = await renderAuditList([]); + + await waitForFirstLoadToComplete(); + + store.queryFailed = true; + isRefreshing.value = true; + await nextTick(); + + expect(screen.queryByTestId("query-error")).not.toBeInTheDocument(); + }); + + test("EXAMPLE: The error banner is not shown when queries succeed", async () => { + const { verify } = await renderAuditList([createMessage()]); + + await waitForFirstLoadToComplete(); + + verify.messagesAreVisible(); + expect(screen.queryByTestId("query-error")).not.toBeInTheDocument(); + }); + }); + describe("RULE: A query-control change results in exactly one query", () => { test("EXAMPLE: Changing the filter text fires a single query", async () => { const { refreshNow, store } = await renderAuditList([createMessage()]); diff --git a/src/Frontend/src/components/audit/AuditList.vue b/src/Frontend/src/components/audit/AuditList.vue index 19dbd77a74..2623914d36 100644 --- a/src/Frontend/src/components/audit/AuditList.vue +++ b/src/Frontend/src/components/audit/AuditList.vue @@ -17,7 +17,7 @@ import PageBanner, { type BannerMessage } from "@/components/PageBanner.vue"; import { useConfigurationStore } from "@/stores/ConfigurationStore"; const store = useAuditStore(); -const { messages, totalCount, sortBy, messageFilterString, selectedEndpointName, itemsPerPage, dateRange } = storeToRefs(store); +const { messages, totalCount, sortBy, messageFilterString, selectedEndpointName, itemsPerPage, dateRange, queryFailed } = storeToRefs(store); const route = useRoute(); const router = useRouter(); const autoRefreshValue = ref(null); @@ -147,6 +147,10 @@ watch(autoRefreshValue, (newValue) => { +