feat(platform): frame notifications as customer closure

This commit is contained in:
lingniu
2026-07-05 20:45:22 +08:00
parent 1e4a11d806
commit 2b246f414e
4 changed files with 15 additions and 14 deletions

View File

@@ -63,7 +63,7 @@ const navGroups = [
description: '断链、离线、定位异常和通知升级',
items: [
{ itemKey: 'alert-events', text: '告警事件', icon: <IconHistogram /> },
{ itemKey: 'notification-rules', text: '通知规则', icon: <IconBell /> }
{ itemKey: 'notification-rules', text: '通知闭环', icon: <IconBell /> }
]
},
{

View File

@@ -773,7 +773,7 @@ export function NotificationRules() {
return (
<div className="vp-page">
<PageHeader title="通知规则" description="集中管理告警触发条件、通知对象、升级时间和验收标准,让断链与数据质量问题可追踪、可通知、可闭环" />
<PageHeader title="通知闭环" description="面向客户交付告警影响、通知对象、升级时限和恢复验收;规则配置只作为闭环背后的证据。" />
<Card bordered loading={loading} className="vp-notification-customer-board" bodyStyle={{ padding: 0 }}>
<div className="vp-notification-customer-summary">
<Space wrap>

View File

@@ -600,7 +600,7 @@ function notificationHandoffText({
...issueLines,
'',
`告警事件:${qualityShareURL()}`,
`通知规则${appURL(buildAppHash({ page: 'notification-rules' }))}`,
`通知闭环${appURL(buildAppHash({ page: 'notification-rules' }))}`,
`运维质量:${appURL(buildAppHash({ page: 'ops-quality' }))}`
].join('\n');
}
@@ -654,7 +654,7 @@ function alertBusinessImpactText({
`容量风险:${storageOk ? '存储可写' : '存储异常'}Kafka Lag ${formatLag(health?.kafkaLag)};容量发现 ${capacityCount.toLocaleString()}`,
`建议动作:${severity === '高风险' ? '立即通知责任团队并按 P0/P1 队列闭环' : severity === '需关注' ? '纳入当日治理,持续观察来源覆盖和字段完整性' : '保持监控'}`,
`告警事件:${qualityShareURL()}`,
`通知规则${appURL(buildAppHash({ page: 'notification-rules' }))}`,
`通知闭环${appURL(buildAppHash({ page: 'notification-rules' }))}`,
`运维质量:${appURL(buildAppHash({ page: 'ops-quality' }))}`
].join('\n');
}
@@ -715,7 +715,7 @@ function alertCustomerDecisionText({
`告警事件:${qualityShareURL()}`,
`实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: focusLookup?.key || filters.keyword || '', protocol: focusIssue?.protocol || filters.protocol }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: focusLookup?.key || filters.keyword || '', protocol: focusIssue?.protocol || filters.protocol }))}`,
`通知规则${appURL(buildAppHash({ page: 'notification-rules' }))}`
`通知闭环${appURL(buildAppHash({ page: 'notification-rules' }))}`
].join('\n');
}
@@ -869,7 +869,7 @@ export function Quality({
value: primaryPolicy?.target || '待配置',
detail: primaryPolicy ? `${primaryPolicy.name} / ${primaryPolicy.channel}` : '缺少通知策略,建议先配置责任团队。',
color: primaryPolicy ? 'blue' as const : 'orange' as const,
action: '通知规则',
action: '通知闭环',
onClick: () => onOpenNotificationRules?.()
},
{
@@ -1226,7 +1226,7 @@ export function Quality({
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={copyAlertCustomerDecision}></Button>
<Button size="small" disabled={priorityRows.length === 0} onClick={copyPriorityDigest}></Button>
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
</Space>
</div>
<div className="vp-alert-decision-grid">
@@ -1310,7 +1310,7 @@ export function Quality({
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={copyNotificationHandoff}></Button>
<Button size="small" onClick={copyPolicyRunbook}> SLA </Button>
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
</Space>
</div>
<div className="vp-alert-sla-grid">
@@ -1350,7 +1350,7 @@ export function Quality({
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={copyNotificationHandoff}></Button>
<Button size="small" onClick={copyPolicyRunbook}></Button>
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
</Space>
</div>
<div className="vp-alert-channel-grid">
@@ -1426,7 +1426,7 @@ export function Quality({
<Space wrap>
<Button size="small" theme="solid" type="primary" disabled={priorityRows.length === 0} onClick={copyNotificationHandoff}></Button>
<Button size="small" onClick={copyBusinessImpact}></Button>
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}
</Space>
</div>
<div className="vp-alert-center-grid">
@@ -1757,7 +1757,7 @@ export function Quality({
/>
</Card>
<div className="vp-alert-ops-grid">
<Card bordered title={<Space><span></span>{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}</Space>}>
<Card bordered title={<Space><span></span>{onOpenNotificationRules ? <Button size="small" onClick={onOpenNotificationRules}></Button> : null}</Space>}>
<Table<AlertRuleRow>
pagination={false}
dataSource={rules}

View File

@@ -4279,7 +4279,8 @@ test('renders notification rules as a standalone operations page', async () => {
render(<App />);
expect(await screen.findByRole('heading', { name: '通知规则' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '通知闭环' })).toBeInTheDocument();
expect(screen.getByText('面向客户交付告警影响、通知对象、升级时限和恢复验收;规则配置只作为闭环背后的证据。')).toBeInTheDocument();
expect(screen.getByText('客户告警闭环')).toBeInTheDocument();
expect(screen.getByText('从告警触发到通知升级,再到验收恢复')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户告警闭环 发现问题 告警队列' })).toBeInTheDocument();
@@ -4845,7 +4846,7 @@ test('copies priority queue notification digest on quality page', async () => {
expect(screen.getByText('把告警翻译成客户能理解的通知、时限和恢复标准')).toBeInTheDocument();
expect(screen.getByText('每一条告警都先回答影响哪些车、通知谁、多久升级、恢复后如何验收;链路、协议和原始记录只作为证据附件。')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户通知SLA 客户影响范围 影响摘要' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户通知SLA 通知负责人 通知规则' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户通知SLA 通知负责人 通知闭环' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户通知SLA 升级时限 复制策略' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户通知SLA 恢复验收 交接包' })).toBeInTheDocument();
expect(screen.getAllByText('接入运维 + 业务责任人').length).toBeGreaterThanOrEqual(1);
@@ -4864,7 +4865,7 @@ test('copies priority queue notification digest on quality page', async () => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('实时http://localhost:3000/#/realtime?keyword=VIN-DIGEST-001&protocol=VEHICLE_SERVICE'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('原始记录http://localhost:3000/#/history-query?keyword=VIN-DIGEST-001&protocol=VEHICLE_SERVICE&tab=raw&dateFrom=2026-07-03&dateTo=2026-07-04&includeFields=true'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('2. [P1] 粤A汇总2 / 13307795426'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('通知规则http://localhost:3000/#/notification-rules'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('通知闭环http://localhost:3000/#/notification-rules'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('运维质量http://localhost:3000/#/ops-quality'));
fireEvent.click(screen.getByRole('button', { name: '复制优先队列通知汇总' }));