Implement execution workspaces and work products
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
companyMemberships,
|
||||
goals,
|
||||
heartbeatRuns,
|
||||
executionWorkspaces,
|
||||
issueAttachments,
|
||||
issueLabels,
|
||||
issueComments,
|
||||
@@ -353,6 +354,40 @@ export function issueService(db: Db) {
|
||||
}
|
||||
}
|
||||
|
||||
async function assertValidProjectWorkspace(companyId: string, projectId: string | null | undefined, projectWorkspaceId: string) {
|
||||
const workspace = await db
|
||||
.select({
|
||||
id: projectWorkspaces.id,
|
||||
companyId: projectWorkspaces.companyId,
|
||||
projectId: projectWorkspaces.projectId,
|
||||
})
|
||||
.from(projectWorkspaces)
|
||||
.where(eq(projectWorkspaces.id, projectWorkspaceId))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
if (!workspace) throw notFound("Project workspace not found");
|
||||
if (workspace.companyId !== companyId) throw unprocessable("Project workspace must belong to same company");
|
||||
if (projectId && workspace.projectId !== projectId) {
|
||||
throw unprocessable("Project workspace must belong to the selected project");
|
||||
}
|
||||
}
|
||||
|
||||
async function assertValidExecutionWorkspace(companyId: string, projectId: string | null | undefined, executionWorkspaceId: string) {
|
||||
const workspace = await db
|
||||
.select({
|
||||
id: executionWorkspaces.id,
|
||||
companyId: executionWorkspaces.companyId,
|
||||
projectId: executionWorkspaces.projectId,
|
||||
})
|
||||
.from(executionWorkspaces)
|
||||
.where(eq(executionWorkspaces.id, executionWorkspaceId))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
if (!workspace) throw notFound("Execution workspace not found");
|
||||
if (workspace.companyId !== companyId) throw unprocessable("Execution workspace must belong to same company");
|
||||
if (projectId && workspace.projectId !== projectId) {
|
||||
throw unprocessable("Execution workspace must belong to the selected project");
|
||||
}
|
||||
}
|
||||
|
||||
async function assertValidLabelIds(companyId: string, labelIds: string[], dbOrTx: any = db) {
|
||||
if (labelIds.length === 0) return;
|
||||
const existing = await dbOrTx
|
||||
@@ -647,6 +682,12 @@ export function issueService(db: Db) {
|
||||
if (data.assigneeUserId) {
|
||||
await assertAssignableUser(companyId, data.assigneeUserId);
|
||||
}
|
||||
if (data.projectWorkspaceId) {
|
||||
await assertValidProjectWorkspace(companyId, data.projectId, data.projectWorkspaceId);
|
||||
}
|
||||
if (data.executionWorkspaceId) {
|
||||
await assertValidExecutionWorkspace(companyId, data.projectId, data.executionWorkspaceId);
|
||||
}
|
||||
if (data.status === "in_progress" && !data.assigneeAgentId && !data.assigneeUserId) {
|
||||
throw unprocessable("in_progress issues require an assignee");
|
||||
}
|
||||
@@ -665,6 +706,26 @@ export function issueService(db: Db) {
|
||||
parseProjectExecutionWorkspacePolicy(project?.executionWorkspacePolicy),
|
||||
) as Record<string, unknown> | null;
|
||||
}
|
||||
let projectWorkspaceId = issueData.projectWorkspaceId ?? null;
|
||||
if (!projectWorkspaceId && issueData.projectId) {
|
||||
const project = await tx
|
||||
.select({
|
||||
executionWorkspacePolicy: projects.executionWorkspacePolicy,
|
||||
})
|
||||
.from(projects)
|
||||
.where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId)))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
const projectPolicy = parseProjectExecutionWorkspacePolicy(project?.executionWorkspacePolicy);
|
||||
projectWorkspaceId = projectPolicy?.defaultProjectWorkspaceId ?? null;
|
||||
if (!projectWorkspaceId) {
|
||||
projectWorkspaceId = await tx
|
||||
.select({ id: projectWorkspaces.id })
|
||||
.from(projectWorkspaces)
|
||||
.where(and(eq(projectWorkspaces.projectId, issueData.projectId), eq(projectWorkspaces.companyId, companyId)))
|
||||
.orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id))
|
||||
.then((rows) => rows[0]?.id ?? null);
|
||||
}
|
||||
}
|
||||
const [company] = await tx
|
||||
.update(companies)
|
||||
.set({ issueCounter: sql`${companies.issueCounter} + 1` })
|
||||
@@ -681,6 +742,7 @@ export function issueService(db: Db) {
|
||||
goalId: issueData.goalId,
|
||||
defaultGoalId: defaultCompanyGoal?.id ?? null,
|
||||
}),
|
||||
...(projectWorkspaceId ? { projectWorkspaceId } : {}),
|
||||
...(executionWorkspaceSettings ? { executionWorkspaceSettings } : {}),
|
||||
companyId,
|
||||
issueNumber,
|
||||
@@ -741,6 +803,17 @@ export function issueService(db: Db) {
|
||||
if (issueData.assigneeUserId) {
|
||||
await assertAssignableUser(existing.companyId, issueData.assigneeUserId);
|
||||
}
|
||||
const nextProjectId = issueData.projectId !== undefined ? issueData.projectId : existing.projectId;
|
||||
const nextProjectWorkspaceId =
|
||||
issueData.projectWorkspaceId !== undefined ? issueData.projectWorkspaceId : existing.projectWorkspaceId;
|
||||
const nextExecutionWorkspaceId =
|
||||
issueData.executionWorkspaceId !== undefined ? issueData.executionWorkspaceId : existing.executionWorkspaceId;
|
||||
if (nextProjectWorkspaceId) {
|
||||
await assertValidProjectWorkspace(existing.companyId, nextProjectId, nextProjectWorkspaceId);
|
||||
}
|
||||
if (nextExecutionWorkspaceId) {
|
||||
await assertValidExecutionWorkspace(existing.companyId, nextProjectId, nextExecutionWorkspaceId);
|
||||
}
|
||||
|
||||
applyStatusSideEffects(issueData.status, patch);
|
||||
if (issueData.status && issueData.status !== "done") {
|
||||
|
||||
Reference in New Issue
Block a user