Add workflow statuses (#6765)

Following figma updates
https://www.figma.com/design/PNBfTgOVraw557OXChYagk/Explo?node-id=21872-7929&t=DOUzd6rzwr6lprcs-0

- No activity targets for workflow entities for now
- Adding a direct relation between workflow run et workflow
- Adding a status on the version (draft, active, deactivated)
- Adding a list of statuses on workflow 
- publishedVersionId => lastPublishedVersionId

Also adding:
- the endpoint to deactivate a version
This commit is contained in:
Thomas Trompette
2024-08-28 14:53:25 +02:00
committed by GitHub
parent da23ca3c23
commit ff1adb06b2
25 changed files with 419 additions and 289 deletions

View File

@ -19,6 +19,7 @@ export class WorkflowTriggerGraphqlApiExceptionFilter
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION:
case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE:
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
case WorkflowTriggerExceptionCode.FORBIDDEN:
throw new UserInputError(exception.message);
default:
throw new InternalServerError(exception.message);

View File

@ -27,6 +27,15 @@ export class WorkflowTriggerResolver {
);
}
@Mutation(() => Boolean)
async disableWorkflowTrigger(
@Args('workflowVersionId') workflowVersionId: string,
) {
return await this.workflowTriggerWorkspaceService.disableWorkflowTrigger(
workflowVersionId,
);
}
@Mutation(() => WorkflowRunDTO)
async runWorkflowVersion(
@AuthWorkspaceMemberId() workspaceMemberId: string,

View File

@ -27,7 +27,7 @@ export const workflowsAllView = async (
{
fieldMetadataId:
objectMetadataMap[STANDARD_OBJECT_IDS.workflow].fields[
WORKFLOW_STANDARD_FIELD_IDS.publishedVersionId
WORKFLOW_STANDARD_FIELD_IDS.lastPublishedVersionId
],
position: 1,
isVisible: true,

View File

@ -10,7 +10,6 @@ export const ACTIVITY_TARGET_STANDARD_FIELD_IDS = {
person: '20202020-4afd-4ae7-99c2-de57d795a93f',
company: '20202020-7cc0-44a1-8068-f11171fdd02e',
opportunity: '20202020-1fc2-4af1-8c91-7901ee0fd38b',
workflow: '20202020-a63d-40d0-b24d-ddcc1347d583',
custom: '20202020-7f21-442f-94be-32462281b1ca',
};
@ -45,7 +44,6 @@ export const ATTACHMENT_STANDARD_FIELD_IDS = {
person: '20202020-0158-4aa2-965c-5cdafe21ffa2',
company: '20202020-ceab-4a28-b546-73b06b4c08d5',
opportunity: '20202020-7374-499d-bea3-9354890755b5',
workflow: '20202020-0906-4dc3-b26f-edc951c7ab82',
custom: '20202020-302d-43b3-9aea-aa4f89282a9f',
};
@ -190,7 +188,6 @@ export const TIMELINE_ACTIVITY_STANDARD_FIELD_IDS = {
opportunity: '20202020-7664-4a35-a3df-580d389fd527',
task: '20202020-b2f5-415c-9135-a31dfe49501b',
note: '20202020-ec55-4135-8da5-3a20badc0156',
workflow: '20202020-9e59-4030-aa27-55abd676c3c8',
custom: '20202020-4a71-41b0-9f83-9cdcca3f8b14',
linkedRecordCachedName: '20202020-cfdb-4bef-bbce-a29f41230934',
linkedRecordId: '20202020-2e0e-48c0-b445-ee6c1e61687d',
@ -284,7 +281,6 @@ export const NOTE_TARGET_STANDARD_FIELD_IDS = {
company: 'c500fbc0-d6f2-4982-a959-5a755431696c',
opportunity: '20202020-4e42-417a-a705-76581c9ade79',
custom: '20202020-3d12-4579-94ee-7117c1bad492',
workflow: '20202020-eb46-47c5-8f3f-f3f93e7aec20',
};
export const OPPORTUNITY_STANDARD_FIELD_IDS = {
@ -348,7 +344,6 @@ export const TASK_TARGET_STANDARD_FIELD_IDS = {
company: '20202020-4703-4a4e-948c-487b0c60a92c',
opportunity: '20202020-6cb2-4c01-a9a5-aca3dbc11d41',
custom: '20202020-41c1-4c9a-8c75-be0971ef89af',
workflow: '20202020-a16c-47a3-b21c-c41c9bcac659',
};
export const VIEW_FIELD_STANDARD_FIELD_IDS = {
@ -400,20 +395,18 @@ export const WORKFLOW_EVENT_LISTENER_STANDARD_FIELD_IDS = {
export const WORKFLOW_STANDARD_FIELD_IDS = {
name: '20202020-b3d3-478f-acc0-5d901e725b20',
publishedVersionId: '20202020-326a-4fba-8639-3456c0a169e8',
versions: '20202020-9432-416e-8f3c-27ee3153d099',
eventListeners: '20202020-0229-4c66-832e-035c67579a38',
lastPublishedVersionId: '20202020-326a-4fba-8639-3456c0a169e8',
statuses: '20202020-357c-4432-8c50-8c31b4a552d9',
position: '20202020-39b0-4d8c-8c5f-33c2326deb5f',
versions: '20202020-9432-416e-8f3c-27ee3153d099',
runs: '20202020-759b-4340-b58b-e73595c4df4f',
eventListeners: '20202020-0229-4c66-832e-035c67579a38',
favorites: '20202020-c554-4c41-be7a-cf9cd4b0d512',
activityTargets: '20202020-9d65-445a-899d-1c6f1cf3a9ab',
attachments: '20202020-ea95-4d4d-81cd-9921740316b8',
timelineActivities: '20202020-dd79-492a-9d11-58333ed0f71a',
taskTargets: '20202020-0094-4e79-b934-03eaa8ab949c',
noteTargets: '20202020-40aa-4839-965e-972a2f72e08d',
};
export const WORKFLOW_RUN_STANDARD_FIELD_IDS = {
workflowVersion: '20202020-2f52-4ba8-8dc4-d0d6adb9578d',
workflow: '20202020-8c57-4e7f-84f5-f373f68e1b82',
startedAt: '20202020-a234-4e2d-bd15-85bcea6bb183',
endedAt: '20202020-e1c1-4b6b-bbbd-b2beaf2e159e',
status: '20202020-6b3e-4f9c-8c2b-2e5b8e6d6f3b',
@ -424,6 +417,7 @@ export const WORKFLOW_VERSION_STANDARD_FIELD_IDS = {
name: '20202020-a12f-4cca-9937-a2e40cc65509',
workflow: '20202020-afa3-46c3-91b0-0631ca6aa1c8',
trigger: '20202020-4eae-43e7-86e0-212b41a30b48',
status: '20202020-5a34-440e-8a25-39d8c3d1d4cf',
runs: '20202020-1d08-46df-901a-85045f18099a',
steps: '20202020-5988-4a64-b94a-1f9b7b989039',
};

View File

@ -1,12 +1,10 @@
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
@ -17,7 +15,6 @@ import { ActivityWorkspaceEntity } from 'src/modules/activity/standard-objects/a
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
@WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.activityTarget,
@ -89,27 +86,6 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceJoinColumn('opportunity')
opportunityId: string | null;
@WorkspaceRelation({
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.workflow,
type: RelationMetadataType.MANY_TO_ONE,
label: 'Workflow',
description: 'ActivityTarget workflow',
icon: 'IconSettingsAutomation',
inverseSideTarget: () => WorkflowWorkspaceEntity,
inverseSideFieldKey: 'activityTargets',
})
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
@WorkspaceIsNullable()
workflow: Relation<WorkflowWorkspaceEntity> | null;
@WorkspaceJoinColumn('workflow')
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
workflowId: string | null;
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,
argsFactory: (oppositeObjectMetadata) => ({

View File

@ -1,6 +1,5 @@
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
@ -8,7 +7,6 @@ import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-en
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
@ -22,7 +20,6 @@ import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.work
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@WorkspaceEntity({
@ -168,27 +165,6 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceJoinColumn('opportunity')
opportunityId: string | null;
@WorkspaceRelation({
standardId: ATTACHMENT_STANDARD_FIELD_IDS.workflow,
type: RelationMetadataType.MANY_TO_ONE,
label: 'Workflow',
description: 'Attachment workflow',
icon: 'IconSettingsAutomation',
inverseSideTarget: () => WorkflowWorkspaceEntity,
inverseSideFieldKey: 'attachments',
})
@WorkspaceIsNullable()
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
workflow: Relation<WorkflowWorkspaceEntity> | null;
@WorkspaceJoinColumn('workflow')
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
workflowId: string | null;
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,
argsFactory: (oppositeObjectMetadata) => ({

View File

@ -1,12 +1,10 @@
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
@ -17,7 +15,6 @@ import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/com
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
@WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.noteTarget,
@ -104,25 +101,4 @@ export class NoteTargetWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'noteTargets',
})
custom: Relation<CustomWorkspaceEntity>;
@WorkspaceRelation({
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.workflow,
type: RelationMetadataType.MANY_TO_ONE,
label: 'Workflow',
description: 'Note workflow',
icon: 'IconTargetArrow',
inverseSideTarget: () => WorkflowWorkspaceEntity,
inverseSideFieldKey: 'noteTargets',
})
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
@WorkspaceIsNullable()
workflow: Relation<WorkflowWorkspaceEntity> | null;
@WorkspaceJoinColumn('workflow')
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
workflowId: string | null;
}

View File

@ -1,12 +1,10 @@
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
@ -17,7 +15,6 @@ import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/com
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
@WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.taskTarget,
@ -104,25 +101,4 @@ export class TaskTargetWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'taskTargets',
})
custom: Relation<CustomWorkspaceEntity>;
@WorkspaceRelation({
standardId: TASK_TARGET_STANDARD_FIELD_IDS.workflow,
type: RelationMetadataType.MANY_TO_ONE,
label: 'Workflow',
description: 'Task workflow',
icon: 'IconTargetArrow',
inverseSideTarget: () => WorkflowWorkspaceEntity,
inverseSideFieldKey: 'taskTargets',
})
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
@WorkspaceIsNullable()
workflow: Relation<WorkflowWorkspaceEntity> | null;
@WorkspaceJoinColumn('workflow')
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
workflowId: string | null;
}

View File

@ -1,6 +1,5 @@
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
@ -8,7 +7,6 @@ import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-en
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
@ -21,7 +19,6 @@ import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.work
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@WorkspaceEntity({
@ -185,27 +182,6 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceJoinColumn('task')
taskId: string | null;
@WorkspaceRelation({
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.workflow,
type: RelationMetadataType.MANY_TO_ONE,
label: 'Workflow',
description: 'Event workflow',
icon: 'IconTargetArrow',
inverseSideTarget: () => WorkflowWorkspaceEntity,
inverseSideFieldKey: 'timelineActivities',
})
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
@WorkspaceIsNullable()
workflow: Relation<WorkflowWorkspaceEntity> | null;
@WorkspaceJoinColumn('workflow')
@WorkspaceGate({
featureFlag: FeatureFlagKey.IsWorkflowEnabled,
})
workflowId: string | null;
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,
argsFactory: (oppositeObjectMetadata) => ({

View File

@ -18,6 +18,7 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re
import { WORKFLOW_RUN_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
export enum WorkflowRunStatus {
NOT_STARTED = 'NOT_STARTED',
@ -111,8 +112,8 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceRelation({
standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.workflowVersion,
type: RelationMetadataType.MANY_TO_ONE,
label: 'Workflow',
description: 'WorkflowVersion workflow',
label: 'Workflow version',
description: 'Workflow version linked to the run.',
icon: 'IconVersions',
inverseSideTarget: () => WorkflowVersionWorkspaceEntity,
inverseSideFieldKey: 'runs',
@ -121,4 +122,18 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceJoinColumn('workflowVersion')
workflowVersionId: string;
@WorkspaceRelation({
standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.workflow,
type: RelationMetadataType.MANY_TO_ONE,
label: 'Workflow',
description: 'Workflow linked to the run.',
icon: 'IconSettingsAutomation',
inverseSideTarget: () => WorkflowWorkspaceEntity,
inverseSideFieldKey: 'runs',
})
workflow: Relation<WorkflowWorkspaceEntity>;
@WorkspaceJoinColumn('workflow')
workflowId: string;
}

View File

@ -24,6 +24,33 @@ import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-ob
import { WorkflowStep } from 'src/modules/workflow/common/types/workflow-step.type';
import { WorkflowTrigger } from 'src/modules/workflow/common/types/workflow-trigger.type';
export enum WorkflowVersionStatus {
DRAFT = 'DRAFT',
ACTIVE = 'ACTIVE',
DEACTIVATED = 'DEACTIVATED',
}
export const WorkflowVersionStatusOptions = [
{
value: WorkflowVersionStatus.DRAFT,
label: 'Draft',
position: 0,
color: 'yellow',
},
{
value: WorkflowVersionStatus.ACTIVE,
label: 'Active',
position: 1,
color: 'green',
},
{
value: WorkflowVersionStatus.DEACTIVATED,
label: 'Deactivated',
position: 2,
color: 'gray',
},
];
@WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.workflowVersion,
namePlural: 'workflowVersions',
@ -65,6 +92,16 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceIsNullable()
steps: WorkflowStep[] | null;
@WorkspaceField({
standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.status,
type: FieldMetadataType.SELECT,
label: 'Version status',
description: 'The workflow version status',
options: WorkflowVersionStatusOptions,
defaultValue: "'DRAFT'",
})
status: WorkflowVersionStatus;
// Relations
@WorkspaceRelation({
standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.workflow,

View File

@ -15,14 +15,14 @@ import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
import { WORKFLOW_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
import {
WorkflowVersionStatus,
WorkflowVersionStatusOptions,
WorkflowVersionWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
@WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.workflow,
@ -47,14 +47,24 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity {
name: string;
@WorkspaceField({
standardId: WORKFLOW_STANDARD_FIELD_IDS.publishedVersionId,
standardId: WORKFLOW_STANDARD_FIELD_IDS.lastPublishedVersionId,
type: FieldMetadataType.TEXT,
label: 'Published Version Id',
description: 'The workflow published version id',
label: 'Last published Version Id',
description: 'The workflow last published version id',
icon: 'IconVersions',
})
@WorkspaceIsNullable()
publishedVersionId: string | null;
lastPublishedVersionId: string | null;
@WorkspaceField({
standardId: WORKFLOW_STANDARD_FIELD_IDS.statuses,
type: FieldMetadataType.MULTI_SELECT,
label: 'Statuses',
description: 'The current statuses of the workflow versions',
options: WorkflowVersionStatusOptions,
})
@WorkspaceIsNullable()
statuses: WorkflowVersionStatus[] | null;
@WorkspaceField({
standardId: WORKFLOW_STANDARD_FIELD_IDS.position,
@ -80,6 +90,18 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceIsNullable()
versions: Relation<WorkflowVersionWorkspaceEntity[]>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.runs,
type: RelationMetadataType.ONE_TO_MANY,
label: 'Runs',
description: 'Workflow runs linked to the workflow.',
icon: 'IconVersions',
inverseSideTarget: () => WorkflowRunWorkspaceEntity,
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
runs: Relation<WorkflowRunWorkspaceEntity>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.eventListeners,
type: RelationMetadataType.ONE_TO_MANY,
@ -92,17 +114,6 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceIsNullable()
eventListeners: Relation<WorkflowEventListenerWorkspaceEntity[]>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.activityTargets,
type: RelationMetadataType.ONE_TO_MANY,
label: 'Activities',
description: 'Activities tied to the contact',
icon: 'IconCheckbox',
inverseSideTarget: () => ActivityTargetWorkspaceEntity,
onDelete: RelationOnDeleteAction.CASCADE,
})
activityTargets: Relation<ActivityTargetWorkspaceEntity[]>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.favorites,
type: RelationMetadataType.ONE_TO_MANY,
@ -114,50 +125,4 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity {
})
@WorkspaceIsSystem()
favorites: Relation<FavoriteWorkspaceEntity[]>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.attachments,
type: RelationMetadataType.ONE_TO_MANY,
label: 'Attachments',
description: 'Attachments linked to the contact.',
icon: 'IconFileImport',
inverseSideTarget: () => AttachmentWorkspaceEntity,
onDelete: RelationOnDeleteAction.CASCADE,
})
attachments: Relation<AttachmentWorkspaceEntity[]>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.timelineActivities,
type: RelationMetadataType.ONE_TO_MANY,
label: 'Events',
description: 'Events linked to the workflow',
icon: 'IconTimelineEvent',
inverseSideTarget: () => TimelineActivityWorkspaceEntity,
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.taskTargets,
type: RelationMetadataType.ONE_TO_MANY,
label: 'Tasks',
description: 'Tasks tied to the workflow',
icon: 'IconCheckbox',
inverseSideTarget: () => TaskTargetWorkspaceEntity,
onDelete: RelationOnDeleteAction.CASCADE,
})
taskTargets: Relation<TaskTargetWorkspaceEntity[]>;
@WorkspaceRelation({
standardId: WORKFLOW_STANDARD_FIELD_IDS.noteTargets,
type: RelationMetadataType.ONE_TO_MANY,
label: 'Notes',
description: 'Notes tied to the workflow',
icon: 'IconNotes',
inverseSideTarget: () => NoteTargetWorkspaceEntity,
onDelete: RelationOnDeleteAction.CASCADE,
})
noteTargets: Relation<NoteTargetWorkspaceEntity[]>;
}

View File

@ -12,7 +12,7 @@ import {
export class WorkflowCommonWorkspaceService {
constructor(private readonly twentyORMManager: TwentyORMManager) {}
async getWorkflowVersion(workflowVersionId: string): Promise<
async getWorkflowVersionOrFail(workflowVersionId: string): Promise<
Omit<WorkflowVersionWorkspaceEntity, 'trigger'> & {
trigger: WorkflowTrigger;
}

View File

@ -32,7 +32,7 @@ export class RunWorkflowJob {
await this.workflowRunWorkspaceService.startWorkflowRun(workflowRunId);
const workflowVersion =
await this.workflowCommonWorkspaceService.getWorkflowVersion(
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
workflowVersionId,
);

View File

@ -10,4 +10,5 @@ export class WorkflowRunException extends CustomException {
export enum WorkflowRunExceptionCode {
WORKFLOW_RUN_NOT_FOUND = 'WORKFLOW_RUN_NOT_FOUND',
INVALID_OPERATION = 'INVALID_OPERATION',
INVALID_INPUT = 'INVALID_INPUT',
}

View File

@ -1,8 +1,10 @@
import { Module } from '@nestjs/common';
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
@Module({
imports: [WorkflowCommonModule],
providers: [WorkflowRunWorkspaceService],
exports: [WorkflowRunWorkspaceService],
})

View File

@ -6,6 +6,7 @@ import {
WorkflowRunStatus,
WorkflowRunWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workflow-common.workspace-service';
import {
WorkflowRunException,
WorkflowRunExceptionCode,
@ -13,7 +14,10 @@ import {
@Injectable()
export class WorkflowRunWorkspaceService {
constructor(private readonly twentyORMManager: TwentyORMManager) {}
constructor(
private readonly twentyORMManager: TwentyORMManager,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
) {}
async createWorkflowRun(workflowVersionId: string, createdBy: ActorMetadata) {
const workflowRunRepository =
@ -21,10 +25,16 @@ export class WorkflowRunWorkspaceService {
'workflowRun',
);
const workflowVersion =
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
workflowVersionId,
);
return (
await workflowRunRepository.save({
workflowVersionId,
createdBy,
workflowId: workflowVersion.workflowId,
status: WorkflowRunStatus.NOT_STARTED,
})
).id;

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { DatabaseEventTriggerService } from 'src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.service';
import { DatabaseEventTriggerListener } from 'src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener';
@Module({
imports: [FeatureFlagModule],
providers: [DatabaseEventTriggerService, DatabaseEventTriggerListener],
exports: [DatabaseEventTriggerService],
})
export class DatabaseEventTriggerModule {}

View File

@ -0,0 +1,50 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
import { WorkflowDatabaseEventTrigger } from 'src/modules/workflow/common/types/workflow-trigger.type';
@Injectable()
export class DatabaseEventTriggerService {
constructor(private readonly twentyORMManager: TwentyORMManager) {}
async createEventListener(
workflowId: string,
trigger: WorkflowDatabaseEventTrigger,
manager: EntityManager,
) {
const eventName = trigger.settings.eventName;
const workflowEventListenerRepository =
await this.twentyORMManager.getRepository<WorkflowEventListenerWorkspaceEntity>(
'workflowEventListener',
);
const workflowEventListener = await workflowEventListenerRepository.create({
workflowId,
eventName,
});
await workflowEventListenerRepository.save(
workflowEventListener,
{},
manager,
);
}
async deleteEventListener(workflowId: string, manager: EntityManager) {
const workflowEventListenerRepository =
await this.twentyORMManager.getRepository<WorkflowEventListenerWorkspaceEntity>(
'workflowEventListener',
);
await workflowEventListenerRepository.delete(
{
workflowId,
},
manager,
);
}
}

View File

@ -5,6 +5,10 @@ import { Processor } from 'src/engine/integrations/message-queue/decorators/proc
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import {
WorkflowVersionStatus,
WorkflowVersionWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-runner.workspace-service';
import {
@ -36,16 +40,32 @@ export class WorkflowEventTriggerJob {
id: data.workflowId,
});
if (!workflow.publishedVersionId) {
if (!workflow.lastPublishedVersionId) {
throw new WorkflowTriggerException(
'Workflow has no published version',
WorkflowTriggerExceptionCode.INTERNAL_ERROR,
);
}
const workflowVersionRepository =
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
'workflowVersion',
);
const workflowVersion = await workflowVersionRepository.findOneByOrFail({
id: workflow.lastPublishedVersionId,
});
if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) {
throw new WorkflowTriggerException(
'Workflow version is not active',
WorkflowTriggerExceptionCode.INTERNAL_ERROR,
);
}
await this.workflowRunnerWorkspaceService.run(
data.workspaceId,
workflow.publishedVersionId,
workflow.lastPublishedVersionId,
data.payload,
{
source: FieldActorSource.WORKFLOW,

View File

@ -1,4 +1,8 @@
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import {
WorkflowVersionStatus,
WorkflowVersionWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import {
WorkflowTrigger,
WorkflowTriggerType,
@ -8,7 +12,32 @@ import {
WorkflowTriggerExceptionCode,
} from 'src/modules/workflow/workflow-trigger/workflow-trigger.exception';
export function assertWorkflowVersionIsValid(
export function assertVersionCanBeActivated(
workflowVersion: Omit<WorkflowVersionWorkspaceEntity, 'trigger'> & {
trigger: WorkflowTrigger;
},
workflow: WorkflowWorkspaceEntity,
) {
assertVersionIsValid(workflowVersion);
const isLastPublishedVersion =
workflow.lastPublishedVersionId === workflowVersion.id;
const isDraft = workflowVersion.status === WorkflowVersionStatus.DRAFT;
const isLastPublishedVersionDeactivated =
workflowVersion.status === WorkflowVersionStatus.DEACTIVATED &&
isLastPublishedVersion;
if (!isDraft && !isLastPublishedVersionDeactivated) {
throw new WorkflowTriggerException(
'Cannot activate non-draft or non-last-published version',
WorkflowTriggerExceptionCode.INVALID_INPUT,
);
}
}
function assertVersionIsValid(
workflowVersion: Omit<WorkflowVersionWorkspaceEntity, 'trigger'> & {
trigger: WorkflowTrigger;
},

View File

@ -12,5 +12,6 @@ export enum WorkflowTriggerExceptionCode {
INVALID_WORKFLOW_TRIGGER = 'INVALID_WORKFLOW_TRIGGER',
INVALID_WORKFLOW_VERSION = 'INVALID_WORKFLOW_VERSION',
INVALID_ACTION_TYPE = 'INVALID_ACTION_TYPE',
FORBIDDEN = 'FORBIDDEN',
INTERNAL_ERROR = 'INTERNAL_ERROR',
}

View File

@ -1,19 +1,21 @@
import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
import { WorkflowRunnerModule } from 'src/modules/workflow/workflow-runner/workflow-runner.module';
import { DatabaseEventTriggerModule } from 'src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.module';
import { WorkflowEventTriggerJob } from 'src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job';
import { DatabaseEventTriggerListener } from 'src/modules/workflow/workflow-trigger/listeners/database-event-trigger.listener';
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workflow-trigger.workspace-service';
@Module({
imports: [WorkflowCommonModule, WorkflowRunnerModule, FeatureFlagModule],
imports: [
WorkflowCommonModule,
WorkflowRunnerModule,
DatabaseEventTriggerModule,
],
providers: [
WorkflowTriggerWorkspaceService,
ScopedWorkspaceContextFactory,
DatabaseEventTriggerListener,
WorkflowEventTriggerJob,
],
exports: [WorkflowTriggerWorkspaceService],

View File

@ -1,18 +1,21 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { buildCreatedByFromWorkspaceMember } from 'src/engine/core-modules/actor/utils/build-created-by-from-workspace-member.util';
import { User } from 'src/engine/core-modules/user/user.entity';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import {
WorkflowDatabaseEventTrigger,
WorkflowTriggerType,
} from 'src/modules/workflow/common/types/workflow-trigger.type';
WorkflowVersionStatus,
WorkflowVersionWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkflowTriggerType } from 'src/modules/workflow/common/types/workflow-trigger.type';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workflow-common.workspace-service';
import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-runner.workspace-service';
import { assertWorkflowVersionIsValid } from 'src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid';
import { DatabaseEventTriggerService } from 'src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.service';
import { assertVersionCanBeActivated } from 'src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util';
import {
WorkflowTriggerException,
WorkflowTriggerExceptionCode,
@ -25,6 +28,7 @@ export class WorkflowTriggerWorkspaceService {
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
private readonly databaseEventTriggerService: DatabaseEventTriggerService,
) {}
async runWorkflowVersion(
@ -42,17 +46,9 @@ export class WorkflowTriggerWorkspaceService {
);
}
const workflowVersion =
await this.workflowCommonWorkspaceService.getWorkflowVersion(
workflowVersionId,
);
if (!workflowVersion) {
throw new WorkflowTriggerException(
'No workflow version found',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
);
}
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
workflowVersionId,
);
return await this.workflowRunnerWorkspaceService.run(
workspaceId,
@ -64,72 +60,178 @@ export class WorkflowTriggerWorkspaceService {
async enableWorkflowTrigger(workflowVersionId: string) {
const workflowVersion =
await this.workflowCommonWorkspaceService.getWorkflowVersion(
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
workflowVersionId,
);
assertWorkflowVersionIsValid(workflowVersion);
switch (workflowVersion.trigger.type) {
case WorkflowTriggerType.DATABASE_EVENT:
await this.upsertEventListenerAndPublishVersion(
workflowVersion.workflowId,
workflowVersionId,
workflowVersion.trigger,
);
break;
default:
break;
}
return true;
}
private async upsertEventListenerAndPublishVersion(
workflowId: string,
workflowVersionId: string,
trigger: WorkflowDatabaseEventTrigger,
) {
const eventName = trigger.settings.eventName;
const workflowEventListenerRepository =
await this.twentyORMManager.getRepository<WorkflowEventListenerWorkspaceEntity>(
'workflowEventListener',
);
const workflowEventListener = await workflowEventListenerRepository.create({
workflowId,
eventName,
});
const workspaceDataSource = await this.twentyORMManager.getDatasource();
const workflowRepository =
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
'workflow',
);
await workspaceDataSource?.transaction(async (transactionManager) => {
// TODO: Use upsert when available for workspace entities
await workflowEventListenerRepository.delete(
{
workflowId,
eventName,
},
transactionManager,
);
await workflowEventListenerRepository.save(
workflowEventListener,
{},
transactionManager,
);
await workflowRepository.update(
{ id: workflowId },
{ publishedVersionId: workflowVersionId },
transactionManager,
);
const workflow = await workflowRepository.findOne({
where: { id: workflowVersion.workflowId },
});
if (!workflow) {
throw new WorkflowTriggerException(
'No workflow found',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
);
}
assertVersionCanBeActivated(workflowVersion, workflow);
const workspaceDataSource = await this.twentyORMManager.getDatasource();
const queryRunner = workspaceDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
const manager = queryRunner.manager;
try {
if (
workflow.lastPublishedVersionId &&
workflowVersionId !== workflow.lastPublishedVersionId
) {
await this.disableWorkflowTriggerWithManager(
workflow.lastPublishedVersionId,
manager,
);
}
await this.activateWorkflowVersion(
workflowVersion.workflowId,
workflowVersionId,
manager,
);
await workflowRepository.update(
{ id: workflow.id },
{ lastPublishedVersionId: workflowVersionId },
manager,
);
switch (workflowVersion.trigger.type) {
case WorkflowTriggerType.DATABASE_EVENT:
await this.databaseEventTriggerService.createEventListener(
workflowVersion.workflowId,
workflowVersion.trigger,
manager,
);
break;
default:
break;
}
await queryRunner.commitTransaction();
return true;
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
async disableWorkflowTrigger(workflowVersionId: string) {
const workspaceDataSource = await this.twentyORMManager.getDatasource();
const queryRunner = workspaceDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await this.disableWorkflowTriggerWithManager(
workflowVersionId,
queryRunner.manager,
);
await queryRunner.commitTransaction();
return true;
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
private async disableWorkflowTriggerWithManager(
workflowVersionId: string,
manager: EntityManager,
) {
const workflowVersionRepository =
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
'workflowVersion',
);
const workflowVersion = await workflowVersionRepository.findOne({
where: { id: workflowVersionId },
});
if (!workflowVersion) {
throw new WorkflowTriggerException(
'No workflow version found',
WorkflowTriggerExceptionCode.INVALID_INPUT,
);
}
if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) {
throw new WorkflowTriggerException(
'Cannot disable non-active workflow version',
WorkflowTriggerExceptionCode.INVALID_INPUT,
);
}
await workflowVersionRepository.update(
{ id: workflowVersionId },
{ status: WorkflowVersionStatus.DEACTIVATED },
manager,
);
switch (workflowVersion?.trigger?.type) {
case WorkflowTriggerType.DATABASE_EVENT:
await this.databaseEventTriggerService.deleteEventListener(
workflowVersion.workflowId,
manager,
);
break;
default:
break;
}
}
private async activateWorkflowVersion(
workflowId: string,
workflowVersionId: string,
manager: EntityManager,
) {
const workflowVersionRepository =
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
'workflowVersion',
);
const activeWorkflowVersions = await workflowVersionRepository.find(
{
where: { workflowId, status: WorkflowVersionStatus.ACTIVE },
},
manager,
);
if (activeWorkflowVersions.length > 0) {
throw new WorkflowTriggerException(
'Cannot have more than one active workflow version',
WorkflowTriggerExceptionCode.FORBIDDEN,
);
}
await workflowVersionRepository.update(
{ id: workflowVersionId },
{ status: WorkflowVersionStatus.ACTIVE },
manager,
);
}
}