feat: Enhance ease of use of workflow editor

This commit is contained in:
Dries Augustyns
2026-04-24 11:56:43 +02:00
parent bdc9b30ef2
commit c910d20bb7
6 changed files with 709 additions and 918 deletions
+6 -1
View File
@@ -219,12 +219,17 @@ export class Workflows {
const auth = res.locals.auth;
const workflowId = req.params.id;
const stepId = req.params.stepId;
const splice = req.query.splice === 'true';
if (!workflowId || !stepId) {
return res.status(400).json({error: 'Workflow ID and Step ID are required'});
}
await WorkflowService.deleteStep(auth.projectId!, workflowId, stepId);
if (splice) {
await WorkflowService.spliceStep(auth.projectId!, workflowId, stepId);
} else {
await WorkflowService.deleteStep(auth.projectId!, workflowId, stepId);
}
return res.status(204).send();
}
+60
View File
@@ -576,6 +576,66 @@ export class WorkflowService {
});
}
/**
* Splice a step out of the flow: re-wire its parent(s) directly to its child,
* then delete only the step itself. Not allowed for CONDITION or TRIGGER steps.
*/
public static async spliceStep(projectId: string, workflowId: string, stepId: string): Promise<void> {
await this.get(projectId, workflowId);
const step = await prisma.workflowStep.findUnique({
where: {id: stepId},
include: {
outgoingTransitions: true,
incomingTransitions: true,
},
});
if (step?.workflowId !== workflowId) {
throw new HttpException(404, 'Workflow step not found');
}
if (step.type === 'TRIGGER') {
throw new HttpException(400, 'Cannot remove the trigger step.');
}
if (step.type === 'CONDITION') {
throw new HttpException(400, 'Cannot splice a condition step out of the flow.');
}
// Check for active executions on this step
const executionsOnStep = await prisma.workflowExecution.count({
where: {
workflowId,
currentStepId: stepId,
status: {in: [WorkflowExecutionStatus.RUNNING, WorkflowExecutionStatus.WAITING]},
},
});
if (executionsOnStep > 0) {
throw new HttpException(
409,
`Cannot remove step "${step.name}" while ${executionsOnStep} execution(s) are currently on it. ` +
'Please disable the workflow first or wait for executions to complete.',
);
}
// Re-wire: for each incoming transition, point it to our child (if we have one)
const child = step.outgoingTransitions[0];
if (child) {
for (const incoming of step.incomingTransitions) {
await prisma.workflowTransition.update({
where: {id: incoming.id},
data: {toStepId: child.toStepId},
});
}
}
// Delete the step — cascades and removes its own transitions
await prisma.workflowStep.delete({where: {id: stepId}});
}
/**
* Create a transition between two steps
*/