Delete filter on branch deletion (#13834)
When the target is not in the direct next step ids of the source (step or trigger), check if there is a filter between both. If yes, delete the filter and remove it from source next step ids. Also refactored the existing.
This commit is contained in:
+233
-8
@@ -1,14 +1,17 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service';
|
||||
import {
|
||||
type WorkflowAction,
|
||||
WorkflowActionType,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service';
|
||||
|
||||
type MockWorkspaceRepository = Partial<
|
||||
WorkspaceRepository<WorkflowVersionWorkspaceEntity>
|
||||
@@ -71,6 +74,7 @@ const mockWorkflowVersion = {
|
||||
|
||||
describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
let twentyORMGlobalManager: jest.Mocked<TwentyORMGlobalManager>;
|
||||
let workflowCommonWorkspaceService: jest.Mocked<WorkflowCommonWorkspaceService>;
|
||||
let service: WorkflowVersionEdgeWorkspaceService;
|
||||
let mockWorkflowVersionWorkspaceRepository: MockWorkspaceRepository;
|
||||
|
||||
@@ -90,6 +94,12 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
.mockResolvedValue(mockWorkflowVersionWorkspaceRepository),
|
||||
} as unknown as jest.Mocked<TwentyORMGlobalManager>;
|
||||
|
||||
workflowCommonWorkspaceService = {
|
||||
getWorkflowVersionOrFail: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkflowVersion),
|
||||
} as unknown as jest.Mocked<WorkflowCommonWorkspaceService>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
WorkflowVersionEdgeWorkspaceService,
|
||||
@@ -97,6 +107,10 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
provide: TwentyORMGlobalManager,
|
||||
useValue: twentyORMGlobalManager,
|
||||
},
|
||||
{
|
||||
provide: WorkflowCommonWorkspaceService,
|
||||
useValue: workflowCommonWorkspaceService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -107,7 +121,7 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
it('should throw if target does not exists', async () => {
|
||||
const call = async () =>
|
||||
await service.createWorkflowVersionEdge({
|
||||
source: 'trigger',
|
||||
source: TRIGGER_STEP_ID,
|
||||
target: 'not-existing-step',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
@@ -121,12 +135,19 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
describe('with source is the trigger', () => {
|
||||
it('should create an edge between trigger and step-1', async () => {
|
||||
const result = await service.createWorkflowVersionEdge({
|
||||
source: 'trigger',
|
||||
source: TRIGGER_STEP_ID,
|
||||
target: 'step-3',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
workflowCommonWorkspaceService.getWorkflowVersionOrFail,
|
||||
).toHaveBeenCalledWith({
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockWorkflowVersionWorkspaceRepository.update,
|
||||
).toHaveBeenCalledWith(mockWorkflowVersionId, {
|
||||
@@ -148,7 +169,7 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
|
||||
it('should not duplicate stepIds if edge already exists', async () => {
|
||||
const result = await service.createWorkflowVersionEdge({
|
||||
source: 'trigger',
|
||||
source: TRIGGER_STEP_ID,
|
||||
target: 'step-1',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
@@ -245,7 +266,7 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
it('should throw if target does not exists', async () => {
|
||||
const call = async () =>
|
||||
await service.deleteWorkflowVersionEdge({
|
||||
source: 'trigger',
|
||||
source: TRIGGER_STEP_ID,
|
||||
target: 'not-existing-step',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
@@ -259,12 +280,19 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
describe('with source is the trigger', () => {
|
||||
it('should delete an edge between trigger and step-1', async () => {
|
||||
const result = await service.deleteWorkflowVersionEdge({
|
||||
source: 'trigger',
|
||||
source: TRIGGER_STEP_ID,
|
||||
target: 'step-1',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
workflowCommonWorkspaceService.getWorkflowVersionOrFail,
|
||||
).toHaveBeenCalledWith({
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockWorkflowVersionWorkspaceRepository.update,
|
||||
).toHaveBeenCalledWith(mockWorkflowVersionId, {
|
||||
@@ -286,12 +314,19 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
|
||||
it('should not delete if edge does not exists', async () => {
|
||||
const result = await service.deleteWorkflowVersionEdge({
|
||||
source: 'trigger',
|
||||
source: TRIGGER_STEP_ID,
|
||||
target: 'step-2',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
workflowCommonWorkspaceService.getWorkflowVersionOrFail,
|
||||
).toHaveBeenCalledWith({
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockWorkflowVersionWorkspaceRepository.update,
|
||||
).not.toHaveBeenCalled();
|
||||
@@ -377,5 +412,195 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with filter steps', () => {
|
||||
it('should delete the filter step when deleting edge from trigger to target through filter', async () => {
|
||||
const mockStepsWithFilter = [
|
||||
{
|
||||
id: 'step-1',
|
||||
type: WorkflowActionType.FORM,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: ['step-2'],
|
||||
},
|
||||
{
|
||||
id: 'step-2',
|
||||
type: WorkflowActionType.SEND_EMAIL,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: [],
|
||||
},
|
||||
{
|
||||
id: 'filter-step',
|
||||
type: WorkflowActionType.FILTER,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: ['step-2'],
|
||||
},
|
||||
] as WorkflowAction[];
|
||||
|
||||
const mockTriggerWithFilter = {
|
||||
type: WorkflowTriggerType.MANUAL,
|
||||
settings: {},
|
||||
nextStepIds: ['step-1', 'filter-step'],
|
||||
};
|
||||
|
||||
const mockWorkflowVersionWithFilter = {
|
||||
id: mockWorkflowVersionId,
|
||||
trigger: mockTriggerWithFilter,
|
||||
steps: mockStepsWithFilter,
|
||||
status: 'DRAFT',
|
||||
} as WorkflowVersionWorkspaceEntity;
|
||||
|
||||
workflowCommonWorkspaceService.getWorkflowVersionOrFail.mockResolvedValue(
|
||||
mockWorkflowVersionWithFilter,
|
||||
);
|
||||
|
||||
const result = await service.deleteWorkflowVersionEdge({
|
||||
source: TRIGGER_STEP_ID,
|
||||
target: 'step-2',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
workflowCommonWorkspaceService.getWorkflowVersionOrFail,
|
||||
).toHaveBeenCalledWith({
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockWorkflowVersionWorkspaceRepository.update,
|
||||
).toHaveBeenCalledWith(mockWorkflowVersionId, {
|
||||
trigger: {
|
||||
...mockTriggerWithFilter,
|
||||
nextStepIds: ['step-1'],
|
||||
},
|
||||
steps: mockStepsWithFilter.filter(
|
||||
(step) => step.id !== 'filter-step',
|
||||
),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
triggerNextStepIds: ['step-1'],
|
||||
stepsNextStepIds: {
|
||||
'step-1': ['step-2'],
|
||||
'step-2': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should delete the filter step when deleting edge from step to target through filter', async () => {
|
||||
const mockStepsWithFilter = [
|
||||
{
|
||||
id: 'step-1',
|
||||
type: WorkflowActionType.FORM,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: ['step-2', 'filter-step'],
|
||||
},
|
||||
{
|
||||
id: 'step-2',
|
||||
type: WorkflowActionType.SEND_EMAIL,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: [],
|
||||
},
|
||||
{
|
||||
id: 'step-3',
|
||||
type: WorkflowActionType.SEND_EMAIL,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: [],
|
||||
},
|
||||
{
|
||||
id: 'filter-step',
|
||||
type: WorkflowActionType.FILTER,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
continueOnFailure: { value: false },
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: ['step-3'],
|
||||
},
|
||||
] as WorkflowAction[];
|
||||
|
||||
const mockWorkflowVersionWithFilter = {
|
||||
id: mockWorkflowVersionId,
|
||||
trigger: mockTrigger,
|
||||
steps: mockStepsWithFilter,
|
||||
status: 'DRAFT',
|
||||
} as WorkflowVersionWorkspaceEntity;
|
||||
|
||||
workflowCommonWorkspaceService.getWorkflowVersionOrFail.mockResolvedValue(
|
||||
mockWorkflowVersionWithFilter,
|
||||
);
|
||||
|
||||
const result = await service.deleteWorkflowVersionEdge({
|
||||
source: 'step-1',
|
||||
target: 'step-3',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
workflowCommonWorkspaceService.getWorkflowVersionOrFail,
|
||||
).toHaveBeenCalledWith({
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockWorkflowVersionWorkspaceRepository.update,
|
||||
).toHaveBeenCalledWith(mockWorkflowVersionId, {
|
||||
steps: mockStepsWithFilter
|
||||
.map((step) => {
|
||||
if (step.id === 'step-1') {
|
||||
return {
|
||||
...step,
|
||||
nextStepIds: ['step-2'],
|
||||
};
|
||||
}
|
||||
|
||||
return step;
|
||||
})
|
||||
.filter((step) => step.id !== 'filter-step'),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
triggerNextStepIds: ['step-1'],
|
||||
stepsNextStepIds: {
|
||||
'step-1': ['step-2'],
|
||||
'step-2': [],
|
||||
'step-3': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+2
@@ -1,8 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service';
|
||||
|
||||
@Module({
|
||||
imports: [WorkflowCommonModule],
|
||||
providers: [WorkflowVersionEdgeWorkspaceService],
|
||||
exports: [WorkflowVersionEdgeWorkspaceService],
|
||||
})
|
||||
|
||||
+328
-113
@@ -1,21 +1,30 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto';
|
||||
import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import {
|
||||
WorkflowVersionEdgeException,
|
||||
WorkflowVersionEdgeExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-edge.exception';
|
||||
import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/assert-workflow-version-is-draft.util';
|
||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import {
|
||||
type WorkflowAction,
|
||||
WorkflowActionType,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowVersionEdgeWorkspaceService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||
) {}
|
||||
|
||||
async createWorkflowVersionEdge({
|
||||
@@ -36,26 +45,16 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowVersion = await workflowVersionRepository.findOne({
|
||||
where: {
|
||||
id: workflowVersionId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(workflowVersion)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
'WorkflowVersion not found',
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const steps = workflowVersion.steps || [];
|
||||
|
||||
const trigger = workflowVersion.trigger;
|
||||
|
||||
const isSourceTrigger = source === 'trigger';
|
||||
const steps = workflowVersion.steps || [];
|
||||
|
||||
const targetStep = steps.find((step) => step.id === target);
|
||||
|
||||
@@ -66,41 +65,150 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
);
|
||||
}
|
||||
|
||||
const isSourceTrigger = source === TRIGGER_STEP_ID;
|
||||
|
||||
if (isSourceTrigger) {
|
||||
if (!isDefined(trigger)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Trigger not found in workflowVersion '${workflowVersionId}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
return this.createTriggerEdge({
|
||||
trigger,
|
||||
steps,
|
||||
target,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
});
|
||||
} else {
|
||||
return this.createStepEdge({
|
||||
trigger,
|
||||
steps,
|
||||
source,
|
||||
target,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger.nextStepIds?.includes(target)) {
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
async deleteWorkflowVersionEdge({
|
||||
source,
|
||||
target,
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
}: {
|
||||
source: string;
|
||||
target: string;
|
||||
workflowVersionId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const updatedTrigger = {
|
||||
...trigger,
|
||||
nextStepIds: [...(trigger.nextStepIds ?? []), target],
|
||||
};
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
trigger: updatedTrigger,
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const trigger = workflowVersion.trigger;
|
||||
const steps = workflowVersion.steps || [];
|
||||
|
||||
const targetStep = steps.find((step) => step.id === target);
|
||||
|
||||
if (!isDefined(targetStep)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Target step '${target}' not found in workflowVersion '${workflowVersionId}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const isSourceTrigger = source === TRIGGER_STEP_ID;
|
||||
|
||||
if (isSourceTrigger) {
|
||||
return this.deleteTriggerEdge({
|
||||
trigger,
|
||||
steps,
|
||||
target,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
});
|
||||
} else {
|
||||
return this.deleteStepEdge({
|
||||
trigger,
|
||||
steps,
|
||||
source,
|
||||
target,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async createTriggerEdge({
|
||||
trigger,
|
||||
steps,
|
||||
target,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
}: {
|
||||
trigger: WorkflowTrigger | null;
|
||||
steps: WorkflowAction[];
|
||||
target: string;
|
||||
workflowVersion: WorkflowVersionWorkspaceEntity;
|
||||
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
if (!isDefined(trigger)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Trigger not found in workflowVersion '${workflowVersion.id}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (trigger.nextStepIds?.includes(target)) {
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger: updatedTrigger,
|
||||
trigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedTrigger = {
|
||||
...trigger,
|
||||
nextStepIds: [...(trigger.nextStepIds ?? []), target],
|
||||
};
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
trigger: updatedTrigger,
|
||||
});
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger: updatedTrigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
|
||||
private async createStepEdge({
|
||||
trigger,
|
||||
steps,
|
||||
source,
|
||||
target,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
}: {
|
||||
trigger: WorkflowTrigger | null;
|
||||
steps: WorkflowAction[];
|
||||
source: string;
|
||||
target: string;
|
||||
workflowVersion: WorkflowVersionWorkspaceEntity;
|
||||
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const sourceStep = steps.find((step) => step.id === source);
|
||||
|
||||
if (!isDefined(sourceStep)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Source step '${source}' not found in workflowVersion '${workflowVersionId}'`,
|
||||
`Source step '${source}' not found in workflowVersion '${workflowVersion.id}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
@@ -135,106 +243,92 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
});
|
||||
}
|
||||
|
||||
async deleteWorkflowVersionEdge({
|
||||
source,
|
||||
private async deleteTriggerEdge({
|
||||
trigger,
|
||||
steps,
|
||||
target,
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
}: {
|
||||
source: string;
|
||||
trigger: WorkflowTrigger | null;
|
||||
steps: WorkflowAction[];
|
||||
target: string;
|
||||
workflowVersionId: string;
|
||||
workspaceId: string;
|
||||
workflowVersion: WorkflowVersionWorkspaceEntity;
|
||||
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
if (!isDefined(trigger)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Trigger not found in workflowVersion '${workflowVersion.id}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowVersion = await workflowVersionRepository.findOne({
|
||||
where: {
|
||||
id: workflowVersionId,
|
||||
},
|
||||
if (!trigger.nextStepIds?.includes(target)) {
|
||||
return this.handleFilterBetweenTriggerAndTarget({
|
||||
trigger,
|
||||
steps,
|
||||
target,
|
||||
workflowVersionId: workflowVersion.id,
|
||||
workflowVersionRepository,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedTrigger = {
|
||||
...trigger,
|
||||
nextStepIds: trigger.nextStepIds?.filter(
|
||||
(nextStepId: string) => nextStepId !== target,
|
||||
),
|
||||
};
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
trigger: updatedTrigger,
|
||||
});
|
||||
|
||||
if (!isDefined(workflowVersion)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
'WorkflowVersion not found',
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const steps = workflowVersion.steps || [];
|
||||
|
||||
const trigger = workflowVersion.trigger;
|
||||
|
||||
const isSourceTrigger = source === 'trigger';
|
||||
|
||||
const targetStep = steps.find((step) => step.id === target);
|
||||
|
||||
if (!isDefined(targetStep)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Target step '${target}' not found in workflowVersion '${workflowVersionId}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (isSourceTrigger) {
|
||||
if (!isDefined(trigger)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Trigger not found in workflowVersion '${workflowVersionId}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!trigger.nextStepIds?.includes(target)) {
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedTrigger = {
|
||||
...trigger,
|
||||
nextStepIds: trigger.nextStepIds?.filter(
|
||||
(nextStepId) => nextStepId !== target,
|
||||
),
|
||||
};
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
trigger: updatedTrigger,
|
||||
});
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger: updatedTrigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger: updatedTrigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteStepEdge({
|
||||
trigger,
|
||||
steps,
|
||||
source,
|
||||
target,
|
||||
workflowVersion,
|
||||
workflowVersionRepository,
|
||||
}: {
|
||||
trigger: WorkflowTrigger | null;
|
||||
steps: WorkflowAction[];
|
||||
source: string;
|
||||
target: string;
|
||||
workflowVersion: WorkflowVersionWorkspaceEntity;
|
||||
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const sourceStep = steps.find((step) => step.id === source);
|
||||
|
||||
if (!isDefined(sourceStep)) {
|
||||
throw new WorkflowVersionEdgeException(
|
||||
`Source step '${source}' not found in workflowVersion '${workflowVersionId}'`,
|
||||
`Source step '${source}' not found in workflowVersion '${workflowVersion.id}'`,
|
||||
WorkflowVersionEdgeExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!sourceStep.nextStepIds?.includes(target)) {
|
||||
return computeWorkflowVersionStepChanges({
|
||||
return await this.handleFilterBetweenSourceAndTarget({
|
||||
trigger,
|
||||
steps,
|
||||
sourceStep,
|
||||
target,
|
||||
workflowVersionId: workflowVersion.id,
|
||||
workflowVersionRepository,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedSourceStep = {
|
||||
...sourceStep,
|
||||
nextStepIds: sourceStep.nextStepIds?.filter(
|
||||
(nextStepId) => nextStepId !== target,
|
||||
(nextStepId: string) => nextStepId !== target,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -255,4 +349,125 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
steps: updatedSteps,
|
||||
});
|
||||
}
|
||||
|
||||
private async handleFilterBetweenTriggerAndTarget({
|
||||
trigger,
|
||||
steps,
|
||||
target,
|
||||
workflowVersionId,
|
||||
workflowVersionRepository,
|
||||
}: {
|
||||
trigger: WorkflowTrigger;
|
||||
steps: WorkflowAction[];
|
||||
target: string;
|
||||
workflowVersionId: string;
|
||||
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const filterBetweenTriggerAndTarget = this.findFilterBetweenNodes({
|
||||
steps,
|
||||
sourceNextStepIds: trigger.nextStepIds,
|
||||
target,
|
||||
});
|
||||
|
||||
if (!isDefined(filterBetweenTriggerAndTarget)) {
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedTrigger = {
|
||||
...trigger,
|
||||
nextStepIds: trigger.nextStepIds?.filter(
|
||||
(nextStepId: string) => nextStepId !== filterBetweenTriggerAndTarget.id,
|
||||
),
|
||||
};
|
||||
|
||||
const updatedSteps = steps.filter(
|
||||
(step) => step.id !== filterBetweenTriggerAndTarget.id,
|
||||
);
|
||||
|
||||
await workflowVersionRepository.update(workflowVersionId, {
|
||||
trigger: updatedTrigger,
|
||||
steps: updatedSteps,
|
||||
});
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger: updatedTrigger,
|
||||
steps: updatedSteps,
|
||||
});
|
||||
}
|
||||
|
||||
private async handleFilterBetweenSourceAndTarget({
|
||||
trigger,
|
||||
steps,
|
||||
sourceStep,
|
||||
target,
|
||||
workflowVersionRepository,
|
||||
workflowVersionId,
|
||||
}: {
|
||||
trigger: WorkflowTrigger | null;
|
||||
steps: WorkflowAction[];
|
||||
sourceStep: WorkflowAction;
|
||||
target: string;
|
||||
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>;
|
||||
workflowVersionId: string;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const filterBetweenSourceAndTarget = this.findFilterBetweenNodes({
|
||||
steps,
|
||||
sourceNextStepIds: sourceStep.nextStepIds,
|
||||
target,
|
||||
});
|
||||
|
||||
if (!isDefined(filterBetweenSourceAndTarget)) {
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger,
|
||||
steps,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedSourceStep = {
|
||||
...sourceStep,
|
||||
nextStepIds: sourceStep.nextStepIds?.filter(
|
||||
(nextStepId: string) => nextStepId !== filterBetweenSourceAndTarget.id,
|
||||
),
|
||||
};
|
||||
|
||||
const updatedSteps = steps
|
||||
.map((step) => {
|
||||
if (step.id === sourceStep.id) {
|
||||
return updatedSourceStep;
|
||||
}
|
||||
|
||||
return step;
|
||||
})
|
||||
.filter((step) => step.id !== filterBetweenSourceAndTarget.id);
|
||||
|
||||
await workflowVersionRepository.update(workflowVersionId, {
|
||||
steps: updatedSteps,
|
||||
});
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
trigger,
|
||||
steps: updatedSteps,
|
||||
});
|
||||
}
|
||||
|
||||
private findFilterBetweenNodes({
|
||||
steps,
|
||||
sourceNextStepIds,
|
||||
target,
|
||||
}: {
|
||||
steps: WorkflowAction[];
|
||||
sourceNextStepIds: string[] | undefined;
|
||||
target: string;
|
||||
}) {
|
||||
const nextStepFilters = steps.filter(
|
||||
(step) =>
|
||||
sourceNextStepIds?.includes(step.id) &&
|
||||
step.type === WorkflowActionType.FILTER,
|
||||
);
|
||||
|
||||
return nextStepFilters.find((step) => step.nextStepIds?.includes(target));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -1,6 +1,8 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
@@ -135,7 +137,7 @@ describe('WorkflowVersionStepWorkspaceService', () => {
|
||||
const result = await service.createWorkflowVersionStep({
|
||||
input: {
|
||||
stepType: WorkflowActionType.FORM,
|
||||
parentStepId: 'trigger',
|
||||
parentStepId: TRIGGER_STEP_ID,
|
||||
nextStepId: undefined,
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
},
|
||||
@@ -160,7 +162,7 @@ describe('WorkflowVersionStepWorkspaceService', () => {
|
||||
const result = await service.createWorkflowVersionStep({
|
||||
input: {
|
||||
stepType: WorkflowActionType.FORM,
|
||||
parentStepId: 'trigger',
|
||||
parentStepId: TRIGGER_STEP_ID,
|
||||
nextStepId: 'step-1',
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
},
|
||||
@@ -259,7 +261,7 @@ describe('WorkflowVersionStepWorkspaceService', () => {
|
||||
|
||||
it('should delete trigger', async () => {
|
||||
const result = await service.deleteWorkflowVersionStep({
|
||||
stepIdToDelete: 'trigger',
|
||||
stepIdToDelete: TRIGGER_STEP_ID,
|
||||
workflowVersionId: mockWorkflowVersionId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
@@ -277,7 +279,7 @@ describe('WorkflowVersionStepWorkspaceService', () => {
|
||||
'step-2': [],
|
||||
'step-3': [],
|
||||
},
|
||||
deletedStepIds: ['trigger'],
|
||||
deletedStepIds: [TRIGGER_STEP_ID],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { insertStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step';
|
||||
import {
|
||||
type WorkflowAction,
|
||||
@@ -146,7 +148,7 @@ describe('insertStep', () => {
|
||||
existingTrigger,
|
||||
existingSteps: [step1],
|
||||
insertedStep: newStep,
|
||||
parentStepId: 'trigger',
|
||||
parentStepId: TRIGGER_STEP_ID,
|
||||
nextStepId: undefined,
|
||||
});
|
||||
|
||||
|
||||
+4
-2
@@ -1,3 +1,5 @@
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { removeStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step';
|
||||
import {
|
||||
type WorkflowAction,
|
||||
@@ -187,7 +189,7 @@ describe('removeStep', () => {
|
||||
const result = removeStep({
|
||||
existingTrigger: mockTrigger,
|
||||
existingSteps: [step1, step2, step3],
|
||||
stepIdToDelete: 'trigger',
|
||||
stepIdToDelete: TRIGGER_STEP_ID,
|
||||
stepToDeleteChildrenIds: ['1'],
|
||||
});
|
||||
|
||||
@@ -247,7 +249,7 @@ describe('removeStep', () => {
|
||||
const result = removeStep({
|
||||
existingTrigger: { ...mockTrigger, nextStepIds: [] },
|
||||
existingSteps: null,
|
||||
stepIdToDelete: 'trigger',
|
||||
stepIdToDelete: TRIGGER_STEP_ID,
|
||||
});
|
||||
|
||||
expect(result.updatedTrigger).toEqual(null);
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
|
||||
@@ -22,7 +24,7 @@ export const insertStep = ({
|
||||
|
||||
let updatedExistingSteps = existingSteps;
|
||||
|
||||
if (parentStepId === 'trigger') {
|
||||
if (parentStepId === TRIGGER_STEP_ID) {
|
||||
if (!existingTrigger) {
|
||||
throw new Error('Cannot insert step from undefined trigger');
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import {
|
||||
type WorkflowAction,
|
||||
@@ -181,7 +182,7 @@ const removeTrigger = ({
|
||||
return {
|
||||
updatedSteps,
|
||||
updatedTrigger: null,
|
||||
removedStepIds: ['trigger', ...stepIdsToRemove],
|
||||
removedStepIds: [TRIGGER_STEP_ID, ...stepIdsToRemove],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -196,7 +197,7 @@ export const removeStep = ({
|
||||
stepIdToDelete: string;
|
||||
stepToDeleteChildrenIds?: string[];
|
||||
}) => {
|
||||
if (stepIdToDelete === 'trigger') {
|
||||
if (stepIdToDelete === TRIGGER_STEP_ID) {
|
||||
return removeTrigger({
|
||||
existingSteps,
|
||||
triggerChildrenIds: stepToDeleteChildrenIds,
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
import { StepStatus, TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@@ -221,7 +221,7 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
const existingTrigger = workflowVersion.trigger;
|
||||
|
||||
const isDeletingTrigger =
|
||||
stepIdToDelete === 'trigger' && isDefined(existingTrigger);
|
||||
stepIdToDelete === TRIGGER_STEP_ID && isDefined(existingTrigger);
|
||||
|
||||
if (!isDeletingTrigger && !isDefined(workflowVersion.steps)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
|
||||
+3
-2
@@ -1,8 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import { type WorkflowStepPositionUpdateInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-step-position-update-input.dto';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import {
|
||||
WorkflowVersionStepException,
|
||||
@@ -17,7 +19,6 @@ import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/
|
||||
import { assertWorkflowVersionTriggerIsDefined } from 'src/modules/workflow/common/utils/assert-workflow-version-trigger-is-defined.util';
|
||||
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { type WorkflowStepPositionUpdateInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-step-position-update-input.dto';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowVersionWorkspaceService {
|
||||
@@ -139,7 +140,7 @@ export class WorkflowVersionWorkspaceService {
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const triggerPosition = positions.find(
|
||||
(position) => position.id === 'trigger',
|
||||
(position) => position.id === TRIGGER_STEP_ID,
|
||||
);
|
||||
|
||||
const updatedTrigger =
|
||||
|
||||
Reference in New Issue
Block a user