feat(platform): promote alert events surface

This commit is contained in:
lingniu
2026-07-04 17:58:33 +08:00
parent eb8f15ca18
commit 1235e76c32
11 changed files with 135 additions and 70 deletions

View File

@@ -95,7 +95,7 @@ test('exposes AMap operations shortcuts when map key is configured', async () =>
fireEvent.click(screen.getByRole('button', { name: '顶部统计查询' }));
expect(window.location.hash.startsWith('#/mileage')).toBe(true);
fireEvent.click(await screen.findByRole('button', { name: '顶部告警事件正常' }));
expect(window.location.hash).toBe('#/quality');
expect(window.location.hash).toBe('#/alert-events');
});
test('shows global alert pressure from notification plan in topbar', async () => {
@@ -151,7 +151,7 @@ test('shows global alert pressure from notification plan in topbar', async () =>
const alertButton = await screen.findByRole('button', { name: '顶部告警事件 P0 2 / 活跃 4' });
expect(alertButton).toHaveTextContent('告警事件 P0 2 / 活跃 4');
fireEvent.click(alertButton);
expect(window.location.hash).toBe('#/quality');
expect(window.location.hash).toBe('#/alert-events');
});
test('shows vehicle service status distribution on dashboard', async () => {
@@ -488,7 +488,7 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
await renderDashboard();
fireEvent.click(screen.getByRole('button', { name: '能力入口 告警事件触发与通知' }));
expect(window.location.hash).toBe('#/quality');
expect(window.location.hash).toBe('#/alert-events');
cleanup();
await renderDashboard();
@@ -649,7 +649,7 @@ test('dashboard exposes vehicle command center map actions', async () => {
expect(screen.getAllByText('有效定位 1').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('降级/离线 2')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '处理高优先级告警' }));
expect(window.location.hash).toBe('#/quality?issueType=NO_SOURCE');
expect(window.location.hash).toBe('#/alert-events?issueType=NO_SOURCE');
cleanup();
window.history.replaceState(null, '', '/#/dashboard');
@@ -744,7 +744,7 @@ test('dashboard shows vehicle service action queue', async () => {
test.each([
{ buttonName: '查看在线车辆', expectedHash: '#/vehicles?online=online' },
{ buttonName: '查看多源车辆', expectedHash: '#/vehicles?coverage=multi' },
{ buttonName: '查看告警事件', expectedHash: '#/quality' }
{ buttonName: '查看告警事件', expectedHash: '#/alert-events' }
])('dashboard posture opens %s', async ({ buttonName, expectedHash }) => {
window.history.replaceState(null, '', '/#/dashboard');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
@@ -2874,7 +2874,7 @@ test('opens dashboard coverage from service status distribution', async () => {
});
test('renders quality issues as vehicle-service governance labels', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -2949,7 +2949,7 @@ test('renders quality issues as vehicle-service governance labels', async () =>
});
test('shows alert rule and notification policy workspace on quality page', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const writeText = vi.fn(() => Promise.resolve());
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
@@ -3045,7 +3045,7 @@ test('shows alert rule and notification policy workspace on quality page', async
});
test('shows actionable priority queue on quality page', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -3162,7 +3162,7 @@ test('shows actionable priority queue on quality page', async () => {
});
test('uses backend quality notification plan on quality page', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -3390,7 +3390,7 @@ test('renders ops quality as a standalone runtime health page', async () => {
});
test('copies notification text from quality priority queue', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const writeText = vi.fn(() => Promise.resolve());
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
@@ -3475,11 +3475,11 @@ test('copies notification text from quality priority queue', async () => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证据http://localhost:3000/#/history?keyword=%E7%B2%A4A%E9%80%9A%E7%9F%A51&protocol=JT808&dateFrom=2026-07-03&dateTo=2026-07-04'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('RAW证据http://localhost:3000/#/history-query?keyword=%E7%B2%A4A%E9%80%9A%E7%9F%A51&protocol=JT808&tab=raw&dateFrom=2026-07-03&dateTo=2026-07-04&includeFields=true'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务http://localhost:3000/#/detail?keyword=%E7%B2%A4A%E9%80%9A%E7%9F%A51&protocol=JT808'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警筛选http://localhost:3000/#/quality'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警筛选http://localhost:3000/#/alert-events'));
});
test('copies dispatch ticket from quality priority queue', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const writeText = vi.fn(() => Promise.resolve());
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
@@ -3570,7 +3570,7 @@ test('copies dispatch ticket from quality priority queue', async () => {
});
test('copies priority queue notification digest on quality page', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const writeText = vi.fn(() => Promise.resolve());
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
@@ -3668,11 +3668,11 @@ test('copies priority queue notification digest on quality page', async () => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('确认平台转发'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('2. [P1] 粤A汇总2 / 13307795426'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('维护身份绑定'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警筛选http://localhost:3000/#/quality'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警筛选http://localhost:3000/#/alert-events'));
});
test('copies notification text from regular quality issue row', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const writeText = vi.fn(() => Promise.resolve());
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
@@ -3759,7 +3759,7 @@ test('copies notification text from regular quality issue row', async () => {
});
test('opens realtime map context from quality priority queue', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -3861,7 +3861,7 @@ test('opens realtime map context from quality priority queue', async () => {
});
test('opens same-day trajectory evidence from quality priority queue', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -3954,7 +3954,7 @@ test('opens same-day trajectory evidence from quality priority queue', async ()
});
test('opens same-day raw evidence from quality priority queue', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4048,7 +4048,7 @@ test('opens same-day raw evidence from quality priority queue', async () => {
});
test('opens vehicle service from quality issue with issue source evidence', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4148,7 +4148,7 @@ test('opens vehicle service from quality issue with issue source evidence', asyn
});
test('opens same-day history evidence from quality issue row', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4224,7 +4224,7 @@ test('opens same-day history evidence from quality issue row', async () => {
});
test('quality page surfaces escalation clock for priority issues', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4311,7 +4311,7 @@ test('quality page surfaces escalation clock for priority issues', async () => {
});
test('opens same-day raw evidence from quality issue row', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4498,7 +4498,7 @@ test('copies trajectory playback summary from history page', async () => {
});
test('opens same-day mileage statistics from quality issue row', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4629,7 +4629,7 @@ test('opens same-day mileage statistics from quality issue row', async () => {
});
test('drills into quality issues by issue type', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4695,7 +4695,7 @@ test('drills into quality issues by issue type', async () => {
});
test('drills into quality issues by protocol bucket', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4761,11 +4761,11 @@ test('drills into quality issues by protocol bucket', async () => {
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/issues?protocol=JT808&limit=20&offset=0'), undefined);
});
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/summary?protocol=JT808'), undefined);
expect(window.location.hash).toBe('#/quality?protocol=JT808');
expect(window.location.hash).toBe('#/alert-events?protocol=JT808');
});
test('drills into quality issues by issue type bucket', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4831,11 +4831,11 @@ test('drills into quality issues by issue type bucket', async () => {
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/issues?issueType=VIN_MISSING&limit=20&offset=0'), undefined);
});
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/summary?issueType=VIN_MISSING'), undefined);
expect(window.location.hash).toBe('#/quality?issueType=VIN_MISSING');
expect(window.location.hash).toBe('#/alert-events?issueType=VIN_MISSING');
});
test('applies shareable quality filters from hash', async () => {
window.history.replaceState(null, '', '/#/quality?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE');
window.history.replaceState(null, '', '/#/alert-events?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4894,7 +4894,7 @@ test('applies shareable quality filters from hash', async () => {
});
test('shows and clears current quality filters', async () => {
window.history.replaceState(null, '', '/#/quality?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE');
window.history.replaceState(null, '', '/#/alert-events?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE');
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -4955,11 +4955,11 @@ test('shows and clears current quality filters', async () => {
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/issues?limit=20&offset=0'), undefined);
});
expect(window.location.hash).toBe('#/quality');
expect(window.location.hash).toBe('#/alert-events');
});
test('copies shareable quality filter link', async () => {
window.history.replaceState(null, '', '/#/quality?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE');
window.history.replaceState(null, '', '/#/alert-events?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE');
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, 'clipboard', {
configurable: true,
@@ -5019,7 +5019,7 @@ test('copies shareable quality filter link', async () => {
fireEvent.click(await screen.findByRole('button', { name: /复制筛选链接/ }));
await waitFor(() => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('/#/quality?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('/#/alert-events?keyword=%E7%B2%A4A&protocol=VEHICLE_SERVICE&issueType=NO_SOURCE'));
});
});
@@ -8585,7 +8585,7 @@ test('surfaces multi-source realtime consistency issues with quality drilldown',
fireEvent.click(screen.getByRole('button', { name: '检查质量' }));
await waitFor(() => {
expect(window.location.hash).toBe('#/quality?keyword=VIN-RT-CONSISTENCY&protocol=GB32960');
expect(window.location.hash).toBe('#/alert-events?keyword=VIN-RT-CONSISTENCY&protocol=GB32960');
});
});
@@ -8715,7 +8715,7 @@ test('opens quality issues from realtime vehicle row with source evidence', asyn
fireEvent.click(screen.getAllByRole('button', { name: '质量问题' })[0]);
await waitFor(() => {
expect(window.location.hash).toBe('#/quality?keyword=VIN-RT-QUALITY&protocol=JT808');
expect(window.location.hash).toBe('#/alert-events?keyword=VIN-RT-QUALITY&protocol=JT808');
});
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
});
@@ -8773,7 +8773,7 @@ test('loads realtime vehicles from shareable source filter hash', async () => {
});
test('opens realtime status from quality issue row with source evidence', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -9417,7 +9417,7 @@ test('opens quality issues from source-filtered vehicle list with source evidenc
fireEvent.click(screen.getByRole('button', { name: '质量问题' }));
await waitFor(() => {
expect(window.location.hash).toBe('#/quality?keyword=VIN-LIST-QUALITY&protocol=JT808');
expect(window.location.hash).toBe('#/alert-events?keyword=VIN-LIST-QUALITY&protocol=JT808');
});
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
});
@@ -9705,7 +9705,7 @@ test('opens quality governance from vehicle detail overview', async () => {
fireEvent.click(screen.getByRole('button', { name: '查看质量问题 3 项' }));
await waitFor(() => {
expect(window.location.hash).toBe('#/quality?keyword=VIN001');
expect(window.location.hash).toBe('#/alert-events?keyword=VIN001');
});
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
expect(screen.getByText('告警事件闭环')).toBeInTheDocument();
@@ -9714,7 +9714,7 @@ test('opens quality governance from vehicle detail overview', async () => {
});
test('quality health storage card stays pending before ops health loads', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -9741,7 +9741,7 @@ test('quality health storage card stays pending before ops health loads', async
});
test('quality health shows active connection capacity metric', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
return {
@@ -9768,7 +9768,7 @@ test('quality health shows active connection capacity metric', async () => {
});
test('quality health shows structured capacity findings', async () => {
window.history.replaceState(null, '', '/#/quality');
window.history.replaceState(null, '', '/#/alert-events');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
return {