fix(web): 補 Work Items 租戶讀取上下文
All checks were successful
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m39s
CD Pipeline / build-and-deploy (push) Successful in 5m36s
CD Pipeline / post-deploy-checks (push) Successful in 2m28s

This commit is contained in:
Your Name
2026-06-18 12:24:11 +08:00
parent 2b17ed5f44
commit 46cb93ec49

View File

@@ -1105,6 +1105,11 @@ async function fetchJson<T>(url: string, timeoutMs = 8000): Promise<T | null> {
}
}
function withProjectId(url: string, projectId: string) {
const separator = url.includes("?") ? "&" : "?";
return `${url}${separator}project_id=${encodeURIComponent(projectId)}`;
}
async function postJson<T>(
url: string,
body: Record<string, unknown>,
@@ -1146,8 +1151,14 @@ function recurrenceOpenItems(recurrence: RecurrenceResponse | null) {
return (recurrence?.items ?? []).filter((item) => item.work_item?.status === "open");
}
const CANONICAL_INCIDENT_ID_RE = /^INC-\d{8}-[A-Z0-9]{4,}$/;
function isCanonicalIncidentId(candidate: string | null | undefined) {
return Boolean(candidate?.trim() && CANONICAL_INCIDENT_ID_RE.test(candidate.trim()));
}
function firstIncidentId(...candidates: Array<string | null | undefined>) {
return candidates.find((candidate) => Boolean(candidate?.trim()))?.trim() ?? null;
return candidates.find(isCanonicalIncidentId)?.trim() ?? null;
}
function parseRepairCandidateDraftWorkItemId(
@@ -6105,22 +6116,46 @@ export default function AwoooPWorkItemsPage() {
setLoading(true);
const encodedProjectId = encodeURIComponent(projectId);
const qualityUrl = `${API_BASE}/api/v1/platform/truth-chain/quality/summary?project_id=${encodedProjectId}&hours=24&limit=30`;
const governanceEventsUrl = `${API_BASE}/api/v1/ai/governance/events?event_type=knowledge_degradation&event_type=governance_slo_data_gap&status=unresolved&size=10`;
const governanceQueueUrl = `${API_BASE}/api/v1/ai/governance/queue?dispatch_status=pending&size=10`;
const governanceKnowledgeQueueUrl = `${API_BASE}/api/v1/ai/governance/queue?dispatch_status=all&event_type=knowledge_degradation&size=20`;
const knowledgeReviewDraftsUrl = `${API_BASE}/api/v1/knowledge?entry_type=auto_runbook&status=review&q=${encodeURIComponent("KM healthcheck")}&limit=100`;
const knowledgeReviewDedupeUrl = `${API_BASE}/api/v1/ai/governance/km-review-drafts/dedupe?limit=100`;
const governanceEventsUrl = withProjectId(
`${API_BASE}/api/v1/ai/governance/events?event_type=knowledge_degradation&event_type=governance_slo_data_gap&status=unresolved&size=10`,
projectId
);
const governanceQueueUrl = withProjectId(
`${API_BASE}/api/v1/ai/governance/queue?dispatch_status=pending&size=10`,
projectId
);
const governanceKnowledgeQueueUrl = withProjectId(
`${API_BASE}/api/v1/ai/governance/queue?dispatch_status=all&event_type=knowledge_degradation&size=20`,
projectId
);
const knowledgeReviewDraftsUrl = withProjectId(
`${API_BASE}/api/v1/knowledge?entry_type=auto_runbook&status=review&q=${encodeURIComponent("KM healthcheck")}&limit=100`,
projectId
);
const knowledgeReviewDedupeUrl = withProjectId(
`${API_BASE}/api/v1/ai/governance/km-review-drafts/dedupe?limit=100`,
projectId
);
const knowledgeStaleCandidatesUrl = `${API_BASE}/api/v1/ai/governance/km-stale-candidates?project_id=${encodedProjectId}&limit=20`;
const knowledgeStaleOwnerReviewsUrl = `${API_BASE}/api/v1/ai/governance/km-stale-owner-reviews?project_id=${encodedProjectId}&dispatch_status=pending&limit=30`;
const knowledgeStaleOwnerReviewBurnDownUrl = `${API_BASE}/api/v1/ai/governance/km-stale-owner-review-burndown?project_id=${encodedProjectId}&limit=20`;
const knowledgeStaleOwnerReviewCompletionQueueUrl = `${API_BASE}/api/v1/ai/governance/km-stale-owner-review-completion-queue?project_id=${encodedProjectId}&status_bucket=all&limit=30`;
const channelEventsUrl = `${API_BASE}/api/v1/platform/events/recent?project_id=${encodedProjectId}&provider_prefix=alertmanager&limit=20`;
const recurrenceUrl = `${API_BASE}/api/v1/platform/events/dossier/recurrence?project_id=${encodedProjectId}&limit=100`;
const sloUrl = `${API_BASE}/api/v1/ai/slo`;
const remediationHistoryUrl = `${API_BASE}/api/v1/ai/slo/remediation/history?limit=80`;
const driftFingerprintUrl = `${API_BASE}/api/v1/drift/fingerprints/state?namespace=awoooi-prod`;
const sloUrl = withProjectId(`${API_BASE}/api/v1/ai/slo`, projectId);
const remediationHistoryUrl = withProjectId(
`${API_BASE}/api/v1/ai/slo/remediation/history?limit=80`,
projectId
);
const driftFingerprintUrl = withProjectId(
`${API_BASE}/api/v1/drift/fingerprints/state?namespace=awoooi-prod`,
projectId
);
const callbackRepliesUrl = `${API_BASE}/api/v1/platform/runs/callback-replies?project_id=${encodedProjectId}&per_page=100`;
const aiRouteStatusUrl = `${API_BASE}/api/v1/platform/ai-route-status?workload_type=deep_rca`;
const aiRouteStatusUrl = withProjectId(
`${API_BASE}/api/v1/platform/ai-route-status?workload_type=deep_rca`,
projectId
);
const [
quality,
@@ -6177,7 +6212,10 @@ export default function AwoooPWorkItemsPage() {
const timelineIncidentId = statusChain?.source_id ?? statusChainIncidentId;
const incidentTimeline = timelineIncidentId
? await fetchJson<IncidentTimelineResponse>(
`${API_BASE}/api/v1/incidents/${encodeURIComponent(timelineIncidentId)}/timeline`,
withProjectId(
`${API_BASE}/api/v1/incidents/${encodeURIComponent(timelineIncidentId)}/timeline`,
projectId
),
12000
)
: null;