Fix issue create response relation summaries (#8901)

Return blockedBy and blocks relation summaries from issue create paths after blocker relations are synced. Refresh child relation summaries after blockParentUntilDone adds a parent blocker relation.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Nicky Leach 2026-07-02 15:49:18 -07:00 committed by GitHub
parent ec92728536
commit 2c4c110e90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 129 additions and 2 deletions

View File

@ -3200,6 +3200,114 @@ describeEmbeddedPostgres("issueService blockers and dependency wake readiness",
expect(blockedRelations.blockedBy.map((relation) => relation.id)).toEqual([blockerId]);
});
it("returns blocked-by summaries on newly created issues", async () => {
const companyId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Paperclip",
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
requireBoardApprovalForNewAgents: false,
});
const blockerId = randomUUID();
await db.insert(issues).values({
id: blockerId,
companyId,
title: "Blocker",
status: "todo",
priority: "high",
});
const created = await svc.create(companyId, {
title: "Blocked issue",
status: "blocked",
priority: "medium",
blockedByIssueIds: [blockerId],
});
expect(created.blockedBy.map((relation) => relation.id)).toEqual([blockerId]);
expect(created.blockedBy[0]).toEqual(expect.objectContaining({
title: "Blocker",
status: "todo",
priority: "high",
}));
expect(created.blocks).toEqual([]);
});
it("returns blocked-by summaries on newly created child issues", async () => {
const companyId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Paperclip",
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
requireBoardApprovalForNewAgents: false,
});
const parentId = randomUUID();
const blockerId = randomUUID();
await db.insert(issues).values([
{
id: parentId,
companyId,
title: "Parent",
status: "todo",
priority: "medium",
},
{
id: blockerId,
companyId,
title: "Blocker",
status: "todo",
priority: "high",
},
]);
const { issue: child } = await svc.createChild(parentId, {
title: "Blocked child issue",
status: "blocked",
priority: "medium",
blockedByIssueIds: [blockerId],
});
expect(child.parentId).toBe(parentId);
expect(child.blockedBy.map((relation) => relation.id)).toEqual([blockerId]);
expect(child.blocks).toEqual([]);
});
it("returns blocks summaries when child creation blocks the parent", async () => {
const companyId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Paperclip",
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
requireBoardApprovalForNewAgents: false,
});
const parentId = randomUUID();
await db.insert(issues).values({
id: parentId,
companyId,
title: "Parent",
status: "todo",
priority: "medium",
});
const { issue: child } = await svc.createChild(parentId, {
title: "Parent-blocking child",
status: "todo",
priority: "medium",
blockParentUntilDone: true,
});
expect(child.blocks.map((relation) => relation.id)).toEqual([parentId]);
expect(child.blocks[0]).toEqual(expect.objectContaining({
title: "Parent",
status: "todo",
priority: "medium",
}));
expect(child.blockedBy).toEqual([]);
});
it("adds terminal blockers to immediate blocked-by summaries", async () => {
const companyId = randomUUID();
await db.insert(companies).values({

View File

@ -3991,6 +3991,23 @@ export function issueService(db: Db) {
return empty;
}
async function withIssueRelationSummaries<T extends { id: string }>(
companyId: string,
rows: T[],
dbOrTx: DbReader = db,
): Promise<Array<T & IssueRelationSummaryMap>> {
if (rows.length === 0) return [];
const relationMap = await getIssueRelationSummaryMap(
companyId,
rows.map((row) => row.id),
dbOrTx,
);
return rows.map((row) => ({
...row,
...(relationMap.get(row.id) ?? { blockedBy: [], blocks: [] }),
}));
}
async function assertNoBlockingCycles(
companyId: string,
issueId: string,
@ -4960,7 +4977,7 @@ export function issueService(db: Db) {
actorUserId,
...issueData
} = data;
const child = await issueService(db).create(parent.companyId, {
let child = await issueService(db).create(parent.companyId, {
...issueData,
parentId: parent.id,
projectId: issueData.projectId ?? parent.projectId,
@ -4983,6 +5000,7 @@ export function issueService(db: Db) {
[...new Set([...existingBlockers.map((row) => row.blockerIssueId), child.id])],
{ agentId: actorAgentId ?? null, userId: actorUserId ?? null },
);
[child] = await withIssueRelationSummaries(parent.companyId, [child], db);
}
return {
@ -5484,7 +5502,8 @@ export function issueService(db: Db) {
);
}
const [enriched] = await withIssueLabels(tx, [issue]);
return enriched;
const [withRelations] = await withIssueRelationSummaries(companyId, [enriched], tx);
return withRelations;
});
},