feat(platform): surface notification priority queue
This commit is contained in:
@@ -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) => (
|
||||
|
||||
Reference in New Issue
Block a user