feat(awooop): show source flow in work items
This commit is contained in:
@@ -2167,6 +2167,18 @@
|
|||||||
"stage": "Stage: {stage}",
|
"stage": "Stage: {stage}",
|
||||||
"sourceEvent": "Source event: {event}",
|
"sourceEvent": "Source event: {event}",
|
||||||
"sourceRefs": "Source refs: {refs} (Sentry {sentry} / SignOz {signoz})",
|
"sourceRefs": "Source refs: {refs} (Sentry {sentry} / SignOz {signoz})",
|
||||||
|
"sourceFlow": {
|
||||||
|
"label": "Source flow: {status}",
|
||||||
|
"detail": "refs={refs}; Sentry={sentry}; SignOz={signoz}; event={event}",
|
||||||
|
"statuses": {
|
||||||
|
"applied": "Applied",
|
||||||
|
"reviewed": "Review recorded",
|
||||||
|
"review": "Awaiting match review",
|
||||||
|
"evidence": "Source evidence found",
|
||||||
|
"provider": "Provider received",
|
||||||
|
"waiting": "Waiting for source"
|
||||||
|
}
|
||||||
|
},
|
||||||
"workItem": "Work item: {id}",
|
"workItem": "Work item: {id}",
|
||||||
"repair": "Repair status: {status}",
|
"repair": "Repair status: {status}",
|
||||||
"reason": "Reason: {reason}",
|
"reason": "Reason: {reason}",
|
||||||
|
|||||||
@@ -2168,6 +2168,18 @@
|
|||||||
"stage": "階段:{stage}",
|
"stage": "階段:{stage}",
|
||||||
"sourceEvent": "來源事件:{event}",
|
"sourceEvent": "來源事件:{event}",
|
||||||
"sourceRefs": "來源 refs:{refs}(Sentry {sentry} / SignOz {signoz})",
|
"sourceRefs": "來源 refs:{refs}(Sentry {sentry} / SignOz {signoz})",
|
||||||
|
"sourceFlow": {
|
||||||
|
"label": "來源流程:{status}",
|
||||||
|
"detail": "refs={refs}; Sentry={sentry}; SignOz={signoz}; event={event}",
|
||||||
|
"statuses": {
|
||||||
|
"applied": "已套用",
|
||||||
|
"reviewed": "審核已記錄",
|
||||||
|
"review": "待審核配對",
|
||||||
|
"evidence": "來源證據已到",
|
||||||
|
"provider": "Provider 已接收",
|
||||||
|
"waiting": "等待來源"
|
||||||
|
}
|
||||||
|
},
|
||||||
"workItem": "Work item:{id}",
|
"workItem": "Work item:{id}",
|
||||||
"repair": "修復狀態:{status}",
|
"repair": "修復狀態:{status}",
|
||||||
"reason": "原因:{reason}",
|
"reason": "原因:{reason}",
|
||||||
|
|||||||
@@ -247,6 +247,14 @@ type RecurrenceWorkItemActionState = {
|
|||||||
error?: string | null;
|
error?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RecurrenceSourceFlowStatus =
|
||||||
|
| "applied"
|
||||||
|
| "reviewed"
|
||||||
|
| "review"
|
||||||
|
| "evidence"
|
||||||
|
| "provider"
|
||||||
|
| "waiting";
|
||||||
|
|
||||||
type SloResponse = {
|
type SloResponse = {
|
||||||
adr100?: {
|
adr100?: {
|
||||||
verification_coverage?: {
|
verification_coverage?: {
|
||||||
@@ -936,6 +944,67 @@ function sourceApplyStatusKey(status?: string | null) {
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recurrenceSourceFlowStatus({
|
||||||
|
item,
|
||||||
|
actionResult,
|
||||||
|
sourceApplyStatus,
|
||||||
|
sourceDecision,
|
||||||
|
reviewStatus,
|
||||||
|
}: {
|
||||||
|
item: RecurrenceItem;
|
||||||
|
actionResult?: RecurrenceWorkItemActionResult | null;
|
||||||
|
sourceApplyStatus: string;
|
||||||
|
sourceDecision: string;
|
||||||
|
reviewStatus: string;
|
||||||
|
}): RecurrenceSourceFlowStatus {
|
||||||
|
const hasAppliedLink =
|
||||||
|
sourceApplyStatus === "applied" ||
|
||||||
|
sourceApplyStatus === "partial" ||
|
||||||
|
Boolean(
|
||||||
|
actionResult?.source_event_provider_event_id ||
|
||||||
|
item.source_correlation_apply?.source_event_provider_event_id
|
||||||
|
);
|
||||||
|
if (hasAppliedLink) return "applied";
|
||||||
|
|
||||||
|
const hasRecordedReview =
|
||||||
|
sourceDecision !== "unknown" ||
|
||||||
|
reviewStatus === "recorded" ||
|
||||||
|
reviewStatus === "accepted" ||
|
||||||
|
reviewStatus === "rejected" ||
|
||||||
|
reviewStatus === "needs_more_evidence";
|
||||||
|
if (hasRecordedReview) return "reviewed";
|
||||||
|
|
||||||
|
if (item.work_item?.kind === "source_correlation_review" || item.source_correlation_review) {
|
||||||
|
return "review";
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceRefTotal =
|
||||||
|
(item.source_ref_total ?? 0) +
|
||||||
|
(item.sentry_ref_total ?? 0) +
|
||||||
|
(item.signoz_ref_total ?? 0);
|
||||||
|
if (sourceRefTotal > 0) return "evidence";
|
||||||
|
if (item.latest_provider_event_id) return "provider";
|
||||||
|
return "waiting";
|
||||||
|
}
|
||||||
|
|
||||||
|
function recurrenceSourceFlowClass(status: RecurrenceSourceFlowStatus) {
|
||||||
|
if (status === "applied") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
|
||||||
|
if (status === "reviewed") return "border-[#9bb6d9] bg-[#eef5ff] text-[#1f5b9b]";
|
||||||
|
if (status === "review" || status === "evidence") {
|
||||||
|
return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]";
|
||||||
|
}
|
||||||
|
if (status === "provider") return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
|
||||||
|
return "border-[#e0ddd4] bg-white text-[#77736a]";
|
||||||
|
}
|
||||||
|
|
||||||
|
function recurrenceSourceFlowIcon(status: RecurrenceSourceFlowStatus) {
|
||||||
|
if (status === "applied") return ShieldCheck;
|
||||||
|
if (status === "reviewed") return GitBranch;
|
||||||
|
if (status === "review" || status === "evidence") return SearchCheck;
|
||||||
|
if (status === "provider") return Network;
|
||||||
|
return Database;
|
||||||
|
}
|
||||||
|
|
||||||
function formatStaleRatio(value: number) {
|
function formatStaleRatio(value: number) {
|
||||||
return `${(value * 100).toFixed(1)}%`;
|
return `${(value * 100).toFixed(1)}%`;
|
||||||
}
|
}
|
||||||
@@ -1556,6 +1625,19 @@ function RecurrenceWorkQueuePanel({
|
|||||||
);
|
);
|
||||||
const sourceApplyStatus = actionResult?.apply_status ?? sourceApply?.apply_status;
|
const sourceApplyStatus = actionResult?.apply_status ?? sourceApply?.apply_status;
|
||||||
const sourceApplyStatusKeyValue = sourceApplyStatusKey(sourceApplyStatus);
|
const sourceApplyStatusKeyValue = sourceApplyStatusKey(sourceApplyStatus);
|
||||||
|
const sourceFlowStatus = recurrenceSourceFlowStatus({
|
||||||
|
item,
|
||||||
|
actionResult,
|
||||||
|
sourceApplyStatus: sourceApplyStatusKeyValue,
|
||||||
|
sourceDecision: sourceDecisionKey,
|
||||||
|
reviewStatus: reviewRecordStatusKey,
|
||||||
|
});
|
||||||
|
const SourceFlowIcon = recurrenceSourceFlowIcon(sourceFlowStatus);
|
||||||
|
const sourceFlowEvent =
|
||||||
|
actionResult?.source_event_provider_event_id ??
|
||||||
|
sourceApply?.source_event_provider_event_id ??
|
||||||
|
item.latest_provider_event_id ??
|
||||||
|
"--";
|
||||||
const canApplySource =
|
const canApplySource =
|
||||||
isSourceReview &&
|
isSourceReview &&
|
||||||
sourceReview?.decision === "accepted" &&
|
sourceReview?.decision === "accepted" &&
|
||||||
@@ -1605,6 +1687,35 @@ function RecurrenceWorkQueuePanel({
|
|||||||
signoz: item.signoz_ref_total ?? 0,
|
signoz: item.signoz_ref_total ?? 0,
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
<div className="grid gap-1">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"inline-flex w-fit items-center gap-1.5 border px-2 py-1 font-semibold",
|
||||||
|
recurrenceSourceFlowClass(sourceFlowStatus)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SourceFlowIcon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
|
{t("sourceFlow.label", {
|
||||||
|
status: t(`sourceFlow.statuses.${sourceFlowStatus}` as never),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<p
|
||||||
|
className="truncate font-mono text-xs text-[#77736a]"
|
||||||
|
title={t("sourceFlow.detail", {
|
||||||
|
refs: item.source_ref_total ?? 0,
|
||||||
|
sentry: item.sentry_ref_total ?? 0,
|
||||||
|
signoz: item.signoz_ref_total ?? 0,
|
||||||
|
event: sourceFlowEvent,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{t("sourceFlow.detail", {
|
||||||
|
refs: item.source_ref_total ?? 0,
|
||||||
|
sentry: item.sentry_ref_total ?? 0,
|
||||||
|
signoz: item.signoz_ref_total ?? 0,
|
||||||
|
event: sourceFlowEvent,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<p>{t("workItem", { id: workItem?.work_item_id ?? "--" })}</p>
|
<p>{t("workItem", { id: workItem?.work_item_id ?? "--" })}</p>
|
||||||
<p>
|
<p>
|
||||||
{t("repair", {
|
{t("repair", {
|
||||||
|
|||||||
Reference in New Issue
Block a user