Add linked issues row to execution workspace detail

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-03-28 22:21:24 -05:00
parent e3f07aad55
commit d9005405b9
6 changed files with 150 additions and 0 deletions

View file

@ -5,9 +5,11 @@ import {
agents,
companies,
createDb,
executionWorkspaces,
issueComments,
issueInboxArchives,
issues,
projects,
} from "@paperclipai/db";
import {
getEmbeddedPostgresTestSupport,
@ -40,6 +42,8 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => {
await db.delete(issueInboxArchives);
await db.delete(activityLog);
await db.delete(issues);
await db.delete(executionWorkspaces);
await db.delete(projects);
await db.delete(agents);
await db.delete(companies);
});
@ -219,6 +223,86 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => {
expect(result.map((issue) => issue.id)).toEqual([matchedIssueId]);
});
it("filters issues by execution workspace id", async () => {
const companyId = randomUUID();
const projectId = randomUUID();
const targetWorkspaceId = randomUUID();
const otherWorkspaceId = randomUUID();
const linkedIssueId = randomUUID();
const otherLinkedIssueId = randomUUID();
const unlinkedIssueId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Paperclip",
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
requireBoardApprovalForNewAgents: false,
});
await db.insert(projects).values({
id: projectId,
companyId,
name: "Workspace project",
status: "in_progress",
});
await db.insert(executionWorkspaces).values([
{
id: targetWorkspaceId,
companyId,
projectId,
mode: "shared_workspace",
strategyType: "project_primary",
name: "Target workspace",
status: "active",
providerType: "local_fs",
},
{
id: otherWorkspaceId,
companyId,
projectId,
mode: "shared_workspace",
strategyType: "project_primary",
name: "Other workspace",
status: "active",
providerType: "local_fs",
},
]);
await db.insert(issues).values([
{
id: linkedIssueId,
companyId,
projectId,
title: "Linked issue",
status: "todo",
priority: "medium",
executionWorkspaceId: targetWorkspaceId,
},
{
id: otherLinkedIssueId,
companyId,
projectId,
title: "Other linked issue",
status: "todo",
priority: "medium",
executionWorkspaceId: otherWorkspaceId,
},
{
id: unlinkedIssueId,
companyId,
projectId,
title: "Unlinked issue",
status: "todo",
priority: "medium",
},
]);
const result = await svc.list(companyId, { executionWorkspaceId: targetWorkspaceId });
expect(result.map((issue) => issue.id)).toEqual([linkedIssueId]);
});
it("hides archived inbox issues until new external activity arrives", async () => {
const companyId = randomUUID();
const userId = "user-1";

View file

@ -275,6 +275,7 @@ export function issueRoutes(db: Db, storage: StorageService) {
inboxArchivedByUserId,
unreadForUserId,
projectId: req.query.projectId as string | undefined,
executionWorkspaceId: req.query.executionWorkspaceId as string | undefined,
parentId: req.query.parentId as string | undefined,
labelId: req.query.labelId as string | undefined,
originKind: req.query.originKind as string | undefined,

View file

@ -70,6 +70,7 @@ export interface IssueFilters {
inboxArchivedByUserId?: string;
unreadForUserId?: string;
projectId?: string;
executionWorkspaceId?: string;
parentId?: string;
labelId?: string;
originKind?: string;
@ -647,6 +648,9 @@ export function issueService(db: Db) {
conditions.push(unreadForUserCondition(companyId, unreadForUserId));
}
if (filters?.projectId) conditions.push(eq(issues.projectId, filters.projectId));
if (filters?.executionWorkspaceId) {
conditions.push(eq(issues.executionWorkspaceId, filters.executionWorkspaceId));
}
if (filters?.parentId) conditions.push(eq(issues.parentId, filters.parentId));
if (filters?.originKind) conditions.push(eq(issues.originKind, filters.originKind));
if (filters?.originId) conditions.push(eq(issues.originId, filters.originId));