Separate create draft cases op (#18613)

Bug: When creating a draft from an activated workflow version, the draft
row was inserted into the database without steps and trigger, then
updated with them in a separate operation. The SSE create-one event
fired on the INSERT, causing the frontend to refetch the draft before
the UPDATE — resulting in steps: null and trigger: null, which crashed
the step editor.

Fix: Reorder the operations so steps are duplicated first, then either
insert a new draft or update an existing one with steps and trigger
already populated. The row never exists in the database without complete
data.
This commit is contained in:
Thomas Trompette
2026-03-13 10:43:20 +00:00
committed by GitHub
parent 349bfc8462
commit dfd28f5b4a
2 changed files with 48 additions and 40 deletions
@@ -1,4 +1,3 @@
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
import {
ConfirmationModal,
StyledCenteredButton,
@@ -7,6 +6,7 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID } from '@/workflow/constants/OverrideWorkflowDraftConfirmationModalId';
import { useCreateDraftFromWorkflowVersion } from '@/workflow/hooks/useCreateDraftFromWorkflowVersion';
import { useLingui } from '@lingui/react/macro';
import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types';
import { getAppPath } from 'twenty-shared/utils';
import { useNavigateApp } from '~/hooks/useNavigateApp';
@@ -58,6 +58,7 @@ export const OverrideWorkflowDraftConfirmationModal = ({
variant="secondary"
title={t`Go to Draft`}
fullWidth
justify="center"
/>
}
/>
@@ -77,44 +77,6 @@ export class WorkflowVersionWorkspaceService {
assertWorkflowVersionTriggerIsDefined(workflowVersionToCopy);
assertWorkflowVersionHasSteps(workflowVersionToCopy);
let draftWorkflowVersion = await workflowVersionRepository.findOne({
where: {
workflowId,
status: WorkflowVersionStatus.DRAFT,
},
});
if (!isDefined(draftWorkflowVersion)) {
const workflowVersionsCount = await workflowVersionRepository.count({
where: {
workflowId,
},
});
const position = await this.recordPositionService.buildRecordPosition(
{
value: 'first',
objectMetadata: {
isCustom: false,
nameSingular: 'workflowVersion',
},
workspaceId,
},
);
const insertResult = await workflowVersionRepository.insert({
workflowId,
name: `v${workflowVersionsCount + 1}`,
status: WorkflowVersionStatus.DRAFT,
position,
});
draftWorkflowVersion = insertResult
.generatedMaps[0] as WorkflowVersionWorkspaceEntity;
}
assertWorkflowVersionIsDraft(draftWorkflowVersion);
const newWorkflowVersionTrigger = workflowVersionToCopy.trigger;
const newWorkflowVersionSteps: WorkflowAction[] = [];
@@ -128,11 +90,56 @@ export class WorkflowVersionWorkspaceService {
newWorkflowVersionSteps.push(duplicatedStep);
}
await workflowVersionRepository.update(draftWorkflowVersion.id, {
const existingDraftVersion = await workflowVersionRepository.findOne({
where: {
workflowId,
status: WorkflowVersionStatus.DRAFT,
},
});
if (isDefined(existingDraftVersion)) {
assertWorkflowVersionIsDraft(existingDraftVersion);
await workflowVersionRepository.update(existingDraftVersion.id, {
steps: newWorkflowVersionSteps,
trigger: newWorkflowVersionTrigger,
});
return {
...existingDraftVersion,
name: existingDraftVersion.name ?? '',
steps: newWorkflowVersionSteps,
trigger: newWorkflowVersionTrigger,
};
}
const workflowVersionsCount = await workflowVersionRepository.count({
where: {
workflowId,
},
});
const position = await this.recordPositionService.buildRecordPosition({
value: 'first',
objectMetadata: {
isCustom: false,
nameSingular: 'workflowVersion',
},
workspaceId,
});
const insertResult = await workflowVersionRepository.insert({
workflowId,
name: `v${workflowVersionsCount + 1}`,
status: WorkflowVersionStatus.DRAFT,
steps: newWorkflowVersionSteps,
trigger: newWorkflowVersionTrigger,
position,
});
const draftWorkflowVersion = insertResult
.generatedMaps[0] as WorkflowVersionWorkspaceEntity;
return {
...draftWorkflowVersion,
name: draftWorkflowVersion.name ?? '',