feat(platform): export notification rules
This commit is contained in:
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { OpsHealth, QualityAlertRule, QualityNotificationPlan, QualityNotificationPolicy, QualityPriorityIssue } from '../api/types';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
|
||||
import { qualityIssueLabel } from '../domain/qualityIssue';
|
||||
|
||||
function formatEscalationMinutes(minutes?: number) {
|
||||
@@ -26,6 +27,32 @@ function ruleTitle(rule: QualityAlertRule) {
|
||||
return rule.title || qualityIssueLabel(rule.issueType);
|
||||
}
|
||||
|
||||
type NotificationExportRow = {
|
||||
category: string;
|
||||
name: string;
|
||||
level: string;
|
||||
ownerOrTarget: string;
|
||||
triggerOrCondition: string;
|
||||
notifyOrChannel: string;
|
||||
slaOrEscalation: string;
|
||||
acceptance: string;
|
||||
count: number | string;
|
||||
release: string;
|
||||
};
|
||||
|
||||
const notificationExportColumns: CsvColumn<NotificationExportRow>[] = [
|
||||
{ title: '类型', value: (row) => row.category },
|
||||
{ title: '名称', value: (row) => row.name },
|
||||
{ title: '级别', value: (row) => row.level },
|
||||
{ title: '责任方', value: (row) => row.ownerOrTarget },
|
||||
{ title: '触发条件', value: (row) => row.triggerOrCondition },
|
||||
{ title: '通知方式', value: (row) => row.notifyOrChannel },
|
||||
{ title: 'SLA/升级', value: (row) => row.slaOrEscalation },
|
||||
{ title: '验收口径', value: (row) => row.acceptance },
|
||||
{ title: '当前命中', value: (row) => row.count },
|
||||
{ title: '运行版本', value: (row) => row.release }
|
||||
];
|
||||
|
||||
function notificationRulesRunbook(plan: QualityNotificationPlan | null, release?: string) {
|
||||
const rules = plan?.rules ?? [];
|
||||
const policies = plan?.policies ?? [];
|
||||
@@ -129,6 +156,35 @@ function evidenceColor(issue: QualityPriorityIssue): 'green' | 'orange' {
|
||||
return countEvidenceLinks(issue) >= 4 ? 'green' : 'orange';
|
||||
}
|
||||
|
||||
function notificationExportRows(plan: QualityNotificationPlan | null, release = ''): NotificationExportRow[] {
|
||||
const version = release.trim() || '-';
|
||||
const ruleRows = (plan?.rules ?? []).map((rule) => ({
|
||||
category: '触发规则',
|
||||
name: ruleTitle(rule),
|
||||
level: rule.level,
|
||||
ownerOrTarget: rule.owner,
|
||||
triggerOrCondition: rule.trigger,
|
||||
notifyOrChannel: rule.notify,
|
||||
slaOrEscalation: rule.sla,
|
||||
acceptance: '-',
|
||||
count: Number(rule.count ?? 0),
|
||||
release: version
|
||||
}));
|
||||
const policyRows = (plan?.policies ?? []).map((policy) => ({
|
||||
category: '通知策略',
|
||||
name: policy.name,
|
||||
level: policy.name.startsWith('P0') ? 'P0' : policy.name.startsWith('P1') ? 'P1' : '-',
|
||||
ownerOrTarget: policy.target,
|
||||
triggerOrCondition: policy.condition,
|
||||
notifyOrChannel: policy.channel,
|
||||
slaOrEscalation: formatEscalationMinutes(policy.escalationMinutes),
|
||||
acceptance: policy.acceptanceCriteria || '-',
|
||||
count: '-',
|
||||
release: version
|
||||
}));
|
||||
return [...ruleRows, ...policyRows];
|
||||
}
|
||||
|
||||
function navigateHash(hash: string) {
|
||||
if (!hash) return;
|
||||
window.location.hash = hash;
|
||||
@@ -176,6 +232,15 @@ export function NotificationRules() {
|
||||
const evidenceCompleteCount = priorityIssues.filter((issue) => countEvidenceLinks(issue) >= 4).length;
|
||||
const primaryOwner = rules.find((rule) => rule.count > 0)?.owner || policies[0]?.target || '-';
|
||||
const nextPriorityIssue = priorityIssues[0];
|
||||
const exportNotificationRules = () => {
|
||||
const rows = notificationExportRows(plan, release);
|
||||
if (rows.length === 0) {
|
||||
Toast.warning('当前没有可导出的通知规则');
|
||||
return;
|
||||
}
|
||||
downloadCsv(`notification-rules-${release || 'current'}.csv`, buildCsv(notificationExportColumns, rows));
|
||||
Toast.success(`已导出 ${rows.length.toLocaleString()} 条通知规则`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vp-page">
|
||||
@@ -250,7 +315,7 @@ export function NotificationRules() {
|
||||
|
||||
<Card
|
||||
bordered
|
||||
title={<Space><span>规则运行手册</span><Button size="small" aria-label="复制通知规则Runbook" icon={<IconCopy />} onClick={() => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')}>复制通知规则Runbook</Button><Button size="small" loading={loading} onClick={load}>刷新</Button></Space>}
|
||||
title={<Space><span>规则运行手册</span><Button size="small" aria-label="复制通知规则Runbook" icon={<IconCopy />} onClick={() => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')}>复制通知规则Runbook</Button><Button size="small" aria-label="导出通知规则CSV" onClick={exportNotificationRules}>导出规则 CSV</Button><Button size="small" loading={loading} onClick={load}>刷新</Button></Space>}
|
||||
>
|
||||
<div className="vp-alert-flow">
|
||||
{[
|
||||
|
||||
Reference in New Issue
Block a user