diff --git a/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx b/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx index d0df540f..9270cc06 100644 --- a/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx +++ b/vehicle-data-platform/apps/web/src/pages/NotificationRules.tsx @@ -143,6 +143,51 @@ function alertEscalationChecklist(plan: QualityNotificationPlan | null, release? ].join('\n'); } +function alertNotificationDrillPackage(plan: QualityNotificationPlan | null, health: OpsHealth | null, release?: string) { + const rules = plan?.rules ?? []; + const policies = plan?.policies ?? []; + const issues = plan?.priorityIssues ?? []; + const p0Rules = rules.filter((rule) => rule.level === 'P0'); + const p1Rules = rules.filter((rule) => rule.level === 'P1'); + const p0Issues = issues.filter((issue) => issue.priority === 'P0'); + const firstP0Policy = policies.find((policy) => policy.name.startsWith('P0')) ?? policies[0]; + const firstP1Policy = policies.find((policy) => policy.name.startsWith('P1')) ?? policies[1] ?? policies[0]; + const unhealthyLinks = (health?.linkHealth ?? []).filter((item) => item.status !== 'ok'); + const nextIssue = issues[0]; + return [ + '【告警通知演练包】', + `运行版本:${release?.trim() || '-'}`, + `链路状态:${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常'}`, + `规则规模:P0 ${p0Rules.length.toLocaleString()} 类,P1 ${p1Rules.length.toLocaleString()} 类,待通知 ${issues.length.toLocaleString()} 条`, + `当前优先级:${p0Issues.length > 0 ? `P0 ${p0Issues.length.toLocaleString()} 条` : issues.length > 0 ? 'P1/普通告警待跟进' : '暂无待通知告警'}`, + '', + '通知策略核对:', + `P0策略:${firstP0Policy ? `${firstP0Policy.name} / ${firstP0Policy.target} / ${firstP0Policy.channel} / ${formatEscalationMinutes(firstP0Policy.escalationMinutes)}` : '-'}`, + `P0验收:${firstP0Policy?.acceptanceCriteria || '-'}`, + `P1策略:${firstP1Policy ? `${firstP1Policy.name} / ${firstP1Policy.target} / ${firstP1Policy.channel} / ${formatEscalationMinutes(firstP1Policy.escalationMinutes)}` : '-'}`, + `P1验收:${firstP1Policy?.acceptanceCriteria || '-'}`, + '', + '演练步骤:', + '1. 在告警事件页确认 P0/P1 命中数量与本页一致', + '2. 对第一条待通知告警打开车辆服务、实时、轨迹、RAW 四类证据', + '3. 复制单条告警通知给责任团队,记录发送时间', + '4. 超过升级窗口仍未恢复时,按策略升级到业务责任人', + '5. 恢复后按验收口径复核,并确认告警不再命中', + '', + '演练样例:', + nextIssue ? [ + `[${nextIssue.priority}] ${nextIssue.vehicleLabel}`, + `问题:${qualityIssueLabel(nextIssue.issueType)} / ${nextIssue.protocol}`, + `SLA:${nextIssue.sla}`, + `通知文本:${nextIssue.notificationText}`, + `车辆服务:${window.location.origin}${window.location.pathname}${nextIssue.vehicleHash}`, + `实时证据:${window.location.origin}${window.location.pathname}${nextIssue.realtimeHash}`, + `轨迹证据:${window.location.origin}${window.location.pathname}${nextIssue.historyHash}`, + `RAW证据:${window.location.origin}${window.location.pathname}${nextIssue.rawHash}` + ].join('\n') : '暂无待通知样例' + ].join('\n'); +} + function countEvidenceLinks(issue: QualityPriorityIssue) { return [issue.vehicleHash, issue.realtimeHash, issue.historyHash, issue.rawHash].filter(Boolean).length; } @@ -271,7 +316,7 @@ export function NotificationRules() { 告警处置队列} + title={告警处置队列} style={{ marginTop: 16 }} >
diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx index e8977564..432186dd 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -3481,6 +3481,14 @@ test('renders notification rules as a standalone operations page', async () => { expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务:http://localhost:3000/#/detail?keyword=VIN-RULES-001')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('RAW证据:http://localhost:3000/#/history-query?keyword=VIN-RULES-001&tab=raw')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('超过升级窗口未恢复时')); + fireEvent.click(screen.getByRole('button', { name: '复制通知演练包' })); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【告警通知演练包】')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('运行版本:platform-rules-test')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('规则规模:P0 1 类,P1 1 类,待通知 1 条')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('P0策略:P0 实时中断 / 接入运维 + 业务责任人 / 邮件 / 企业微信 / 30 分钟升级')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('2. 对第一条待通知告警打开车辆服务、实时、轨迹、RAW 四类证据')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('通知文本:【P0 告警通知】通知规则页待通知')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证据:http://localhost:3000/#/history?keyword=VIN-RULES-001')); fireEvent.click(screen.getByRole('button', { name: '复制单条告警通知' })); expect(writeText).toHaveBeenCalledWith('【P0 告警通知】通知规则页待通知'); fireEvent.click(screen.getByRole('button', { name: '车辆服务' }));