feat(web): 在 Work Items 顯示報表缺口 owner review
This commit is contained in:
@@ -7815,6 +7815,47 @@
|
||||
"remediationHistory": "補救歷史"
|
||||
}
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"eyebrow": "報表資料源 owner review",
|
||||
"title": "報表資料源 PlayBook / Verifier 處置板",
|
||||
"subtitle": "把 report-source-gap 從報表頁接到 Work Items:每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 no-send 與 owner review。",
|
||||
"loading": "正在讀取 report-source-gap owner review read model。",
|
||||
"unavailable": "報表資料源 read model 尚未回應;不能把報表全 0 或缺資料判定為健康。",
|
||||
"empty": "目前沒有 report-source-gap owner review 卡。",
|
||||
"boundaryTitle": "不可誤讀合約",
|
||||
"boundary": "live Telegram send={live};runtime gate={gate}。這裡只做草案與 owner review readback,不發送、不排程、不執行。",
|
||||
"openReports": "回報表總控",
|
||||
"ownerRequired": "需 owner review",
|
||||
"ownerOptional": "owner review 可後補",
|
||||
"scheduleBoundary": "排程仍維持 no-send preview",
|
||||
"fieldsTitle": "PlayBook 必填欄位",
|
||||
"checksTitle": "Verifier 檢查",
|
||||
"nextAction": "下一步:{action}",
|
||||
"metrics": {
|
||||
"sources": "來源可讀",
|
||||
"gaps": "資料缺口",
|
||||
"playbooks": "PlayBook 草案",
|
||||
"verifiers": "Verifier 計畫",
|
||||
"confidence": "可信度",
|
||||
"ownerReview": "需 owner"
|
||||
},
|
||||
"assets": {
|
||||
"playbook": "PlayBook",
|
||||
"verifier": "Verifier",
|
||||
"script": "腳本",
|
||||
"schedule": "排程"
|
||||
},
|
||||
"states": {
|
||||
"draft_required": "需草案",
|
||||
"plan_required": "需計畫",
|
||||
"readback_only": "只讀回讀",
|
||||
"no_send_preview": "no-send preview",
|
||||
"ok": "正常",
|
||||
"gap": "缺口",
|
||||
"blocked": "阻塞",
|
||||
"unknown": "待確認"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"live": "已完成",
|
||||
"in_progress": "推進中",
|
||||
@@ -7850,6 +7891,9 @@
|
||||
"aiRouteRepairWorkItem": {
|
||||
"title": "AI Provider primary lane 修復工作項"
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"title": "報表資料源 PlayBook / Verifier owner review"
|
||||
},
|
||||
"configDriftFsm": {
|
||||
"title": "Config Drift fingerprint 狀態機"
|
||||
},
|
||||
@@ -7898,6 +7942,7 @@
|
||||
"autoRepair": "必須同時有 auto_repair、verification_result=success與KM 回寫",
|
||||
"recurrenceWorkItems": "Run 完成無修復、修復失敗與人工閘門必須進入可追蹤工作項",
|
||||
"aiRouteRepairWorkItem": "Provider lane 降級時必須顯示 evidence、owner、PlayBook候選與是否可自動修復",
|
||||
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 no-send 與 owner review;不得把全 0 當健康或自動執行授權",
|
||||
"configDriftFsm": "同一 drift fingerprint 必須顯示重複、PR、零 diff、交接與下一步",
|
||||
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或人工檢查",
|
||||
"telegramCallbacks": "按下詳情與歷史不能再只依賴 Redis TTL或舊快照",
|
||||
@@ -7937,6 +7982,11 @@
|
||||
"aiRouteRepairSafety": "可安全自動修復:{safe}",
|
||||
"aiRouteRepairSummary": "AI route 目前由 {selected} 承接;下一步:{action};需人工介入:{human}",
|
||||
"aiRouteRepairUnavailable": "AI route repair evidence 尚未回傳",
|
||||
"reportSourceGapOwnerReview": "報表資料源缺口:{gaps};PlayBook 草案 {playbooks};Verifier 計畫 {verifiers};需 owner {owners}",
|
||||
"reportSourceGapLatest": "最新工作項:{workItem};route={route}",
|
||||
"reportSourceGapAssets": "資產狀態:PlayBook {playbook};Verifier {verifier};排程 {schedule}",
|
||||
"reportSourceGapBoundary": "live send={live};runtime gate={gate}",
|
||||
"reportSourceGapEmpty": "目前沒有 report-source-gap 處置卡",
|
||||
"humanRequired": {
|
||||
"yes": "是",
|
||||
"no": "否"
|
||||
|
||||
@@ -7815,6 +7815,47 @@
|
||||
"remediationHistory": "補救歷史"
|
||||
}
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"eyebrow": "報表資料源 owner review",
|
||||
"title": "報表資料源 PlayBook / Verifier 處置板",
|
||||
"subtitle": "把 report-source-gap 從報表頁接到 Work Items:每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 no-send 與 owner review。",
|
||||
"loading": "正在讀取 report-source-gap owner review read model。",
|
||||
"unavailable": "報表資料源 read model 尚未回應;不能把報表全 0 或缺資料判定為健康。",
|
||||
"empty": "目前沒有 report-source-gap owner review 卡。",
|
||||
"boundaryTitle": "不可誤讀合約",
|
||||
"boundary": "live Telegram send={live};runtime gate={gate}。這裡只做草案與 owner review readback,不發送、不排程、不執行。",
|
||||
"openReports": "回報表總控",
|
||||
"ownerRequired": "需 owner review",
|
||||
"ownerOptional": "owner review 可後補",
|
||||
"scheduleBoundary": "排程仍維持 no-send preview",
|
||||
"fieldsTitle": "PlayBook 必填欄位",
|
||||
"checksTitle": "Verifier 檢查",
|
||||
"nextAction": "下一步:{action}",
|
||||
"metrics": {
|
||||
"sources": "來源可讀",
|
||||
"gaps": "資料缺口",
|
||||
"playbooks": "PlayBook 草案",
|
||||
"verifiers": "Verifier 計畫",
|
||||
"confidence": "可信度",
|
||||
"ownerReview": "需 owner"
|
||||
},
|
||||
"assets": {
|
||||
"playbook": "PlayBook",
|
||||
"verifier": "Verifier",
|
||||
"script": "腳本",
|
||||
"schedule": "排程"
|
||||
},
|
||||
"states": {
|
||||
"draft_required": "需草案",
|
||||
"plan_required": "需計畫",
|
||||
"readback_only": "只讀回讀",
|
||||
"no_send_preview": "no-send preview",
|
||||
"ok": "正常",
|
||||
"gap": "缺口",
|
||||
"blocked": "阻塞",
|
||||
"unknown": "待確認"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"live": "已完成",
|
||||
"in_progress": "推進中",
|
||||
@@ -7850,6 +7891,9 @@
|
||||
"aiRouteRepairWorkItem": {
|
||||
"title": "AI Provider primary lane 修復工作項"
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"title": "報表資料源 PlayBook / Verifier owner review"
|
||||
},
|
||||
"configDriftFsm": {
|
||||
"title": "Config Drift fingerprint 狀態機"
|
||||
},
|
||||
@@ -7898,6 +7942,7 @@
|
||||
"autoRepair": "必須同時有 auto_repair、verification_result=success與KM 回寫",
|
||||
"recurrenceWorkItems": "Run 完成無修復、修復失敗與人工閘門必須進入可追蹤工作項",
|
||||
"aiRouteRepairWorkItem": "Provider lane 降級時必須顯示 evidence、owner、PlayBook候選與是否可自動修復",
|
||||
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 no-send 與 owner review;不得把全 0 當健康或自動執行授權",
|
||||
"configDriftFsm": "同一 drift fingerprint 必須顯示重複、PR、零 diff、交接與下一步",
|
||||
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或人工檢查",
|
||||
"telegramCallbacks": "按下詳情與歷史不能再只依賴 Redis TTL或舊快照",
|
||||
@@ -7937,6 +7982,11 @@
|
||||
"aiRouteRepairSafety": "可安全自動修復:{safe}",
|
||||
"aiRouteRepairSummary": "AI route 目前由 {selected} 承接;下一步:{action};需人工介入:{human}",
|
||||
"aiRouteRepairUnavailable": "AI route repair evidence 尚未回傳",
|
||||
"reportSourceGapOwnerReview": "報表資料源缺口:{gaps};PlayBook 草案 {playbooks};Verifier 計畫 {verifiers};需 owner {owners}",
|
||||
"reportSourceGapLatest": "最新工作項:{workItem};route={route}",
|
||||
"reportSourceGapAssets": "資產狀態:PlayBook {playbook};Verifier {verifier};排程 {schedule}",
|
||||
"reportSourceGapBoundary": "live send={live};runtime gate={gate}",
|
||||
"reportSourceGapEmpty": "目前沒有 report-source-gap 處置卡",
|
||||
"humanRequired": {
|
||||
"yes": "是",
|
||||
"no": "否"
|
||||
|
||||
@@ -956,6 +956,61 @@ type AiRouteStatusResponse = {
|
||||
repair_evidence?: AiRouteRepairEvidence | null;
|
||||
};
|
||||
|
||||
type ReportSourceGapPlaybookVerifier = {
|
||||
work_item_id: string;
|
||||
source_id: string;
|
||||
display_name: string;
|
||||
route: string;
|
||||
playbook_draft_id: string;
|
||||
verifier_plan_id: string;
|
||||
playbook_state: string;
|
||||
verifier_state: string;
|
||||
script_state: string;
|
||||
schedule_state: string;
|
||||
owner_review_required: boolean;
|
||||
runtime_gate_open: boolean;
|
||||
playbook_template_fields: string[];
|
||||
verifier_checks: string[];
|
||||
next_action: string;
|
||||
};
|
||||
|
||||
type ReportSourceHealthResponse = {
|
||||
source_health?: Array<{
|
||||
source_id?: string | null;
|
||||
display_name?: string | null;
|
||||
state?: string | null;
|
||||
source_ok?: boolean | null;
|
||||
work_item_id?: string | null;
|
||||
next_action?: string | null;
|
||||
}>;
|
||||
automation_assets?: Array<{
|
||||
key?: string | null;
|
||||
label?: string | null;
|
||||
state?: string | null;
|
||||
done_count?: number | null;
|
||||
blocked_count?: number | null;
|
||||
next_action?: string | null;
|
||||
}>;
|
||||
source_gap_playbook_verifier?: ReportSourceGapPlaybookVerifier[];
|
||||
all_zero_assessment?: {
|
||||
is_all_zero_signal?: boolean | null;
|
||||
verdict?: string | null;
|
||||
next_action?: string | null;
|
||||
};
|
||||
rollups?: {
|
||||
source_count?: number | null;
|
||||
source_ok_count?: number | null;
|
||||
source_gap_count?: number | null;
|
||||
confidence_percent?: number | null;
|
||||
report_work_item_count?: number | null;
|
||||
source_gap_playbook_draft_count?: number | null;
|
||||
source_gap_verifier_plan_count?: number | null;
|
||||
source_gap_owner_review_required_count?: number | null;
|
||||
live_send_allowed_count?: number | null;
|
||||
runtime_gate_count?: number | null;
|
||||
};
|
||||
};
|
||||
|
||||
type Telemetry = {
|
||||
quality: AutomationQualitySummary | null;
|
||||
governanceEvents: GovernanceEventsResponse | null;
|
||||
@@ -976,6 +1031,7 @@ type Telemetry = {
|
||||
statusChain: AwoooPStatusChain | null;
|
||||
incidentTimeline: IncidentTimelineResponse | null;
|
||||
aiRouteStatus: AiRouteStatusResponse | null;
|
||||
reportSourceHealth: ReportSourceHealthResponse | null;
|
||||
};
|
||||
|
||||
type AutomationAssetLedgerKey = "km" | "playbook" | "script" | "schedule" | "verifier";
|
||||
@@ -1291,6 +1347,21 @@ function routeLabel(item?: RemediationHistoryItem | null) {
|
||||
return route || "--";
|
||||
}
|
||||
|
||||
function reportSourceStateKey(value?: string | null) {
|
||||
if (
|
||||
value === "draft_required" ||
|
||||
value === "plan_required" ||
|
||||
value === "readback_only" ||
|
||||
value === "no_send_preview" ||
|
||||
value === "ok" ||
|
||||
value === "gap" ||
|
||||
value === "blocked"
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function recurrenceOpenItems(recurrence: RecurrenceResponse | null) {
|
||||
return (recurrence?.items ?? []).filter((item) => item.work_item?.status === "open");
|
||||
}
|
||||
@@ -2382,6 +2453,14 @@ function buildWorkItems(
|
||||
(governanceUnresolved > 0 && governanceQueuePending === 0);
|
||||
const mcpGateMissing = hasGateFailure(telemetry.quality, "mcp_gateway_observed");
|
||||
const timelineGateMissing = hasGateFailure(telemetry.quality, "timeline_recorded");
|
||||
const reportSourceHealth = telemetry.reportSourceHealth;
|
||||
const reportSourceCards = reportSourceHealth?.source_gap_playbook_verifier ?? [];
|
||||
const reportSourceRollups = reportSourceHealth?.rollups;
|
||||
const reportSourceRuntimeGate = toCount(reportSourceRollups?.runtime_gate_count);
|
||||
const reportSourceOwnerReview = toCount(
|
||||
reportSourceRollups?.source_gap_owner_review_required_count
|
||||
);
|
||||
const firstReportSourceCard = reportSourceCards[0] ?? null;
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -2578,6 +2657,48 @@ function buildWorkItems(
|
||||
: [t("evidence.remediationHistoryEmpty")],
|
||||
href: "/governance",
|
||||
},
|
||||
{
|
||||
id: "reportSourceGapOwnerReview",
|
||||
phase: "P2-110D",
|
||||
status: reportSourceCards.length > 0
|
||||
? "in_progress"
|
||||
: reportSourceHealth
|
||||
? "watching"
|
||||
: "blocked",
|
||||
surfaceKey: "workItems",
|
||||
source: "/api/v1/agents/agent-report-source-health",
|
||||
gateKey: "reportSourceGapOwnerReview",
|
||||
evidence: t("evidence.reportSourceGapOwnerReview", {
|
||||
gaps: toCount(reportSourceRollups?.source_gap_count),
|
||||
playbooks: toCount(reportSourceRollups?.source_gap_playbook_draft_count),
|
||||
verifiers: toCount(reportSourceRollups?.source_gap_verifier_plan_count),
|
||||
owners: reportSourceOwnerReview,
|
||||
}),
|
||||
evidenceDetails: firstReportSourceCard
|
||||
? [
|
||||
t("evidence.reportSourceGapLatest", {
|
||||
workItem: firstReportSourceCard.work_item_id,
|
||||
route: firstReportSourceCard.route,
|
||||
}),
|
||||
t("evidence.reportSourceGapAssets", {
|
||||
playbook: t(
|
||||
`reportSourceGapOwnerReview.states.${reportSourceStateKey(firstReportSourceCard.playbook_state)}` as never
|
||||
),
|
||||
verifier: t(
|
||||
`reportSourceGapOwnerReview.states.${reportSourceStateKey(firstReportSourceCard.verifier_state)}` as never
|
||||
),
|
||||
schedule: t(
|
||||
`reportSourceGapOwnerReview.states.${reportSourceStateKey(firstReportSourceCard.schedule_state)}` as never
|
||||
),
|
||||
}),
|
||||
t("evidence.reportSourceGapBoundary", {
|
||||
live: toCount(reportSourceRollups?.live_send_allowed_count),
|
||||
gate: reportSourceRuntimeGate,
|
||||
}),
|
||||
]
|
||||
: [t("evidence.reportSourceGapEmpty")],
|
||||
href: `/awooop/work-items?project_id=awoooi#report-source-gap-owner-review`,
|
||||
},
|
||||
{
|
||||
id: "telegramCallbacks",
|
||||
phase: "T45",
|
||||
@@ -3193,6 +3314,172 @@ function AutomationAssetLedgerPanel({
|
||||
);
|
||||
}
|
||||
|
||||
function ReportSourceGapOwnerReviewPanel({
|
||||
sourceHealth,
|
||||
loading,
|
||||
}: {
|
||||
sourceHealth: ReportSourceHealthResponse | null;
|
||||
loading: boolean;
|
||||
}) {
|
||||
const t = useTranslations("awooop.workItems.reportSourceGapOwnerReview");
|
||||
const cards = sourceHealth?.source_gap_playbook_verifier ?? [];
|
||||
const rollups = sourceHealth?.rollups;
|
||||
const sourceCount = toCount(rollups?.source_count);
|
||||
const sourceOk = toCount(rollups?.source_ok_count);
|
||||
const sourceGap = toCount(rollups?.source_gap_count);
|
||||
const playbookDrafts = toCount(rollups?.source_gap_playbook_draft_count);
|
||||
const verifierPlans = toCount(rollups?.source_gap_verifier_plan_count);
|
||||
const ownerReviews = toCount(rollups?.source_gap_owner_review_required_count);
|
||||
const liveSend = toCount(rollups?.live_send_allowed_count);
|
||||
const runtimeGate = toCount(rollups?.runtime_gate_count);
|
||||
const confidence = toCount(rollups?.confidence_percent);
|
||||
const isUnavailable = !sourceHealth && !loading;
|
||||
|
||||
return (
|
||||
<section
|
||||
id="report-source-gap-owner-review"
|
||||
className="border border-[#e0ddd4] bg-white"
|
||||
data-testid="report-source-gap-owner-review"
|
||||
>
|
||||
<div className="grid gap-px bg-[#e0ddd4] lg:grid-cols-[0.92fr_1.08fr]">
|
||||
<div className="min-w-0 bg-white p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center border border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]">
|
||||
<FileText className="h-5 w-5" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.08em] text-[#8a5a08]">
|
||||
{t("eyebrow")}
|
||||
</p>
|
||||
<h3 className="mt-1 text-lg font-semibold tracking-normal text-[#141413]">
|
||||
{t("title")}
|
||||
</h3>
|
||||
<p className="mt-2 max-w-3xl text-sm leading-6 text-[#5f5b52]">
|
||||
{t("subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-px bg-[#e0ddd4] sm:grid-cols-2 xl:grid-cols-3">
|
||||
{[
|
||||
{ key: "sources", value: loading && !sourceHealth ? "--" : `${sourceOk}/${sourceCount}` },
|
||||
{ key: "gaps", value: loading && !sourceHealth ? "--" : sourceGap },
|
||||
{ key: "playbooks", value: loading && !sourceHealth ? "--" : playbookDrafts },
|
||||
{ key: "verifiers", value: loading && !sourceHealth ? "--" : verifierPlans },
|
||||
{ key: "confidence", value: loading && !sourceHealth ? "--" : `${confidence}%` },
|
||||
{ key: "ownerReview", value: loading && !sourceHealth ? "--" : ownerReviews },
|
||||
].map((metric) => (
|
||||
<div key={metric.key} className="min-w-0 bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#77736a]">
|
||||
{t(`metrics.${metric.key}` as never)}
|
||||
</p>
|
||||
<p className="mt-1 break-all font-mono text-xl font-semibold text-[#141413]">
|
||||
{metric.value}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2 text-xs leading-5 text-[#5f5b52]">
|
||||
<div className="border border-[#e0ddd4] bg-[#faf9f3] px-3 py-2">
|
||||
<p className="font-semibold text-[#141413]">{t("boundaryTitle")}</p>
|
||||
<p className="mt-1">{t("boundary", { live: liveSend, gate: runtimeGate })}</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/reports"
|
||||
className="inline-flex w-fit items-center gap-1.5 border border-[#d8d3c7] bg-white px-2.5 py-1 text-xs font-semibold text-[#141413] hover:border-[#d97757]"
|
||||
>
|
||||
{t("openReports")}
|
||||
<ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 bg-white p-4">
|
||||
{isUnavailable ? (
|
||||
<div className="border border-[#e2a29b] bg-[#fff0ef] px-3 py-2 text-sm leading-6 text-[#9f2f25]">
|
||||
{t("unavailable")}
|
||||
</div>
|
||||
) : cards.length === 0 ? (
|
||||
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2 text-sm leading-6 text-[#5f5b52]">
|
||||
{loading ? t("loading") : t("empty")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-3">
|
||||
{cards.map((card) => (
|
||||
<article key={card.work_item_id} className="border border-[#e0ddd4] bg-white p-3">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold text-[#141413]">
|
||||
{card.display_name}
|
||||
</p>
|
||||
<p className="mt-1 break-all font-mono text-[11px] leading-5 text-[#77736a]">
|
||||
{card.work_item_id}
|
||||
</p>
|
||||
</div>
|
||||
<span className="shrink-0 border border-[#d9b36f] bg-[#fff7e8] px-2 py-0.5 font-mono text-[11px] font-semibold text-[#8a5a08]">
|
||||
{card.owner_review_required ? t("ownerRequired") : t("ownerOptional")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 grid gap-2 md:grid-cols-4">
|
||||
{[
|
||||
["playbook", card.playbook_state, card.playbook_draft_id],
|
||||
["verifier", card.verifier_state, card.verifier_plan_id],
|
||||
["script", card.script_state, card.route],
|
||||
["schedule", card.schedule_state, t("scheduleBoundary")],
|
||||
].map(([key, state, detail]) => (
|
||||
<div key={key} className="min-w-0 border border-[#eee9dd] bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-[0.08em] text-[#77736a]">
|
||||
{t(`assets.${key}` as never)}
|
||||
</p>
|
||||
<p className="mt-1 text-xs font-semibold text-[#141413]">
|
||||
{t(`states.${reportSourceStateKey(state)}` as never)}
|
||||
</p>
|
||||
<p className="mt-1 break-all font-mono text-[10px] leading-4 text-[#5f5b52]">
|
||||
{detail}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 grid gap-3 md:grid-cols-2">
|
||||
<div className="border border-[#eee9dd] bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#141413]">{t("fieldsTitle")}</p>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{card.playbook_template_fields.map((field) => (
|
||||
<span key={field} className="border border-[#d8d3c7] bg-white px-2 py-0.5 font-mono text-[10px] text-[#5f5b52]">
|
||||
{field}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-[#eee9dd] bg-[#faf9f3] px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#141413]">{t("checksTitle")}</p>
|
||||
<div className="mt-2 grid gap-1">
|
||||
{card.verifier_checks.map((check) => (
|
||||
<div key={check} className="flex min-w-0 items-start gap-1.5 text-[11px] leading-4 text-[#5f5b52]">
|
||||
<CheckCircle2 className="mt-0.5 h-3.5 w-3.5 shrink-0 text-[#17602a]" aria-hidden="true" />
|
||||
<span className="break-words font-mono">{check}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-3 text-xs leading-5 text-[#5f5b52]">
|
||||
{t("nextAction", { action: card.next_action })}
|
||||
</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RepairCandidateDraftPanel({
|
||||
draft,
|
||||
chain,
|
||||
@@ -6439,6 +6726,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
statusChain: null,
|
||||
incidentTimeline: null,
|
||||
aiRouteStatus: null,
|
||||
reportSourceHealth: null,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||
@@ -6487,6 +6775,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
`${API_BASE}/api/v1/platform/ai-route-status?workload_type=deep_rca`,
|
||||
projectId
|
||||
);
|
||||
const reportSourceHealthUrl = `${API_BASE}/api/v1/agents/agent-report-source-health`;
|
||||
|
||||
const [
|
||||
quality,
|
||||
@@ -6506,6 +6795,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
driftFingerprintState,
|
||||
callbackReplies,
|
||||
aiRouteStatus,
|
||||
reportSourceHealth,
|
||||
] = await Promise.all([
|
||||
fetchJson<AutomationQualitySummary>(qualityUrl, 15000),
|
||||
fetchJson<GovernanceEventsResponse>(governanceEventsUrl),
|
||||
@@ -6524,6 +6814,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
fetchJson<DriftFingerprintState>(driftFingerprintUrl, 12000),
|
||||
fetchJson<CallbackRepliesWorkItemResponse>(callbackRepliesUrl, 12000),
|
||||
fetchJson<AiRouteStatusResponse>(aiRouteStatusUrl, 12000),
|
||||
fetchJson<ReportSourceHealthResponse>(reportSourceHealthUrl, 12000),
|
||||
]);
|
||||
|
||||
const statusChainIncidentId = selectStatusChainIncidentId(
|
||||
@@ -6571,6 +6862,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
statusChain,
|
||||
incidentTimeline,
|
||||
aiRouteStatus,
|
||||
reportSourceHealth,
|
||||
});
|
||||
setLastUpdated(new Date());
|
||||
setLoading(false);
|
||||
@@ -6674,6 +6966,11 @@ export default function AwoooPWorkItemsPage() {
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
<ReportSourceGapOwnerReviewPanel
|
||||
sourceHealth={telemetry.reportSourceHealth}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
<IncidentEvidenceHeader
|
||||
projectId={projectId}
|
||||
incidentIds={visibleIncidentIds}
|
||||
|
||||
Reference in New Issue
Block a user