feat(awooop): record recurrence handoff proposals
This commit is contained in:
@@ -1846,13 +1846,17 @@
|
||||
"previewing": "Previewing",
|
||||
"dryRun": "Dry-run",
|
||||
"dryRunning": "Dry-running",
|
||||
"failed": "The safe preview / dry-run API did not respond, so the next step cannot be claimed.",
|
||||
"handoff": "Handoff",
|
||||
"handoffing": "Handing off",
|
||||
"failed": "The safe preview / dry-run / handoff API did not respond, so the next step cannot be claimed.",
|
||||
"allowed": "Safety gate passed",
|
||||
"blocked": "Safety gate blocked",
|
||||
"mode": "Mode: {mode}",
|
||||
"previewResult": "Result: {result}",
|
||||
"writes": "Writes: incident={incident}; autoRepair={autoRepair}; ticket={ticket}",
|
||||
"history": "Dry-run stored: {recorded}",
|
||||
"handoffStatus": "Handoff: {kind} / {status}",
|
||||
"externalTicket": "External ticket created: {created}",
|
||||
"ticket": "Ticket preview: {title}",
|
||||
"modes": {
|
||||
"auto": "Auto select",
|
||||
@@ -1862,6 +1866,18 @@
|
||||
"observe": "Observe",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"handoffKinds": {
|
||||
"ticket_proposal": "Ticket proposal",
|
||||
"manual_review": "Manual review",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"handoffStatuses": {
|
||||
"ready_to_record": "Ready to record",
|
||||
"recorded": "Recorded",
|
||||
"record_failed": "Record failed",
|
||||
"blocked": "Blocked",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"previews": {
|
||||
"ticket_preview_ready": "Ticket preview ready",
|
||||
"reverify_preview_ready": "Reverify preview ready",
|
||||
|
||||
@@ -1847,13 +1847,17 @@
|
||||
"previewing": "預覽中",
|
||||
"dryRun": "乾跑",
|
||||
"dryRunning": "乾跑中",
|
||||
"failed": "安全預覽 / 乾跑 API 未回應,不能判定下一步。",
|
||||
"handoff": "交接",
|
||||
"handoffing": "交接中",
|
||||
"failed": "安全預覽 / 乾跑 / 交接 API 未回應,不能判定下一步。",
|
||||
"allowed": "安全閘門通過",
|
||||
"blocked": "安全閘門阻塞",
|
||||
"mode": "模式:{mode}",
|
||||
"previewResult": "結果:{result}",
|
||||
"writes": "寫入:incident={incident};autoRepair={autoRepair};ticket={ticket}",
|
||||
"history": "試跑入庫:{recorded}",
|
||||
"handoffStatus": "交接:{kind} / {status}",
|
||||
"externalTicket": "外部 Ticket 建立:{created}",
|
||||
"ticket": "Ticket 預覽:{title}",
|
||||
"modes": {
|
||||
"auto": "自動選擇",
|
||||
@@ -1863,6 +1867,18 @@
|
||||
"observe": "觀察",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"handoffKinds": {
|
||||
"ticket_proposal": "Ticket 提案",
|
||||
"manual_review": "人工覆核",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"handoffStatuses": {
|
||||
"ready_to_record": "待寫入歷史",
|
||||
"recorded": "已寫入歷史",
|
||||
"record_failed": "寫入失敗",
|
||||
"blocked": "已阻塞",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"previews": {
|
||||
"ticket_preview_ready": "Ticket 預覽已就緒",
|
||||
"reverify_preview_ready": "重新驗證預覽已就緒",
|
||||
|
||||
@@ -110,12 +110,16 @@ type RecurrenceWorkItemActionResult = {
|
||||
incident_id?: string | null;
|
||||
mode?: string | null;
|
||||
requested_mode?: string | null;
|
||||
handoff_kind?: string | null;
|
||||
handoff_status?: string | null;
|
||||
handoff_owner?: string | null;
|
||||
allowed?: boolean | null;
|
||||
executed?: boolean | null;
|
||||
safety_level?: string | null;
|
||||
writes_incident_state?: boolean | null;
|
||||
writes_auto_repair_result?: boolean | null;
|
||||
writes_ticket?: boolean | null;
|
||||
creates_external_ticket?: boolean | null;
|
||||
verification_result_preview?: string | null;
|
||||
next_step?: string | null;
|
||||
checks?: Array<{ name?: string | null; passed?: boolean | null; detail?: string | null }>;
|
||||
@@ -153,7 +157,7 @@ type RecurrenceWorkItemActionResult = {
|
||||
};
|
||||
|
||||
type RecurrenceWorkItemActionState = {
|
||||
loading?: "preview" | "dryRun" | null;
|
||||
loading?: "preview" | "dryRun" | "handoff" | null;
|
||||
result?: RecurrenceWorkItemActionResult | null;
|
||||
error?: string | null;
|
||||
};
|
||||
@@ -331,6 +335,25 @@ function recurrencePreviewKey(preview?: string | null) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function recurrenceHandoffStatusKey(status?: string | null) {
|
||||
if (
|
||||
status === "ready_to_record" ||
|
||||
status === "recorded" ||
|
||||
status === "record_failed" ||
|
||||
status === "blocked"
|
||||
) {
|
||||
return status;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function recurrenceHandoffKindKey(kind?: string | null) {
|
||||
if (kind === "ticket_proposal" || kind === "manual_review") {
|
||||
return kind;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function buildWorkItems(
|
||||
telemetry: Telemetry,
|
||||
t: ReturnType<typeof useTranslations>
|
||||
@@ -653,7 +676,7 @@ function RecurrenceWorkQueuePanel({
|
||||
const summary = recurrence?.summary;
|
||||
const runWorkItemAction = useCallback(async (
|
||||
workItemId: string,
|
||||
action: "preview" | "dryRun"
|
||||
action: "preview" | "dryRun" | "handoff"
|
||||
) => {
|
||||
setActionState((current) => ({
|
||||
...current,
|
||||
@@ -662,21 +685,36 @@ function RecurrenceWorkQueuePanel({
|
||||
|
||||
const encodedProjectId = encodeURIComponent(projectId);
|
||||
const encodedWorkItemId = encodeURIComponent(workItemId);
|
||||
const result = action === "preview"
|
||||
? await fetchJson<RecurrenceWorkItemActionResult>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/preview?project_id=${encodedProjectId}&work_item_id=${encodedWorkItemId}`,
|
||||
12000
|
||||
)
|
||||
: await postJson<RecurrenceWorkItemActionResult>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/dry-run`,
|
||||
{
|
||||
project_id: projectId,
|
||||
work_item_id: workItemId,
|
||||
mode: "auto",
|
||||
limit: 300,
|
||||
},
|
||||
15000
|
||||
);
|
||||
let result: RecurrenceWorkItemActionResult | null = null;
|
||||
if (action === "preview") {
|
||||
result = await fetchJson<RecurrenceWorkItemActionResult>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/preview?project_id=${encodedProjectId}&work_item_id=${encodedWorkItemId}`,
|
||||
12000
|
||||
);
|
||||
} else if (action === "dryRun") {
|
||||
result = await postJson<RecurrenceWorkItemActionResult>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/dry-run`,
|
||||
{
|
||||
project_id: projectId,
|
||||
work_item_id: workItemId,
|
||||
mode: "auto",
|
||||
limit: 300,
|
||||
},
|
||||
15000
|
||||
);
|
||||
} else {
|
||||
result = await postJson<RecurrenceWorkItemActionResult>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence/work-item/handoff`,
|
||||
{
|
||||
project_id: projectId,
|
||||
work_item_id: workItemId,
|
||||
mode: "auto",
|
||||
handoff_kind: "ticket_proposal",
|
||||
limit: 300,
|
||||
},
|
||||
15000
|
||||
);
|
||||
}
|
||||
|
||||
setActionState((current) => ({
|
||||
...current,
|
||||
@@ -736,6 +774,8 @@ function RecurrenceWorkQueuePanel({
|
||||
const actionAllowed = actionResult?.allowed === true;
|
||||
const actionModeKey = recurrenceActionModeKey(actionResult?.mode);
|
||||
const previewKey = recurrencePreviewKey(actionResult?.verification_result_preview);
|
||||
const handoffStatusKey = recurrenceHandoffStatusKey(actionResult?.handoff_status);
|
||||
const handoffKindKey = recurrenceHandoffKindKey(actionResult?.handoff_kind);
|
||||
|
||||
return (
|
||||
<article
|
||||
@@ -802,6 +842,17 @@ function RecurrenceWorkQueuePanel({
|
||||
? t("actions.dryRunning")
|
||||
: t("actions.dryRun")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runWorkItemAction(workItemId, "handoff")}
|
||||
disabled={currentAction?.loading === "handoff"}
|
||||
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-[#9bc7a4] hover:bg-[#f0faf2] hover:text-[#17602a] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
<GitBranch className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{currentAction?.loading === "handoff"
|
||||
? t("actions.handoffing")
|
||||
: t("actions.handoff")}
|
||||
</button>
|
||||
</>
|
||||
) : null}
|
||||
{runHref ? (
|
||||
@@ -863,6 +914,21 @@ function RecurrenceWorkQueuePanel({
|
||||
recorded: String(actionResult.history?.recorded ?? false),
|
||||
})}
|
||||
</p>
|
||||
{actionResult.handoff_status ? (
|
||||
<p>
|
||||
{t("actions.handoffStatus", {
|
||||
status: t(`actions.handoffStatuses.${handoffStatusKey}` as never),
|
||||
kind: t(`actions.handoffKinds.${handoffKindKey}` as never),
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
{actionResult.creates_external_ticket !== undefined ? (
|
||||
<p>
|
||||
{t("actions.externalTicket", {
|
||||
created: String(actionResult.creates_external_ticket ?? false),
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
{actionResult.ticket_preview?.title ? (
|
||||
<p className="truncate">
|
||||
{t("actions.ticket", {
|
||||
|
||||
Reference in New Issue
Block a user