diff --git a/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts index bc707d550..1bbfdc273 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts @@ -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); diff --git a/packages/twenty-server/src/engine/core-modules/workflow/workflow-trigger.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/workflow-trigger.resolver.ts index cdce3b954..82aa30498 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/workflow-trigger.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/workflow-trigger.resolver.ts @@ -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, diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts index 313ed4208..280b512d3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts @@ -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, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index 955031ba4..cbc1c49f5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -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', }; diff --git a/packages/twenty-server/src/modules/activity/standard-objects/activity-target.workspace-entity.ts b/packages/twenty-server/src/modules/activity/standard-objects/activity-target.workspace-entity.ts index 41967728a..00e26c406 100644 --- a/packages/twenty-server/src/modules/activity/standard-objects/activity-target.workspace-entity.ts +++ b/packages/twenty-server/src/modules/activity/standard-objects/activity-target.workspace-entity.ts @@ -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 | null; - - @WorkspaceJoinColumn('workflow') - @WorkspaceGate({ - featureFlag: FeatureFlagKey.IsWorkflowEnabled, - }) - workflowId: string | null; - @WorkspaceDynamicRelation({ type: RelationMetadataType.MANY_TO_ONE, argsFactory: (oppositeObjectMetadata) => ({ diff --git a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts index cd23af305..bdadd2f96 100644 --- a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts +++ b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts @@ -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 | null; - - @WorkspaceJoinColumn('workflow') - @WorkspaceGate({ - featureFlag: FeatureFlagKey.IsWorkflowEnabled, - }) - workflowId: string | null; - @WorkspaceDynamicRelation({ type: RelationMetadataType.MANY_TO_ONE, argsFactory: (oppositeObjectMetadata) => ({ diff --git a/packages/twenty-server/src/modules/note/standard-objects/note-target.workspace-entity.ts b/packages/twenty-server/src/modules/note/standard-objects/note-target.workspace-entity.ts index abb015895..674fab971 100644 --- a/packages/twenty-server/src/modules/note/standard-objects/note-target.workspace-entity.ts +++ b/packages/twenty-server/src/modules/note/standard-objects/note-target.workspace-entity.ts @@ -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; - - @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 | null; - - @WorkspaceJoinColumn('workflow') - @WorkspaceGate({ - featureFlag: FeatureFlagKey.IsWorkflowEnabled, - }) - workflowId: string | null; } diff --git a/packages/twenty-server/src/modules/task/standard-objects/task-target.workspace-entity.ts b/packages/twenty-server/src/modules/task/standard-objects/task-target.workspace-entity.ts index 82c864ed2..394fcbb0c 100644 --- a/packages/twenty-server/src/modules/task/standard-objects/task-target.workspace-entity.ts +++ b/packages/twenty-server/src/modules/task/standard-objects/task-target.workspace-entity.ts @@ -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; - - @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 | null; - - @WorkspaceJoinColumn('workflow') - @WorkspaceGate({ - featureFlag: FeatureFlagKey.IsWorkflowEnabled, - }) - workflowId: string | null; } diff --git a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts index db6698bf8..d683c1c1f 100644 --- a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts +++ b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts @@ -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 | null; - - @WorkspaceJoinColumn('workflow') - @WorkspaceGate({ - featureFlag: FeatureFlagKey.IsWorkflowEnabled, - }) - workflowId: string | null; - @WorkspaceDynamicRelation({ type: RelationMetadataType.MANY_TO_ONE, argsFactory: (oppositeObjectMetadata) => ({ diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts index 5f78df891..c38cf4b5a 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts @@ -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; + + @WorkspaceJoinColumn('workflow') + workflowId: string; } diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts index d8b0be0c5..5324aaf63 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts index c778be493..455a7e02b 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts @@ -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; + @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; + @WorkspaceRelation({ standardId: WORKFLOW_STANDARD_FIELD_IDS.eventListeners, type: RelationMetadataType.ONE_TO_MANY, @@ -92,17 +114,6 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsNullable() eventListeners: Relation; - @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; - @WorkspaceRelation({ standardId: WORKFLOW_STANDARD_FIELD_IDS.favorites, type: RelationMetadataType.ONE_TO_MANY, @@ -114,50 +125,4 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity { }) @WorkspaceIsSystem() favorites: Relation; - - @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; - - @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; - - @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; - - @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; } diff --git a/packages/twenty-server/src/modules/workflow/common/workflow-common.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workflow-common.workspace-service.ts index a975ecd42..cfb44ea26 100644 --- a/packages/twenty-server/src/modules/workflow/common/workflow-common.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workflow-common.workspace-service.ts @@ -12,7 +12,7 @@ import { export class WorkflowCommonWorkspaceService { constructor(private readonly twentyORMManager: TwentyORMManager) {} - async getWorkflowVersion(workflowVersionId: string): Promise< + async getWorkflowVersionOrFail(workflowVersionId: string): Promise< Omit & { trigger: WorkflowTrigger; } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts index 4762a6be7..7c544974f 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts @@ -32,7 +32,7 @@ export class RunWorkflowJob { await this.workflowRunWorkspaceService.startWorkflowRun(workflowRunId); const workflowVersion = - await this.workflowCommonWorkspaceService.getWorkflowVersion( + await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail( workflowVersionId, ); diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.exception.ts index e1668045a..02f50928d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.exception.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.exception.ts @@ -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', } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts index 27ec554da..55a987bc9 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts @@ -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], }) diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts index a772d9a6f..ba71af333 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts @@ -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; diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.module.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.module.ts new file mode 100644 index 000000000..8d69223b5 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.module.ts @@ -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 {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.service.ts new file mode 100644 index 000000000..4db20d091 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/database-event-trigger.service.ts @@ -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( + '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( + 'workflowEventListener', + ); + + await workflowEventListenerRepository.delete( + { + workflowId, + }, + manager, + ); + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/listeners/database-event-trigger.listener.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts similarity index 100% rename from packages/twenty-server/src/modules/workflow/workflow-trigger/listeners/database-event-trigger.listener.ts rename to packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job.ts index 0137dd57a..465204bd4 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job.ts @@ -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( + '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, diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts similarity index 62% rename from packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid.ts rename to packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts index d1b7ea977..a75e77323 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts @@ -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 & { + 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 & { trigger: WorkflowTrigger; }, diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.exception.ts index dd1548685..a3e8c1774 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.exception.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.exception.ts @@ -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', } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts index ca8a6c83d..dbf7e6feb 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts @@ -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], diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.workspace-service.ts index a77827530..6e225bdcd 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.workspace-service.ts @@ -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( - 'workflowEventListener', - ); - - const workflowEventListener = await workflowEventListenerRepository.create({ - workflowId, - eventName, - }); - - const workspaceDataSource = await this.twentyORMManager.getDatasource(); - const workflowRepository = await this.twentyORMManager.getRepository( '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( + '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( + '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, + ); } }