feat(awooop): apply source correlation links
This commit is contained in:
@@ -140,6 +140,14 @@ type RecurrenceItem = {
|
||||
reviewer_id?: string | null;
|
||||
recorded_at?: string | null;
|
||||
} | null;
|
||||
source_correlation_apply?: {
|
||||
apply_id?: string | null;
|
||||
apply_status?: string | null;
|
||||
target_incident_id?: string | null;
|
||||
source_event_id?: string | null;
|
||||
source_event_provider_event_id?: string | null;
|
||||
recorded_at?: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
type RecurrenceResponse = {
|
||||
@@ -158,6 +166,7 @@ type RecurrenceResponse = {
|
||||
failed_repair_group_total?: number;
|
||||
source_correlation_review_group_total?: number;
|
||||
source_correlation_decision_recorded_group_total?: number;
|
||||
source_correlation_applied_group_total?: number;
|
||||
};
|
||||
items: RecurrenceItem[];
|
||||
};
|
||||
@@ -174,8 +183,11 @@ type RecurrenceWorkItemActionResult = {
|
||||
decision?: string | null;
|
||||
review_status?: string | null;
|
||||
review_record_status?: string | null;
|
||||
apply_status?: string | null;
|
||||
target_incident_id?: string | null;
|
||||
latest_provider_event_id?: string | null;
|
||||
source_event_id?: string | null;
|
||||
source_event_provider_event_id?: string | null;
|
||||
allowed?: boolean | null;
|
||||
executed?: boolean | null;
|
||||
safety_level?: string | null;
|
||||
@@ -223,7 +235,14 @@ type RecurrenceWorkItemActionResult = {
|
||||
};
|
||||
|
||||
type RecurrenceWorkItemActionState = {
|
||||
loading?: "preview" | "dryRun" | "handoff" | "acceptSource" | "rejectSource" | null;
|
||||
loading?:
|
||||
| "preview"
|
||||
| "dryRun"
|
||||
| "handoff"
|
||||
| "acceptSource"
|
||||
| "rejectSource"
|
||||
| "applySource"
|
||||
| null;
|
||||
result?: RecurrenceWorkItemActionResult | null;
|
||||
error?: string | null;
|
||||
};
|
||||
@@ -904,6 +923,19 @@ function staleRatioRecheckStatusKey(status?: string | null) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function sourceApplyStatusKey(status?: string | null) {
|
||||
if (
|
||||
status === "ready_to_apply" ||
|
||||
status === "applied" ||
|
||||
status === "partial" ||
|
||||
status === "record_failed" ||
|
||||
status === "blocked"
|
||||
) {
|
||||
return status;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function formatStaleRatio(value: number) {
|
||||
return `${(value * 100).toFixed(1)}%`;
|
||||
}
|
||||
@@ -948,6 +980,8 @@ function buildWorkItems(
|
||||
recurrenceSummary?.source_correlation_review_group_total ?? 0;
|
||||
const recurrenceSourceReviewRecorded =
|
||||
recurrenceSummary?.source_correlation_decision_recorded_group_total ?? 0;
|
||||
const recurrenceSourceApplied =
|
||||
recurrenceSummary?.source_correlation_applied_group_total ?? 0;
|
||||
const latestRecurrenceOpenItem = recurrenceOpenItems(telemetry.eventRecurrence)[0] ?? null;
|
||||
const driftState = telemetry.driftFingerprintState;
|
||||
const driftFsmKey = driftFsmStateKey(driftState?.fsm_state);
|
||||
@@ -1023,12 +1057,18 @@ function buildWorkItems(
|
||||
t("evidence.recurrenceSourceReviewRecorded", {
|
||||
count: recurrenceSourceReviewRecorded,
|
||||
}),
|
||||
t("evidence.recurrenceSourceApplied", {
|
||||
count: recurrenceSourceApplied,
|
||||
}),
|
||||
]
|
||||
: [
|
||||
t("evidence.recurrenceEmpty"),
|
||||
t("evidence.recurrenceSourceReviewRecorded", {
|
||||
count: recurrenceSourceReviewRecorded,
|
||||
}),
|
||||
t("evidence.recurrenceSourceApplied", {
|
||||
count: recurrenceSourceApplied,
|
||||
}),
|
||||
],
|
||||
href: latestRecurrenceOpenItem?.work_item?.work_item_id
|
||||
? `/awooop/work-items?project_id=${encodeURIComponent(telemetry.eventRecurrence?.project_id ?? "awoooi")}&work_item_id=${encodeURIComponent(latestRecurrenceOpenItem.work_item.work_item_id)}${latestRecurrenceOpenItem.work_item.incident_id ? `&incident_id=${encodeURIComponent(latestRecurrenceOpenItem.work_item.incident_id)}` : ""}`
|
||||
@@ -1342,7 +1382,13 @@ function RecurrenceWorkQueuePanel({
|
||||
const summary = recurrence?.summary;
|
||||
const runWorkItemAction = useCallback(async (
|
||||
workItemId: string,
|
||||
action: "preview" | "dryRun" | "handoff" | "acceptSource" | "rejectSource",
|
||||
action:
|
||||
| "preview"
|
||||
| "dryRun"
|
||||
| "handoff"
|
||||
| "acceptSource"
|
||||
| "rejectSource"
|
||||
| "applySource",
|
||||
targetIncidentId?: string | null
|
||||
) => {
|
||||
setActionState((current) => ({
|
||||
@@ -1381,6 +1427,18 @@ function RecurrenceWorkQueuePanel({
|
||||
},
|
||||
15000
|
||||
);
|
||||
} else if (action === "applySource") {
|
||||
result = await postJson<RecurrenceWorkItemActionResult>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence/source-correlation/apply`,
|
||||
{
|
||||
project_id: projectId,
|
||||
work_item_id: workItemId,
|
||||
reviewer_id: "operator_console",
|
||||
operator_note: "operator_console_apply_source_match",
|
||||
limit: 300,
|
||||
},
|
||||
15000
|
||||
);
|
||||
} else {
|
||||
result = await postJson<RecurrenceWorkItemActionResult>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence/source-correlation/review`,
|
||||
@@ -1407,7 +1465,12 @@ function RecurrenceWorkQueuePanel({
|
||||
error: result ? null : t("actions.failed"),
|
||||
},
|
||||
}));
|
||||
if (result?.history?.recorded || result?.review_record_status === "recorded") {
|
||||
if (
|
||||
result?.history?.recorded ||
|
||||
result?.review_record_status === "recorded" ||
|
||||
result?.apply_status === "applied" ||
|
||||
result?.apply_status === "partial"
|
||||
) {
|
||||
onRecorded();
|
||||
}
|
||||
}, [onRecorded, projectId, t]);
|
||||
@@ -1437,6 +1500,11 @@ function RecurrenceWorkQueuePanel({
|
||||
count: summary?.source_correlation_review_group_total ?? 0,
|
||||
})}
|
||||
</span>
|
||||
<span className="border border-[#9bc7a4] bg-[#f0faf2] px-2 py-0.5 text-[#17602a]">
|
||||
{t("sourceApplied", {
|
||||
count: summary?.source_correlation_applied_group_total ?? 0,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1468,6 +1536,7 @@ function RecurrenceWorkQueuePanel({
|
||||
const handoffStatusKey = recurrenceHandoffStatusKey(actionResult?.handoff_status);
|
||||
const handoffKindKey = recurrenceHandoffKindKey(actionResult?.handoff_kind);
|
||||
const sourceReview = item.source_correlation_review;
|
||||
const sourceApply = item.source_correlation_apply;
|
||||
const isSourceReview = workItem?.kind === "source_correlation_review";
|
||||
const workItemOpen = workItem?.status === "open";
|
||||
const targetIncidentId = firstIncidentId(
|
||||
@@ -1485,6 +1554,13 @@ function RecurrenceWorkQueuePanel({
|
||||
actionResult?.review_status ??
|
||||
sourceReview?.review_status
|
||||
);
|
||||
const sourceApplyStatus = actionResult?.apply_status ?? sourceApply?.apply_status;
|
||||
const sourceApplyStatusKeyValue = sourceApplyStatusKey(sourceApplyStatus);
|
||||
const canApplySource =
|
||||
isSourceReview &&
|
||||
sourceReview?.decision === "accepted" &&
|
||||
Boolean(targetIncidentId) &&
|
||||
sourceApplyStatusKeyValue !== "applied";
|
||||
|
||||
return (
|
||||
<article
|
||||
@@ -1555,6 +1631,19 @@ function RecurrenceWorkQueuePanel({
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
{sourceApply || actionResult?.apply_status ? (
|
||||
<p>
|
||||
{t("sourceApplyStatus", {
|
||||
status: t(
|
||||
`actions.sourceApplyStatuses.${sourceApplyStatusKeyValue}` as never
|
||||
),
|
||||
event:
|
||||
actionResult?.source_event_provider_event_id ??
|
||||
sourceApply?.source_event_provider_event_id ??
|
||||
"--",
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||||
{workItemId ? (
|
||||
@@ -1627,6 +1716,20 @@ function RecurrenceWorkQueuePanel({
|
||||
? t("actions.sourceRejecting")
|
||||
: t("actions.sourceReject")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runWorkItemAction(workItemId, "applySource")}
|
||||
disabled={
|
||||
currentAction?.loading === "applySource" ||
|
||||
!canApplySource
|
||||
}
|
||||
className="inline-flex items-center gap-1.5 border border-[#d8d3c7] bg-white px-2 py-1 text-xs font-semibold text-[#2e2b26] hover:border-[#1f6feb] hover:bg-[#edf4ff] hover:text-[#0f4fa8] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
<GitBranch className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{currentAction?.loading === "applySource"
|
||||
? t("actions.sourceApplying")
|
||||
: t("actions.sourceApply")}
|
||||
</button>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
@@ -1708,6 +1811,16 @@ function RecurrenceWorkQueuePanel({
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
{actionResult.apply_status ? (
|
||||
<p>
|
||||
{t("actions.sourceApplyResult", {
|
||||
status: t(
|
||||
`actions.sourceApplyStatuses.${sourceApplyStatusKeyValue}` as never
|
||||
),
|
||||
event: actionResult.source_event_provider_event_id ?? "--",
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
{actionResult.handoff_status ? (
|
||||
<p>
|
||||
{t("actions.handoffStatus", {
|
||||
|
||||
Reference in New Issue
Block a user