feat(platform): add alert policy runbook copy
This commit is contained in:
@@ -449,6 +449,26 @@ function normalizePriorityIssues(rows?: QualityPriorityIssue[]): PriorityIssueRo
|
|||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notificationPolicyRunbookText(policies: QualityNotificationPolicy[], rules: AlertRuleRow[]) {
|
||||||
|
const activeRules = rules.filter((row) => row.count > 0);
|
||||||
|
const lines = policies.map((policy, index) => [
|
||||||
|
`${index + 1}. ${policy.name} / ${policy.target}`,
|
||||||
|
` 触发:${policy.condition}`,
|
||||||
|
` 渠道:${policy.channel}`,
|
||||||
|
` 升级:${formatEscalationMinutes(policy.escalationMinutes) || '-'}`,
|
||||||
|
` 验收:${policy.acceptanceCriteria || '-'}`
|
||||||
|
].join('\n'));
|
||||||
|
return [
|
||||||
|
'【告警通知策略Runbook】',
|
||||||
|
`活跃规则:${activeRules.length.toLocaleString()} 类`,
|
||||||
|
`P0规则:${activeRules.filter((row) => row.level === 'P0').length.toLocaleString()} 类`,
|
||||||
|
'',
|
||||||
|
...lines,
|
||||||
|
'',
|
||||||
|
`告警筛选:${qualityShareURL()}`
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
async function copyText(value: string, label: string) {
|
async function copyText(value: string, label: string) {
|
||||||
const text = value.trim();
|
const text = value.trim();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@@ -629,6 +649,9 @@ export function Quality({
|
|||||||
}
|
}
|
||||||
copyText(priorityIssueDigestText(priorityRows, notificationPlan?.summary ?? summary, health?.runtime?.platformRelease, policies), '优先队列通知汇总');
|
copyText(priorityIssueDigestText(priorityRows, notificationPlan?.summary ?? summary, health?.runtime?.platformRelease, policies), '优先队列通知汇总');
|
||||||
};
|
};
|
||||||
|
const copyPolicyRunbook = () => {
|
||||||
|
copyText(notificationPolicyRunbookText(policies, rules), '通知策略Runbook');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vp-page">
|
<div className="vp-page">
|
||||||
@@ -783,7 +806,10 @@ export function Quality({
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<Card bordered title="通知策略">
|
<Card
|
||||||
|
bordered
|
||||||
|
title={<Space><span>通知策略</span><Button size="small" onClick={copyPolicyRunbook}>复制通知策略Runbook</Button></Space>}
|
||||||
|
>
|
||||||
<div className="vp-notification-policy-list">
|
<div className="vp-notification-policy-list">
|
||||||
{policies.map((item) => (
|
{policies.map((item) => (
|
||||||
<div key={item.name} className="vp-notification-policy">
|
<div key={item.name} className="vp-notification-policy">
|
||||||
|
|||||||
@@ -2881,6 +2881,11 @@ test('renders quality issues as vehicle-service governance labels', async () =>
|
|||||||
|
|
||||||
test('shows alert rule and notification policy workspace on quality page', async () => {
|
test('shows alert rule and notification policy workspace on quality page', async () => {
|
||||||
window.history.replaceState(null, '', '/#/quality');
|
window.history.replaceState(null, '', '/#/quality');
|
||||||
|
const writeText = vi.fn(() => Promise.resolve());
|
||||||
|
Object.defineProperty(navigator, 'clipboard', {
|
||||||
|
configurable: true,
|
||||||
|
value: { writeText }
|
||||||
|
});
|
||||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||||
const path = String(input);
|
const path = String(input);
|
||||||
if (path.includes('/api/ops/health')) {
|
if (path.includes('/api/ops/health')) {
|
||||||
@@ -2961,6 +2966,12 @@ test('shows alert rule and notification policy workspace on quality page', async
|
|||||||
expect(screen.getByText('站内告警 / 邮件 / 企业微信')).toBeInTheDocument();
|
expect(screen.getByText('站内告警 / 邮件 / 企业微信')).toBeInTheDocument();
|
||||||
expect(screen.getByText('30 分钟升级')).toBeInTheDocument();
|
expect(screen.getByText('30 分钟升级')).toBeInTheDocument();
|
||||||
expect(screen.getByText('验收:来源恢复并持续 10 分钟,车辆服务可查到实时与历史证据')).toBeInTheDocument();
|
expect(screen.getByText('验收:来源恢复并持续 10 分钟,车辆服务可查到实时与历史证据')).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '复制通知策略Runbook' }));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【告警通知策略Runbook】'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('P0 实时中断 / 接入运维 + 业务责任人'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('升级:30 分钟升级'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('验收:来源恢复并持续 10 分钟,车辆服务可查到实时与历史证据'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('shows actionable priority queue on quality page', async () => {
|
test('shows actionable priority queue on quality page', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user