mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
fix(alerts): backport delivery diagnosis response ordering
Backport runtime and regression tests from 495562ef66. Reproduced both failures on exact release-line parent 101bae339; patched 38 focused tests, TypeScript and three-width Chromium pass. Retain local qualification and exact runtime browser receipt. No recipient receipt or release HOLD clearance claimed.
Change-source: pulse-maintainer
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,43 @@
|
||||
# Release-line Overview diagnosis ordering regression
|
||||
|
||||
Exact runtime parent: `101bae33963ee65a5a83bc229d081b43825de889`.
|
||||
Backport source: `495562ef663e473889490a774a80cf8d04405732` (main).
|
||||
Regression provenance: `32e620a6c36b4ce08729de747cb232dfabf5c1ba`
|
||||
introduced the asynchronous delivery diagnosis projection on 26 August 2026;
|
||||
it is an ancestor of this parent and v6.4.1. The unversioned completion path
|
||||
is also present in v6.4.3-rc.1. This repairs an existing candidate surface,
|
||||
not new product scope (RELEASE_PROMOTION_POLICY, Release Train rules 2/4).
|
||||
|
||||
The main runtime patch and its two regression tests apply without adaptation.
|
||||
With only those tests and the browser fixture added to the exact parent,
|
||||
2 tests fail and 11 pass: overlapping reads replace the current disabled
|
||||
warning, and an obsolete response repopulates an empty active set. Chromium
|
||||
also fails the retained-warning assertion (0 versus 1) at its first viewport.
|
||||
These are local release-line reproductions, not inherited main receipts.
|
||||
|
||||
With the six-line runtime patch, all 13 tests pass. Another 25 adjacent
|
||||
presentation and delivery-action tests pass. TypeScript (`tsc --noEmit`) exits
|
||||
0. No full repository suite was run for this bounded UI backport.
|
||||
|
||||
Commands:
|
||||
|
||||
- `npm --prefix frontend-modern test -- src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx src/features/alerts/__tests__/useAlertOverviewState.test.tsx`
|
||||
- `npm --prefix frontend-modern test -- src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx src/features/alerts/__tests__/deliveryDiagnosisPresentation.test.ts`
|
||||
- `npm --prefix frontend-modern run type-check`
|
||||
- `pulse-heavy-run -- node scripts/check-alert-diagnosis-ordering.mjs`
|
||||
|
||||
The fixture uses the real Overview component and synthetic API responses on
|
||||
loopback only. It does not exercise an installed backend, actual delivery,
|
||||
recipient receipt, or production routing. Latest-started ownership prevents
|
||||
superseded successful reads from updating state, including empty-set
|
||||
invalidation. Failed refreshes still retain the previous snapshot; no new
|
||||
freshness claim or indicator is added.
|
||||
|
||||
This does not resolve warning-projection ordering, SQLite crashes, paid
|
||||
convergence, recipient qualification or the release HOLD. No public release
|
||||
or issue-resolution claim follows from this component evidence.
|
||||
|
||||
Patched Chromium passes at 1440, 900 and 390 pixels: warning retained, stale
|
||||
dispatch absent, new alert present, text within viewport and no page errors.
|
||||
Desktop and phone screenshots were visually inspected: warning and controls
|
||||
remain visible. Receipt binds the runtime file hash to the exact parent.
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
> pulse-modern@1.0.0 test
|
||||
> vitest run src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx src/features/alerts/__tests__/deliveryDiagnosisPresentation.test.ts
|
||||
|
||||
|
||||
RUN v3.2.7 /var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/frontend-modern
|
||||
|
||||
✓ src/features/alerts/__tests__/deliveryDiagnosisPresentation.test.ts (16 tests) 137ms
|
||||
✓ src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx (9 tests) 358ms
|
||||
|
||||
Test Files 2 passed (2)
|
||||
Tests 25 passed (25)
|
||||
Start at 17:38:35
|
||||
Duration 2.35s (transform 1.13s, setup 212ms, collect 1.21s, tests 495ms, environment 727ms, prepare 184ms)
|
||||
@@ -0,0 +1 @@
|
||||
{"result":"passed","viewports":[1440,900,390],"scope":"Real Overview and Chromium; scripted diagnoses, not installed delivery or receipt"}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
> pulse-modern@1.0.0 test
|
||||
> vitest run src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx src/features/alerts/__tests__/useAlertOverviewState.test.tsx
|
||||
|
||||
|
||||
RUN v3.2.7 /var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/frontend-modern
|
||||
|
||||
✓ src/features/alerts/__tests__/useAlertOverviewState.test.tsx (8 tests) 41ms
|
||||
✓ src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx (5 tests) 156ms
|
||||
|
||||
Test Files 2 passed (2)
|
||||
Tests 13 passed (13)
|
||||
Start at 17:38:05
|
||||
Duration 1.84s (transform 964ms, setup 109ms, collect 1.18s, tests 197ms, environment 658ms, prepare 123ms)
|
||||
@@ -0,0 +1,18 @@
|
||||
node:internal/modules/run_main:107
|
||||
triggerUncaughtException(
|
||||
^
|
||||
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
0 !== 1
|
||||
|
||||
at file:///var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/scripts/check-alert-diagnosis-ordering.mjs:103:12 {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 0,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
Node.js v24.20.0
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
> pulse-modern@1.0.0 test
|
||||
> vitest run src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx src/features/alerts/__tests__/useAlertOverviewState.test.tsx
|
||||
|
||||
|
||||
RUN v3.2.7 /var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/frontend-modern
|
||||
|
||||
❯ src/features/alerts/__tests__/useAlertOverviewState.test.tsx (8 tests | 1 failed) 47ms
|
||||
× useAlertOverviewState > invalidates pending diagnosis reads when the active set becomes empty 13ms
|
||||
→ expected { a1: { alertIdentifier: 'a1', …(1) } } to deeply equal {}
|
||||
✓ useAlertOverviewState > owns overview stats, filtering, and acknowledge flows outside the tab shell 4ms
|
||||
✓ useAlertOverviewState > keeps a stable id order for alerts sharing a startTime (#1218) 1ms
|
||||
✓ useAlertOverviewState > counts alerts with missing startTime in total24h 1ms
|
||||
✓ useAlertOverviewState > groups only explicit shared-system correlations across resources 1ms
|
||||
✓ useAlertOverviewState > groups explicit infrastructure synthesis without hiding the supporting detector 1ms
|
||||
✓ useAlertOverviewState > does not infer correlation by truncating resource paths 1ms
|
||||
✓ useAlertOverviewState > still groups multiple detector signals for the exact same resource 1ms
|
||||
❯ src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx (5 tests | 1 failed) 167ms
|
||||
✓ OverviewTab delivery status line > renders held-notification status from the bulk diagnosis endpoint 95ms
|
||||
✓ OverviewTab delivery status line > does not turn dispatch evidence into receipt for 'ready' 16ms
|
||||
✓ OverviewTab delivery status line > does not turn dispatch evidence into receipt for 'cooldown' 17ms
|
||||
× OverviewTab delivery status line > ignores an older diagnosis response after the active alert set changes 24ms
|
||||
→ expected <span …(2)></span> to be null
|
||||
✓ OverviewTab delivery status line > renders no delivery line when the diagnosis fetch fails 10ms
|
||||
|
||||
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯
|
||||
|
||||
FAIL src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx > OverviewTab delivery status line > ignores an older diagnosis response after the active alert set changes
|
||||
AssertionError: expected <span …(2)></span> to be null
|
||||
|
||||
- Expected:
|
||||
null
|
||||
|
||||
+ Received:
|
||||
<span
|
||||
class="text-xs text-muted"
|
||||
title="Alert delivery is currently eligible for notification delivery."
|
||||
>
|
||||
Dispatch requested 11:15 AM
|
||||
</span>
|
||||
|
||||
❯ src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx:145:56
|
||||
143| finishOlder([makeDiagnosis('a1', { lastNotified: '2026-08-26T10:15…
|
||||
144| await Promise.resolve();
|
||||
145| expect(screen.queryByText(/^Dispatch requested /)).toBeNull();
|
||||
| ^
|
||||
146| expect(screen.getByText('Notifications are turned off')).toBeTruth…
|
||||
147| });
|
||||
|
||||
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯
|
||||
|
||||
FAIL src/features/alerts/__tests__/useAlertOverviewState.test.tsx > useAlertOverviewState > invalidates pending diagnosis reads when the active set becomes empty
|
||||
AssertionError: expected { a1: { alertIdentifier: 'a1', …(1) } } to deeply equal {}
|
||||
|
||||
- Expected
|
||||
+ Received
|
||||
|
||||
- {}
|
||||
+ {
|
||||
+ "a1": {
|
||||
+ "alertIdentifier": "a1",
|
||||
+ "reason": "ready",
|
||||
+ },
|
||||
+ }
|
||||
|
||||
❯ src/features/alerts/__tests__/useAlertOverviewState.test.tsx:85:40
|
||||
83| finish([{ alertIdentifier: 'a1', reason: 'ready' } as AlertDeliver…
|
||||
84| await Promise.resolve();
|
||||
85| expect(result.deliveryDiagnoses()).toEqual({});
|
||||
| ^
|
||||
86| });
|
||||
87|
|
||||
|
||||
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯
|
||||
|
||||
|
||||
Test Files 2 failed (2)
|
||||
Tests 2 failed | 11 passed (13)
|
||||
Start at 17:37:40
|
||||
Duration 1.88s (transform 1.03s, setup 143ms, collect 1.24s, tests 214ms, environment 607ms, prepare 329ms)
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
> pulse-modern@1.0.0 type-check
|
||||
> tsc --noEmit
|
||||
@@ -15,6 +15,15 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
Overview delivery diagnoses use latest-started refresh ownership. Older bulk
|
||||
responses cannot overwrite newer card notification status, and an empty active
|
||||
alert set invalidates outstanding reads. Disposal also prevents updates. Failed
|
||||
refreshes retain the existing snapshot; this ordering repair does not add a
|
||||
freshness indicator or establish recipient receipt. Verify response overlap in
|
||||
`OverviewTab.deliverystatus.test.tsx`, empty-set invalidation in
|
||||
`useAlertOverviewState.test.tsx`, and rendered ordering at three widths using
|
||||
`scripts/check-alert-diagnosis-ordering.mjs`.
|
||||
|
||||
Confirmed canonical metric recovery publishes the clearing evaluation's value,
|
||||
observation time, and resolved metric wording in the snapshot consumed by
|
||||
recent-resolution reads and notification callbacks. Clone that snapshot before
|
||||
|
||||
@@ -20,6 +20,15 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
Overview delivery diagnoses use latest-started refresh ownership. Older bulk
|
||||
responses cannot overwrite newer card notification status, and an empty active
|
||||
alert set invalidates outstanding reads. Disposal also prevents updates. Failed
|
||||
refreshes retain the existing snapshot; this ordering repair does not add a
|
||||
freshness indicator or establish recipient receipt. Verify response overlap in
|
||||
`OverviewTab.deliverystatus.test.tsx`, empty-set invalidation in
|
||||
`useAlertOverviewState.test.tsx`, and rendered ordering at three widths using
|
||||
`scripts/check-alert-diagnosis-ordering.mjs`.
|
||||
|
||||
Proxmox backup presentation treats every manifestless PBS artifact as
|
||||
non-recoverable. It renders the artifact as `Running` when current writer
|
||||
visibility is absent or a matching writer is active, and as danger-tone
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "2d550da942c6f0a7e7896e7aca86493983f90b84",
|
||||
"verified_at": "2026-09-06T15:54:59.817913Z",
|
||||
"base_sha": "101bae33963ee65a5a83bc229d081b43825de889",
|
||||
"verified_at": "2026-09-06T16:42:39.585210Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/features/alerts/deliveryDiagnosisPresentation.ts"
|
||||
"frontend-modern/src/features/alerts/useAlertOverviewState.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/features/alerts/deliveryDiagnosisPresentation.ts": "bc3eadf8f790517b377430a42eb67e8fdfcadb53bb13b87cd971d6a9ab91d607"
|
||||
"frontend-modern/src/features/alerts/useAlertOverviewState.ts": "64d0b891e7ad228e8590da859dc25e825b6164c8cf76a01983a219d6cd079b23"
|
||||
},
|
||||
"routes": [
|
||||
"/qualification (loopback synthetic Overview, not production routing)"
|
||||
@@ -27,11 +27,11 @@
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Ready and cooldown show Dispatch requested; cooldown says next eligible; missing timestamp shows Notification pending. Scripted diagnoses, not recipient receipt.",
|
||||
"390px screenshot visually inspected: longer status wraps within card and controls remain visible."
|
||||
"Newer notifications-disabled response remains visible after older dispatch response completes. Synthetic diagnoses, not recipient receipt.",
|
||||
"Desktop and phone screenshots visually inspected: current warning and card controls remain visible within viewport."
|
||||
],
|
||||
"interactions": [
|
||||
"pulse-heavy-run -- node scripts/check-alert-dispatch-copy.mjs passed at all three widths; two dispatch labels, no Notified labels, one pending label, text fits viewport, no page errors.",
|
||||
"Evidence: /var/lib/pulse-maintainer/queue/staging/20260906T155036Z-release-line/browser-dispatch.log and browser-dispatch/*.png."
|
||||
"pulse-heavy-run -- node scripts/check-alert-diagnosis-ordering.mjs passed at 1440, 900 and 390 pixels: retained warning, absent stale dispatch, new alert present, text within viewport, no page errors.",
|
||||
"Exact-parent failing and patched passing evidence: docs/qualification/release-v6.4-diagnosis-ordering/."
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createSignal } from 'solid-js';
|
||||
import { cleanup, render, screen, waitFor } from '@solidjs/testing-library';
|
||||
import { DEFAULT_LOCALE, setActiveLocale } from '@/i18n';
|
||||
import type { Alert, AlertDeliveryDiagnosis } from '@/types/api';
|
||||
@@ -124,6 +125,27 @@ describe('OverviewTab delivery status line', () => {
|
||||
if (state.reason === 'cooldown') expect(screen.getByText(/next eligible/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('ignores an older diagnosis response after the active alert set changes', async () => {
|
||||
let finishOlder!: (value: AlertDeliveryDiagnosis[]) => void;
|
||||
getDeliveryDiagnoses.mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
finishOlder = resolve;
|
||||
}),
|
||||
);
|
||||
getDeliveryDiagnoses.mockResolvedValueOnce([
|
||||
makeDiagnosis('a1', { status: 'suppressed', reason: 'notifications_disabled' }),
|
||||
]);
|
||||
const [alerts, setAlerts] = createSignal<Record<string, Alert>>({ a1: makeAlert('a1') });
|
||||
render(() => <OverviewTab {...defaultProps()} activeAlerts={alerts()} />);
|
||||
await waitFor(() => expect(getDeliveryDiagnoses).toHaveBeenCalledTimes(1));
|
||||
setAlerts({ a1: makeAlert('a1'), a2: makeAlert('a2') });
|
||||
await waitFor(() => expect(screen.getByText('Notifications are turned off')).toBeTruthy());
|
||||
finishOlder([makeDiagnosis('a1', { lastNotified: '2026-08-26T10:15:00Z' })]);
|
||||
await Promise.resolve();
|
||||
expect(screen.queryByText(/^Dispatch requested /)).toBeNull();
|
||||
expect(screen.getByText('Notifications are turned off')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders no delivery line when the diagnosis fetch fails', async () => {
|
||||
const activeAlerts: Record<string, Alert> = { a1: makeAlert('a1') };
|
||||
getDeliveryDiagnoses.mockRejectedValue(new Error('boom'));
|
||||
|
||||
@@ -4,12 +4,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AlertsAPI } from '@/api/alerts';
|
||||
import { notificationStore } from '@/stores/notifications';
|
||||
import type { Alert } from '@/types/api';
|
||||
import type { Alert, AlertDeliveryDiagnosis } from '@/types/api';
|
||||
|
||||
import { useAlertOverviewState } from '../useAlertOverviewState';
|
||||
|
||||
vi.mock('@/api/alerts', () => ({
|
||||
AlertsAPI: {
|
||||
getDeliveryDiagnoses: vi.fn(),
|
||||
acknowledge: vi.fn(),
|
||||
bulkAcknowledge: vi.fn(),
|
||||
unacknowledge: vi.fn(),
|
||||
@@ -47,6 +48,7 @@ describe('useAlertOverviewState', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-03-22T12:00:00Z'));
|
||||
vi.mocked(AlertsAPI.getDeliveryDiagnoses).mockReset().mockResolvedValue([]);
|
||||
vi.mocked(AlertsAPI.acknowledge).mockReset();
|
||||
vi.mocked(AlertsAPI.unacknowledge).mockReset();
|
||||
vi.mocked(AlertsAPI.bulkAcknowledge).mockReset();
|
||||
@@ -58,6 +60,31 @@ describe('useAlertOverviewState', () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('invalidates pending diagnosis reads when the active set becomes empty', async () => {
|
||||
let finish!: (value: AlertDeliveryDiagnosis[]) => void;
|
||||
vi.mocked(AlertsAPI.getDeliveryDiagnoses).mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
finish = resolve;
|
||||
}),
|
||||
);
|
||||
const [activeAlerts, setActiveAlerts] = createSignal<Record<string, Alert>>({
|
||||
a1: makeAlert('a1', new Date().toISOString()),
|
||||
});
|
||||
const { result } = renderHook(() =>
|
||||
useAlertOverviewState({
|
||||
activeAlerts,
|
||||
overrides: () => [],
|
||||
showAcknowledged: () => true,
|
||||
updateAlert: vi.fn(),
|
||||
}),
|
||||
);
|
||||
expect(AlertsAPI.getDeliveryDiagnoses).toHaveBeenCalledOnce();
|
||||
setActiveAlerts({});
|
||||
finish([{ alertIdentifier: 'a1', reason: 'ready' } as AlertDeliveryDiagnosis]);
|
||||
await Promise.resolve();
|
||||
expect(result.deliveryDiagnoses()).toEqual({});
|
||||
});
|
||||
|
||||
it('owns overview stats, filtering, and acknowledge flows outside the tab shell', async () => {
|
||||
const now = Date.now();
|
||||
const [activeAlerts] = createSignal<Record<string, Alert>>({
|
||||
|
||||
@@ -68,17 +68,21 @@ export function useAlertOverviewState(props: UseAlertOverviewStateProps) {
|
||||
Record<string, AlertDeliveryDiagnosis>
|
||||
>({});
|
||||
let diagnosisStateDisposed = false;
|
||||
let diagnosisRequestVersion = 0;
|
||||
onCleanup(() => {
|
||||
diagnosisStateDisposed = true;
|
||||
});
|
||||
const refreshDeliveryDiagnoses = async () => {
|
||||
// A slower previous refresh must not replace a newer notification state.
|
||||
// Increment even for an empty alert set to invalidate outstanding requests.
|
||||
const requestVersion = ++diagnosisRequestVersion;
|
||||
if (activeAlerts().length === 0) {
|
||||
setDeliveryDiagnoses({});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const list = await AlertsAPI.getDeliveryDiagnoses();
|
||||
if (diagnosisStateDisposed) return;
|
||||
if (diagnosisStateDisposed || requestVersion !== diagnosisRequestVersion) return;
|
||||
const next: Record<string, AlertDeliveryDiagnosis> = {};
|
||||
for (const diagnosis of list) {
|
||||
next[diagnosis.alertIdentifier || diagnosis.alertId] = diagnosis;
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// Isolated real-browser component qualification; no installed backend or delivery claim.
|
||||
import { createServer } from "../frontend-modern/node_modules/vite/dist/node/index.js";
|
||||
import solid from "../frontend-modern/node_modules/vite-plugin-solid/dist/esm/index.mjs";
|
||||
import { chromium } from "@playwright/test";
|
||||
import { resolve } from "node:path";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import assert from "node:assert/strict";
|
||||
const root = resolve("frontend-modern");
|
||||
process.chdir(root);
|
||||
const fixture = `
|
||||
import { createSignal } from 'solid-js';
|
||||
import { render } from 'solid-js/web';
|
||||
import { Router, Route } from '@solidjs/router';
|
||||
import { AlertsAPI } from '/src/api/alerts';
|
||||
import { NotificationsAPI } from '/src/api/notifications';
|
||||
import { OverviewTab } from '/src/features/alerts/OverviewTab';
|
||||
import '/src/index.css';
|
||||
let finishOlder;
|
||||
let requests = 0;
|
||||
AlertsAPI.getDeliveryDiagnoses = () => {
|
||||
requests++;
|
||||
if (requests === 1) return new Promise(resolve => { finishOlder = resolve; });
|
||||
return Promise.resolve([{alertIdentifier:'a1', alertId:'a1', status:'suppressed',
|
||||
reason:'notifications_disabled', message:'Notifications disabled by current configuration'}]);
|
||||
};
|
||||
window.finishOlder = () => finishOlder([{alertIdentifier:'a1', alertId:'a1',
|
||||
status:'would_send',reason:'ready',lastNotified:'2026-08-26T10:15:00Z'}]);
|
||||
window.requestCount = () => requests;
|
||||
AlertsAPI.getEvents = async () => [];
|
||||
NotificationsAPI.getHealth = async () => ({queue:{status:'healthy'}});
|
||||
const alert = id => ({id,resourceId:id,resourceName:'VM '+id,type:'cpu',level:'warning',
|
||||
message:'High CPU on '+id,startTime:new Date().toISOString(),acknowledged:false,node:'node1'});
|
||||
function Fixture() {
|
||||
const [alerts, setAlerts] = createSignal({a1:alert('a1')});
|
||||
return <main class="p-4"><button onClick={()=>setAlerts({a1:alert('a1'),a2:alert('a2')})}>Add alert</button>
|
||||
<OverviewTab overrides={[]} activeAlerts={alerts()}
|
||||
updateAlert={()=>{}} showQuickTip={()=>false} dismissQuickTip={()=>{}} showAcknowledged={()=>true}
|
||||
setShowAcknowledged={()=>{}} alertsDisabled={()=>false}/></main>; }
|
||||
render(()=><Router><Route path="/qualification" component={Fixture}/></Router>,document.getElementById('root'));
|
||||
`;
|
||||
const server = await createServer({
|
||||
root,
|
||||
configFile: false,
|
||||
optimizeDeps: {
|
||||
noDiscovery: true,
|
||||
entries: [],
|
||||
esbuildOptions: { target: "esnext" },
|
||||
},
|
||||
esbuild: { target: "esnext" },
|
||||
plugins: [
|
||||
solid(),
|
||||
{
|
||||
name: "dispatch-fixture",
|
||||
configureServer(s) {
|
||||
s.middlewares.use((req, res, next) => {
|
||||
if (req.url === "/qualification") {
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end(
|
||||
'<div id="root"></div><script type="module" src="/dispatch-fixture.tsx"></script>',
|
||||
);
|
||||
} else next();
|
||||
});
|
||||
},
|
||||
resolveId(id) {
|
||||
if (id === "/dispatch-fixture.tsx") return id;
|
||||
},
|
||||
load(id) {
|
||||
if (id === "/dispatch-fixture.tsx") return fixture;
|
||||
},
|
||||
},
|
||||
],
|
||||
resolve: { alias: { "@": resolve(root, "src") } },
|
||||
server: { host: "127.0.0.1", port: 5199, strictPort: true },
|
||||
});
|
||||
let browser;
|
||||
try {
|
||||
await server.listen();
|
||||
browser = await chromium.launch({ headless: true });
|
||||
mkdirSync("/tmp/pulse-alert-diagnosis-ordering", { recursive: true });
|
||||
for (const width of [1440, 900, 390]) {
|
||||
const page = await browser.newPage({ viewport: { width, height: 1000 } });
|
||||
const errors = [];
|
||||
page.on("pageerror", (e) => {
|
||||
errors.push(e.message);
|
||||
console.error(e.message);
|
||||
});
|
||||
page.on("console", (m) => {
|
||||
if (m.type() === "error") console.error(m.text());
|
||||
});
|
||||
await page.route("http://127.0.0.1:5199/api/**", (route) =>
|
||||
route.fulfill({ json: [] }),
|
||||
);
|
||||
await page.goto("http://127.0.0.1:5199/qualification");
|
||||
await page.waitForFunction(() => window.requestCount?.() === 1);
|
||||
await page.getByRole("button", { name: "Add alert", exact: true }).click();
|
||||
await page
|
||||
.getByText("Notifications are turned off", { exact: true })
|
||||
.waitFor();
|
||||
await page.evaluate(async () => {
|
||||
window.finishOlder();
|
||||
await Promise.resolve();
|
||||
});
|
||||
assert.equal(
|
||||
await page
|
||||
.getByText("Notifications are turned off", { exact: true })
|
||||
.count(),
|
||||
1,
|
||||
);
|
||||
assert.equal(await page.getByText(/^Dispatch requested /).count(), 0);
|
||||
assert.equal(
|
||||
await page.getByText("High CPU on a2", { exact: true }).count(),
|
||||
1,
|
||||
);
|
||||
const label = page.getByText("Notifications are turned off", {
|
||||
exact: true,
|
||||
});
|
||||
assert.equal(
|
||||
await label.evaluate((el) => {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
return [...range.getClientRects()].every(
|
||||
(b) => b.left >= 0 && b.right <= innerWidth,
|
||||
);
|
||||
}),
|
||||
true,
|
||||
"current status must fit viewport",
|
||||
);
|
||||
assert.deepEqual(errors, []);
|
||||
await page.screenshot({
|
||||
path: "/tmp/pulse-alert-diagnosis-ordering/" + width + ".png",
|
||||
fullPage: true,
|
||||
});
|
||||
await page.close();
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
result: "passed",
|
||||
viewports: [1440, 900, 390],
|
||||
scope:
|
||||
"Real Overview and Chromium; scripted diagnoses, not installed delivery or receipt",
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await browser?.close();
|
||||
await server.close();
|
||||
}
|
||||
Reference in New Issue
Block a user