Align recovery resolution UI contract

This commit is contained in:
Dotta committed 2026-05-09 23:49:30 -05:00
1 parent 4614547f3f
commit c74fab4462
3 files changed
+20 -5

No files matched your search

+1 -1
View File
@@ -212,7 +212,7 @@ const RESOLVE_ISSUE_RECOVERY_ACTION_OUTCOMES = [
export const resolveIssueRecoveryActionSchema = z.object({
actionId: z.string().uuid().optional(),
outcome: z.enum(RESOLVE_ISSUE_RECOVERY_ACTION_OUTCOMES),
sourceIssueStatus: z.enum(["done", "in_review", "blocked"]).optional().nullable(),
sourceIssueStatus: z.enum(["done", "in_review", "blocked"]).optional(),
resolutionNote: multilineTextSchema.optional().nullable(),
}).strict().superRefine((value, ctx) => {
if (value.outcome === "restored") {
+1 -1
View File
@@ -106,7 +106,7 @@ export const issuesApi = {
data: {
actionId?: string;
outcome: IssueRecoveryActionOutcome;
sourceIssueStatus?: "done" | "in_review" | "blocked" | null;
sourceIssueStatus?: "done" | "in_review" | "blocked";
resolutionNote?: string | null;
},
) => api.post<ResolveRecoveryActionResponse>(`/issues/${id}/recovery-actions/resolve`, data),
+18 -3
View File
@@ -8,7 +8,7 @@ import { approvalsApi } from "../api/approvals";
import { activityApi, type RunForIssue } from "../api/activity";
import { heartbeatsApi, type ActiveRunForIssue, type LiveRunForIssue } from "../api/heartbeats";
import { instanceSettingsApi } from "../api/instanceSettings";
import { accessApi } from "../api/access";
import { accessApi, type CurrentBoardAccess } from "../api/access";
import { agentsApi } from "../api/agents";
import { authApi } from "../api/auth";
import { projectsApi } from "../api/projects";
@@ -212,6 +212,20 @@ function treeControlPreviewErrorCopy(error: unknown): string {
return error instanceof Error ? error.message : "Unable to load preview.";
}
function canBoardResolveRecoveryAction(
companyId: string | null | undefined,
boardAccess: CurrentBoardAccess | undefined,
) {
if (!companyId || !boardAccess) return false;
if (boardAccess.source === "local_implicit" || boardAccess.isInstanceAdmin) return true;
const membership = boardAccess.memberships?.find(
(item) => item.companyId === companyId && item.status === "active",
);
if (!membership) return boardAccess.companyIds.includes(companyId) && !boardAccess.memberships;
return membership.membershipRole !== "viewer" && membership.membershipRole !== null;
}
function resolveRunningIssueRun(
activeRun: ActiveRunForIssue | null | undefined,
liveRuns: readonly LiveRunForIssue[] | undefined,
@@ -1391,6 +1405,7 @@ export function IssueDetail() {
selectedCompanyId
&& boardAccess?.companyIds?.includes(selectedCompanyId),
);
const canResolveBoardRecoveryAction = canBoardResolveRecoveryAction(selectedCompanyId, boardAccess);
const { data: feedbackVotes } = useQuery({
queryKey: queryKeys.issues.feedbackVotes(issueId!),
queryFn: () => issuesApi.listFeedbackVotes(issueId!),
@@ -1730,7 +1745,7 @@ export function IssueDetail() {
mutationFn: (data: {
actionId?: string;
outcome: IssueRecoveryActionOutcome;
sourceIssueStatus?: "done" | "in_review" | "blocked" | null;
sourceIssueStatus?: "done" | "in_review" | "blocked";
resolutionNote?: string | null;
}) => issuesApi.resolveRecoveryAction(issueId!, data),
onSuccess: ({ issue: nextIssue }) => {
@@ -3872,7 +3887,7 @@ export function IssueDetail() {
successfulRunHandoff={issue.successfulRunHandoff ?? null}
recoveryAction={issue.activeRecoveryAction ?? null}
onResolveRecoveryAction={handleResolveRecoveryAction}
canCancelRecoveryAction={canManageTreeControl}
canCancelRecoveryAction={canResolveBoardRecoveryAction}
legacyRecoverySourceIssue={legacyRecoverySourceIssue}
comments={threadComments}
locallyQueuedCommentRunIds={locallyQueuedCommentRunIds}