diff --git a/src/components/admin/review-row.tsx b/src/components/admin/review-row.tsx index 0da9f62..155e131 100644 --- a/src/components/admin/review-row.tsx +++ b/src/components/admin/review-row.tsx @@ -644,6 +644,10 @@ export function ReviewRow({ // time (decided_at is unset until approve/dismiss). const rowTimestamp = review.decided_at ?? review.created_at const relativeTime = formatDateRelative(rowTimestamp, rowTimestamp ?? "") + // Who made the decision ("admin" from the UI, "stakwork" from workflows). + // Trim guards legacy reviews decided before decided_by was reliable (""). + const decider = + review.status !== "pending" ? review.decided_by?.trim() : undefined const direction = useMemo( () => extractDirection(review.action_name, review.action_payload), @@ -851,6 +855,12 @@ export function ReviewRow({ {review.run_ref_id && Run #{review.run_ref_id.slice(-5)}} {review.run_ref_id && ·} {relativeTime} + {decider && ( + <> + · + by {decider} + + )}
e.stopPropagation()}> diff --git a/src/lib/__tests__/reviews.test.tsx b/src/lib/__tests__/reviews.test.tsx index 730534d..e936dd6 100644 --- a/src/lib/__tests__/reviews.test.tsx +++ b/src/lib/__tests__/reviews.test.tsx @@ -139,6 +139,41 @@ describe("ReviewRow", () => { expect(badge!.className).toContain("bg-red-500") }) + // ── Decider attribution ───────────────────────────────────────────────────── + + it("shows who decided on decided reviews", () => { + const { container } = render( + + ) + const decider = container.querySelector("[data-testid='review-decider']") + expect(decider).toBeTruthy() + expect(decider!.textContent).toBe("by stakwork") + }) + + it("hides decider on pending reviews and on legacy blank decided_by", () => { + const pending = render( + + ) + expect(pending.container.querySelector("[data-testid='review-decider']")).toBeNull() + + const legacy = render( + + ) + expect(legacy.container.querySelector("[data-testid='review-decider']")).toBeNull() + }) + // ── Approve / Dismiss only for pending ───────────────────────────────────── it("shows Approve and Dismiss buttons only for pending rows", () => { diff --git a/src/lib/graph-api.ts b/src/lib/graph-api.ts index 3e3154f..a0f2a46 100644 --- a/src/lib/graph-api.ts +++ b/src/lib/graph-api.ts @@ -1178,7 +1178,12 @@ export async function approveReview( // cancel_active_runs: an admin deciding here makes any in-flight // human-review workflow moot, so ask the backend to stop it. Workflows // deciding their own reviews omit this — cancelling would kill their run. - const body: Record = { cancel_active_runs: true } + // decided_by distinguishes admin-UI decisions from workflow ones on the + // review node (workflows send their own identity, e.g. "stakwork"). + const body: Record = { + cancel_active_runs: true, + decided_by: "admin", + } if (overridePayload) body.override_payload = overridePayload return api.post( `/v2/reviews/${refId}/approve`, @@ -1204,10 +1209,11 @@ export async function dismissReview( } return { status: "Success", review } } - // cancel_active_runs: same admin-decided semantics as approveReview above. + // cancel_active_runs + decided_by: same admin-decided semantics as + // approveReview above. return api.post( `/v2/reviews/${refId}/dismiss`, - { reason, cancel_active_runs: true }, + { reason, cancel_active_runs: true, decided_by: "admin" }, undefined, signal )