feat(platform): surface notification priority queue

This commit is contained in:
lingniu
2026-07-04 18:17:09 +08:00
parent b8c8ff35fc
commit bdad6f7bf7
2 changed files with 104 additions and 3 deletions

View File

@@ -1,8 +1,8 @@
import { Button, Card, Space, Table, Tag, Toast } from '@douyinfe/semi-ui';
import { Button, Card, Space, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui';
import { IconCopy } from '@douyinfe/semi-icons';
import { useEffect, useState } from 'react';
import { api } from '../api/client';
import type { OpsHealth, QualityAlertRule, QualityNotificationPlan, QualityNotificationPolicy } from '../api/types';
import type { OpsHealth, QualityAlertRule, QualityNotificationPlan, QualityNotificationPolicy, QualityPriorityIssue } from '../api/types';
import { PageHeader } from '../components/PageHeader';
import { qualityIssueLabel } from '../domain/qualityIssue';
@@ -56,6 +56,31 @@ function notificationRulesRunbook(plan: QualityNotificationPlan | null, release?
return lines.join('\n');
}
function priorityIssueDigest(plan: QualityNotificationPlan | null, release?: string) {
const issues = plan?.priorityIssues ?? [];
return [
'【当前待通知告警】',
`运行版本:${release?.trim() || '-'}`,
`待通知:${issues.length.toLocaleString()}`,
...issues.map((issue, index) => [
`${index + 1}. [${issue.priority}] ${issue.vehicleLabel}`,
` 问题:${qualityIssueLabel(issue.issueType)} / ${issue.protocol}`,
` SLA${issue.sla}`,
` 建议动作:${issue.actionLabel}`,
` 详情:${issue.detail || '-'}`,
` 车辆服务:${window.location.origin}${window.location.pathname}${issue.vehicleHash}`,
` 实时定位:${window.location.origin}${window.location.pathname}${issue.realtimeHash}`,
` 轨迹证据:${window.location.origin}${window.location.pathname}${issue.historyHash}`,
` RAW证据${window.location.origin}${window.location.pathname}${issue.rawHash}`
].join('\n'))
].join('\n');
}
function navigateHash(hash: string) {
if (!hash) return;
window.location.hash = hash;
}
async function copyText(value: string, label: string) {
try {
await navigator.clipboard.writeText(value);
@@ -90,6 +115,7 @@ export function NotificationRules() {
const rules = plan?.rules ?? [];
const policies = plan?.policies ?? [];
const priorityIssues = plan?.priorityIssues ?? [];
const activeRuleCount = plan?.activeRuleCount ?? rules.filter((rule) => rule.count > 0).length;
const p0RuleCount = plan?.p0RuleCount ?? rules.filter((rule) => rule.count > 0 && rule.level === 'P0').length;
const release = health?.runtime?.platformRelease ?? '';
@@ -110,6 +136,10 @@ export function NotificationRules() {
<div className="vp-kpi-value">{policies.length.toLocaleString()}</div>
<div className="vp-kpi-label"></div>
</Card>
<Card bordered loading={loading}>
<div className="vp-kpi-value">{priorityIssues.length.toLocaleString()}</div>
<div className="vp-kpi-label"></div>
</Card>
<Card bordered loading={loading}>
<div className="vp-kpi-value">{release || '-'}</div>
<div className="vp-kpi-label"></div>
@@ -152,6 +182,46 @@ export function NotificationRules() {
/>
</Card>
<Card
bordered
loading={loading}
title={<Space><span></span><Button size="small" aria-label="复制待通知汇总" disabled={priorityIssues.length === 0} icon={<IconCopy />} onClick={() => copyText(priorityIssueDigest(plan, release), '待通知告警')}></Button></Space>}
style={{ marginTop: 16 }}
>
<Table<QualityPriorityIssue>
pagination={false}
dataSource={priorityIssues}
rowKey={(row?: QualityPriorityIssue) => `${row?.priority ?? ''}-${row?.protocol ?? ''}-${row?.issueType ?? ''}-${row?.vehicleLabel ?? ''}-${row?.lastSeen ?? ''}`}
columns={[
{ title: '优先级', width: 90, render: (_: unknown, row: QualityPriorityIssue) => <Tag color={row.priority === 'P0' ? 'red' : 'orange'}>{row.priority}</Tag> },
{ title: '车辆', width: 210, dataIndex: 'vehicleLabel' },
{ title: '问题', width: 130, render: (_: unknown, row: QualityPriorityIssue) => qualityIssueLabel(row.issueType) },
{ title: 'SLA', width: 120, dataIndex: 'sla' },
{ title: '建议动作', width: 160, dataIndex: 'actionLabel' },
{ title: '最后时间', width: 170, dataIndex: 'lastSeen' },
{
title: '说明',
render: (_: unknown, row: QualityPriorityIssue) => (
<Typography.Text ellipsis={{ showTooltip: true }}>{row.detail || row.actionDetail || '-'}</Typography.Text>
)
},
{
title: '证据',
width: 360,
render: (_: unknown, row: QualityPriorityIssue) => (
<Space spacing={4} wrap>
<Button size="small" disabled={!row.vehicleHash} onClick={() => navigateHash(row.vehicleHash)}></Button>
<Button size="small" disabled={!row.realtimeHash} onClick={() => navigateHash(row.realtimeHash)}></Button>
<Button size="small" disabled={!row.historyHash} onClick={() => navigateHash(row.historyHash)}></Button>
<Button size="small" disabled={!row.rawHash} onClick={() => navigateHash(row.rawHash)}>RAW</Button>
<Button size="small" aria-label="复制单条告警通知" icon={<IconCopy />} onClick={() => copyText(row.notificationText, '告警通知')}></Button>
</Space>
)
}
]}
/>
</Card>
<Card bordered title="通知策略" loading={loading} style={{ marginTop: 16 }}>
<div className="vp-notification-policy-list">
{policies.map((policy: QualityNotificationPolicy) => (