diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 5844aed87d9..27c49949af8 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -422,11 +422,11 @@ type RecoveryActionsLister = { ) => Promise>>; }; -async function annotateRelationSummariesWithRecoveryActions( +async function relationRecoveryActionMap( recoveryActionsSvc: RecoveryActionsLister, companyId: string, relations: { blockedBy: IssueRelationIssueSummary[]; blocks: IssueRelationIssueSummary[] }, -) { +): Promise>> { const candidates: IssueRelationIssueSummary[] = []; const visit = (summary: IssueRelationIssueSummary) => { candidates.push(summary); @@ -436,13 +436,24 @@ async function annotateRelationSummariesWithRecoveryActions( }; for (const blocker of relations.blockedBy) visit(blocker); for (const blocking of relations.blocks) visit(blocking); - if (candidates.length === 0) return; + if (candidates.length === 0) return new Map(); const ids = [...new Set(candidates.map((summary) => summary.id))]; - const map = await recoveryActionsSvc.listActiveForIssues(companyId, ids); - for (const summary of candidates) { - const action = map.get(summary.id) ?? null; - if (action) summary.activeRecoveryAction = action; - } + return recoveryActionsSvc.listActiveForIssues(companyId, ids); +} + +function withRecoveryActionsOnRelationSummaries( + relations: { blockedBy: IssueRelationIssueSummary[]; blocks: IssueRelationIssueSummary[] }, + recoveryActionByIssueId: Map>, +) { + const augment = (summary: IssueRelationIssueSummary): IssueRelationIssueSummary => ({ + ...summary, + activeRecoveryAction: recoveryActionByIssueId.get(summary.id) ?? summary.activeRecoveryAction ?? null, + terminalBlockers: summary.terminalBlockers?.map(augment), + }); + return { + blockedBy: relations.blockedBy.map(augment), + blocks: relations.blocks.map(augment), + }; } const ACTIVE_REVIEW_APPROVAL_STATUSES = new Set(["pending", "revision_requested"]); @@ -1599,11 +1610,15 @@ export function issueRoutes( currentExecutionWorkspacePromise, recoveryActionsSvc.getActiveForIssue(issue.companyId, issue.id), ]); - await annotateRelationSummariesWithRecoveryActions( + const recoveryActionsByRelationIssue = await relationRecoveryActionMap( recoveryActionsSvc, issue.companyId, relations, ); + const relationsWithRecoveryActions = withRecoveryActionsOnRelationSummaries( + relations, + recoveryActionsByRelationIssue, + ); res.json({ issue: { @@ -1621,8 +1636,8 @@ export function issueRoutes( projectId: issue.projectId, goalId: goal?.id ?? issue.goalId, parentId: issue.parentId, - blockedBy: relations.blockedBy, - blocks: relations.blocks, + blockedBy: relationsWithRecoveryActions.blockedBy, + blocks: relationsWithRecoveryActions.blocks, assigneeAgentId: issue.assigneeAgentId, assigneeUserId: issue.assigneeUserId, originKind: issue.originKind, @@ -1713,11 +1728,15 @@ export function issueRoutes( svc.getCurrentScheduledRetry(issue.id), recoveryActionsSvc.getActiveForIssue(issue.companyId, issue.id), ]); - await annotateRelationSummariesWithRecoveryActions( + const recoveryActionsByRelationIssue = await relationRecoveryActionMap( recoveryActionsSvc, issue.companyId, relations, ); + const relationsWithRecoveryActions = withRecoveryActionsOnRelationSummaries( + relations, + recoveryActionsByRelationIssue, + ); const mentionedProjects = mentionedProjectIds.length > 0 ? await projectsSvc.listByIds(issue.companyId, mentionedProjectIds) : []; @@ -1734,8 +1753,8 @@ export function issueRoutes( successfulRunHandoff: successfulRunHandoffStates.get(issue.id) ?? null, scheduledRetry, activeRecoveryAction, - blockedBy: relations.blockedBy, - blocks: relations.blocks, + blockedBy: relationsWithRecoveryActions.blockedBy, + blocks: relationsWithRecoveryActions.blocks, relatedWork: referenceSummary, referencedIssueIdentifiers: referenceSummary.outbound.map((item) => item.issue.identifier ?? item.issue.id), ...documentPayload, diff --git a/server/src/services/recovery/service.ts b/server/src/services/recovery/service.ts index 7af5100011a..20712ed2ca9 100644 --- a/server/src/services/recovery/service.ts +++ b/server/src/services/recovery/service.ts @@ -2193,7 +2193,7 @@ export function recoveryService(db: Db, deps: { enqueueWakeup: RecoveryWakeup }) } async function collectIssueGraphLivenessFindings() { - const issueRows = await db + const issueRowsPromise = Promise.resolve(db .select({ id: issues.id, companyId: issues.companyId, @@ -2218,10 +2218,10 @@ export function recoveryService(db: Db, deps: { enqueueWakeup: RecoveryWakeup }) isNull(issues.hiddenAt), notInArray(issues.originKind, [RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation]), ), - ); - const issueIdsUnderAnalysis = issueRows.map((row) => row.id); + )); const [ + issueRows, relationRows, agentRows, activeRunRows, @@ -2232,6 +2232,7 @@ export function recoveryService(db: Db, deps: { enqueueWakeup: RecoveryWakeup }) recoveryIssueRows, recoveryActionRows, ] = await Promise.all([ + issueRowsPromise, db .select({ companyId: issueRelations.companyId, @@ -2321,21 +2322,24 @@ export function recoveryService(db: Db, deps: { enqueueWakeup: RecoveryWakeup }) notInArray(issues.status, ["done", "cancelled"]), ), ), - issueIdsUnderAnalysis.length === 0 - ? Promise.resolve([]) - : db - .select({ - companyId: issueRecoveryActions.companyId, - issueId: issueRecoveryActions.sourceIssueId, - status: issueRecoveryActions.status, - }) - .from(issueRecoveryActions) - .where( - and( - inArray(issueRecoveryActions.status, ["active", "escalated"]), - inArray(issueRecoveryActions.sourceIssueId, issueIdsUnderAnalysis), - ), - ), + issueRowsPromise.then((rows) => { + const issueIdsUnderAnalysis = rows.map((row) => row.id); + return issueIdsUnderAnalysis.length === 0 + ? [] + : db + .select({ + companyId: issueRecoveryActions.companyId, + issueId: issueRecoveryActions.sourceIssueId, + status: issueRecoveryActions.status, + }) + .from(issueRecoveryActions) + .where( + and( + inArray(issueRecoveryActions.status, ["active", "escalated"]), + inArray(issueRecoveryActions.sourceIssueId, issueIdsUnderAnalysis), + ), + ); + }), ]); const openRecoveryIssues = recoveryIssueRows.flatMap((row) => {