feat(awooop): surface status chain on work queues
This commit is contained in:
@@ -22,6 +22,10 @@ import {
|
||||
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { IncidentEvidenceHeader } from "@/components/awooop/incident-evidence-header";
|
||||
import {
|
||||
AwoooPStatusChainPanel,
|
||||
type AwoooPStatusChain,
|
||||
} from "@/components/awooop/status-chain";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface RunDetail {
|
||||
@@ -63,6 +67,7 @@ interface RunRemediationHistory {
|
||||
interface RunDetailResponse {
|
||||
run: RunDetail;
|
||||
remediation_history?: RunRemediationHistory;
|
||||
awooop_status_chain?: AwoooPStatusChain | null;
|
||||
}
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "";
|
||||
@@ -480,6 +485,8 @@ export default function ApprovalDecisionPage({
|
||||
writesAutoRepairResult={latestRemediation?.writes_auto_repair_result}
|
||||
/>
|
||||
|
||||
<AwoooPStatusChainPanel chain={detail?.awooop_status_chain} />
|
||||
|
||||
<ApprovalRemediationEvidence
|
||||
history={detail?.remediation_history}
|
||||
locale={locale}
|
||||
|
||||
@@ -21,6 +21,10 @@ import {
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import {
|
||||
AwoooPStatusChainPanel,
|
||||
type AwoooPStatusChain,
|
||||
} from "@/components/awooop/status-chain";
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@@ -57,6 +61,7 @@ interface Approval {
|
||||
created_at: string;
|
||||
timeout_at: string | null;
|
||||
remediation_summary?: RemediationSummary | null;
|
||||
awooop_status_chain?: AwoooPStatusChain | null;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -294,6 +299,9 @@ function ApprovalRow({ approval }: { approval: Approval }) {
|
||||
<td className="px-4 py-3">
|
||||
<RemediationEvidenceCell summary={approval.remediation_summary} />
|
||||
</td>
|
||||
<td className="min-w-[280px] px-4 py-3">
|
||||
<AwoooPStatusChainPanel chain={approval.awooop_status_chain} compact />
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className="text-sm text-muted-foreground font-mono">
|
||||
{formattedDate}
|
||||
@@ -312,6 +320,7 @@ function ApprovalRow({ approval }: { approval: Approval }) {
|
||||
|
||||
export default function ApprovalsPage() {
|
||||
const tEvidence = useTranslations("awooop.listEvidence");
|
||||
const tStatusChain = useTranslations("awooop.statusChain");
|
||||
const [approvals, setApprovals] = useState<Approval[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -554,6 +563,9 @@ export default function ApprovalsPage() {
|
||||
<th className="text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{tEvidence("column")}
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{tStatusChain("title")}
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
建立時間
|
||||
</th>
|
||||
@@ -566,7 +578,7 @@ export default function ApprovalsPage() {
|
||||
{loading ? (
|
||||
Array.from({ length: 5 }).map((_, i) => (
|
||||
<tr key={i} className="border-b border-border">
|
||||
{Array.from({ length: 7 }).map((_, j) => (
|
||||
{Array.from({ length: 8 }).map((_, j) => (
|
||||
<td key={j} className="px-4 py-3">
|
||||
<div className="h-5 bg-muted animate-pulse rounded w-20" />
|
||||
</td>
|
||||
|
||||
@@ -27,6 +27,10 @@ import {
|
||||
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { IncidentEvidenceHeader } from "@/components/awooop/incident-evidence-header";
|
||||
import {
|
||||
AwoooPStatusChainPanel,
|
||||
type AwoooPStatusChain,
|
||||
} from "@/components/awooop/status-chain";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type WorkStatus = "live" | "in_progress" | "blocked" | "watching";
|
||||
@@ -278,6 +282,7 @@ type Telemetry = {
|
||||
slo: SloResponse | null;
|
||||
remediationHistory: RemediationHistoryResponse | null;
|
||||
driftFingerprintState: DriftFingerprintState | null;
|
||||
statusChain: AwoooPStatusChain | null;
|
||||
};
|
||||
|
||||
type WorkItem = {
|
||||
@@ -371,6 +376,33 @@ function recurrenceOpenItems(recurrence: RecurrenceResponse | null) {
|
||||
return (recurrence?.items ?? []).filter((item) => item.work_item?.status === "open");
|
||||
}
|
||||
|
||||
function firstIncidentId(...candidates: Array<string | null | undefined>) {
|
||||
return candidates.find((candidate) => Boolean(candidate?.trim()))?.trim() ?? null;
|
||||
}
|
||||
|
||||
function selectStatusChainIncidentId(
|
||||
focusedIncidentId: string | null,
|
||||
remediationHistory: RemediationHistoryResponse | null,
|
||||
recurrence: RecurrenceResponse | null
|
||||
) {
|
||||
const latestRemediationIncident = remediationHistory?.items?.find(
|
||||
(item) => Boolean(item.incident_id?.trim())
|
||||
)?.incident_id;
|
||||
const latestOpenRecurrence = recurrenceOpenItems(recurrence)[0] ?? null;
|
||||
const latestRecurrenceIncident = recurrence?.items?.find(
|
||||
(item) => Boolean(item.latest_incident_id?.trim() || item.work_item?.incident_id?.trim())
|
||||
);
|
||||
|
||||
return firstIncidentId(
|
||||
focusedIncidentId,
|
||||
latestRemediationIncident,
|
||||
latestOpenRecurrence?.latest_incident_id,
|
||||
latestOpenRecurrence?.work_item?.incident_id,
|
||||
latestRecurrenceIncident?.latest_incident_id,
|
||||
latestRecurrenceIncident?.work_item?.incident_id
|
||||
);
|
||||
}
|
||||
|
||||
function recurrenceRepairStatusKey(status?: string | null) {
|
||||
if (
|
||||
status === "auto_repair_verified" ||
|
||||
@@ -1382,6 +1414,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
slo: null,
|
||||
remediationHistory: null,
|
||||
driftFingerprintState: null,
|
||||
statusChain: null,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||
@@ -1418,6 +1451,21 @@ export default function AwoooPWorkItemsPage() {
|
||||
fetchJson<DriftFingerprintState>(driftFingerprintUrl, 12000),
|
||||
]);
|
||||
|
||||
const statusChainIncidentId = selectStatusChainIncidentId(
|
||||
focusedIncidentId,
|
||||
remediationHistory,
|
||||
eventRecurrence
|
||||
);
|
||||
let statusChain: AwoooPStatusChain | null = null;
|
||||
if (statusChainIncidentId) {
|
||||
const statusChainParams = new URLSearchParams({ project_id: projectId });
|
||||
statusChainParams.append("incident_id", statusChainIncidentId);
|
||||
statusChain = await fetchJson<AwoooPStatusChain>(
|
||||
`${API_BASE}/api/v1/platform/status-chain?${statusChainParams.toString()}`,
|
||||
12000
|
||||
);
|
||||
}
|
||||
|
||||
setTelemetry({
|
||||
quality,
|
||||
governanceEvents,
|
||||
@@ -1427,10 +1475,11 @@ export default function AwoooPWorkItemsPage() {
|
||||
slo,
|
||||
remediationHistory,
|
||||
driftFingerprintState,
|
||||
statusChain,
|
||||
});
|
||||
setLastUpdated(new Date());
|
||||
setLoading(false);
|
||||
}, [projectId]);
|
||||
}, [focusedIncidentId, projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTelemetry();
|
||||
@@ -1533,6 +1582,8 @@ export default function AwoooPWorkItemsPage() {
|
||||
writesAutoRepairResult={latestRemediationHistory?.writes_auto_repair_result}
|
||||
/>
|
||||
|
||||
<AwoooPStatusChainPanel chain={telemetry.statusChain} />
|
||||
|
||||
<RecurrenceWorkQueuePanel
|
||||
recurrence={telemetry.eventRecurrence}
|
||||
focusedWorkItemId={focusedWorkItemId}
|
||||
|
||||
Reference in New Issue
Block a user