diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts index 884966e54e..05aa762ead 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts @@ -45,9 +45,9 @@ export class WorkflowTriggerResolver { @Args('workflowVersionId', { type: () => UUIDScalarType }) workflowVersionId: string, ) { - return this.workflowTriggerWorkspaceService.activateWorkflowVersion({ + return this.workflowTriggerWorkspaceService.activateWorkflowVersion( workflowVersionId, - }); + ); } @Mutation(() => Boolean) diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts index da2c6bec25..767f3d4f27 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts @@ -67,14 +67,28 @@ export class WorkflowCommonWorkspaceService { }, }); + return this.getValidWorkflowVersionOrFail(workflowVersion); + } + + async getValidWorkflowVersionOrFail( + workflowVersion: WorkflowVersionWorkspaceEntity | null, + ): Promise { if (!workflowVersion) { throw new WorkflowTriggerException( 'Workflow version not found', - WorkflowTriggerExceptionCode.NOT_FOUND, + WorkflowTriggerExceptionCode.INVALID_INPUT, ); } - return workflowVersion; + // FIXME: For now we will make the trigger optional. Later, we'll have to ensure the trigger is defined when publishing the flow. + // if (!workflowVersion.trigger) { + // throw new WorkflowTriggerException( + // 'Workflow version does not contains trigger', + // WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, + // ); + // } + + return { ...workflowVersion, trigger: workflowVersion.trigger }; } async getObjectMetadataMaps( diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts index c9c4d3284f..307b201dbd 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts @@ -128,9 +128,9 @@ This is the most efficient way for AI to create workflows as it handles all the } if (parameters.activate) { - await this.workflowTriggerService.activateWorkflowVersion({ + await this.workflowTriggerService.activateWorkflowVersion( workflowVersionId, - }); + ); await this.updateWorkflowStatus({ workspaceId, @@ -340,14 +340,11 @@ This is the most efficient way for AI to create workflows as it handles all the description: 'Activate a workflow version. This makes the workflow version active and available for execution.', inputSchema: activateWorkflowVersionSchema, - execute: async (parameters: { - workflowVersionId: string; - workflowId: string; - }) => { + execute: async (parameters: { workflowVersionId: string }) => { try { - return await this.workflowTriggerService.activateWorkflowVersion({ - workflowVersionId: parameters.workflowVersionId, - }); + return await this.workflowTriggerService.activateWorkflowVersion( + parameters.workflowVersionId, + ); } catch (error) { return { success: false, diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts index 85870a3137..9645e401ea 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util.ts @@ -4,6 +4,7 @@ import { WorkflowVersionStatus, type WorkflowVersionWorkspaceEntity, } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; +import { type WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { type WorkflowAction, WorkflowActionType, @@ -17,20 +18,25 @@ import { assertFormStepIsValid } from 'src/modules/workflow/workflow-trigger/uti export function assertVersionCanBeActivated( workflowVersion: WorkflowVersionWorkspaceEntity, + workflow: WorkflowWorkspaceEntity, ) { assertVersionIsValid(workflowVersion); + const isLastPublishedVersion = + workflow.lastPublishedVersionId === workflowVersion.id; + const isDraft = workflowVersion.status === WorkflowVersionStatus.DRAFT; - const isDeactivated = - workflowVersion.status === WorkflowVersionStatus.DEACTIVATED; + const isLastPublishedVersionDeactivated = + workflowVersion.status === WorkflowVersionStatus.DEACTIVATED && + isLastPublishedVersion; - if (!isDraft && !isDeactivated) { + if (!isDraft && !isLastPublishedVersionDeactivated) { throw new WorkflowTriggerException( - 'Cannot activate non-draft or non-deactivated version', + 'Cannot activate non-draft or non-last-published version', WorkflowTriggerExceptionCode.INVALID_INPUT, { - userFriendlyMessage: msg`Cannot activate non-draft or non-deactivated version`, + userFriendlyMessage: msg`Cannot activate non-draft or non-last-published version`, }, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts index b34db13921..1ce8241e52 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { msg } from '@lingui/core/macro'; -import { Not } from 'typeorm'; import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; @@ -79,19 +78,24 @@ export class WorkflowTriggerWorkspaceService { }); } - async activateWorkflowVersion({ - workflowVersionId, - }: { - workflowVersionId: string; - }) { + async activateWorkflowVersion(workflowVersionId: string) { const workspaceId = this.getWorkspaceId(); const workflowVersionRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, + this.getWorkspaceId(), 'workflowVersion', { shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level ); + const workflowVersionNullable = await workflowVersionRepository.findOne({ + where: { id: workflowVersionId }, + }); + + const workflowVersion = + await this.workflowCommonWorkspaceService.getValidWorkflowVersionOrFail( + workflowVersionNullable, + ); + const workflowRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, @@ -99,61 +103,25 @@ export class WorkflowTriggerWorkspaceService { { shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level ); - const workflowVersionToActivate = await workflowVersionRepository.findOne({ - where: { id: workflowVersionId }, - select: { - id: true, - status: true, - workflowId: true, - steps: { - id: true, - type: true, - }, - trigger: { - type: true, - }, - }, + const workflow = await workflowRepository.findOne({ + where: { id: workflowVersion.workflowId }, }); - if (!workflowVersionToActivate) { + if (!workflow) { throw new WorkflowTriggerException( - 'Workflow version not found', - WorkflowTriggerExceptionCode.NOT_FOUND, + 'No workflow found', + WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION, ); } - assertVersionCanBeActivated(workflowVersionToActivate); + assertVersionCanBeActivated(workflowVersion, workflow); - const workflowId = workflowVersionToActivate.workflowId; - - const allWorkflowVersions = await workflowVersionRepository.find({ - where: { - workflowId: workflowVersionToActivate.workflowId, - status: Not(WorkflowVersionStatus.ARCHIVED), - }, - select: { id: true, status: true, trigger: { type: true } }, - }); - - const currentPublishedVersions = allWorkflowVersions.filter( - (version) => - version.status === WorkflowVersionStatus.ACTIVE || - version.status === WorkflowVersionStatus.DEACTIVATED, - ); - - if (currentPublishedVersions.length > 1) { - throw new WorkflowTriggerException( - 'Multiple published versions found', - WorkflowTriggerExceptionCode.INTERNAL_ERROR, - ); - } - - await this.performActivationSteps({ - workflowId, - workflowVersionToActivate, - currentPublishedVersion: currentPublishedVersions?.[0], + await this.performActivationSteps( + workflow, + workflowVersion, workflowRepository, workflowVersionRepository, - }); + ); return true; } @@ -181,57 +149,49 @@ export class WorkflowTriggerWorkspaceService { ); } - private async performActivationSteps({ - workflowId, - workflowVersionToActivate, - currentPublishedVersion, - workflowRepository, - workflowVersionRepository, - }: { - workflowId: string; - workflowVersionToActivate: WorkflowVersionWorkspaceEntity; - currentPublishedVersion: WorkflowVersionWorkspaceEntity | undefined; - workflowRepository: WorkspaceRepository; - workflowVersionRepository: WorkspaceRepository; - }) { + private async performActivationSteps( + workflow: WorkflowWorkspaceEntity, + workflowVersion: WorkflowVersionWorkspaceEntity, + workflowRepository: WorkspaceRepository, + workflowVersionRepository: WorkspaceRepository, + ) { if ( - currentPublishedVersion && - currentPublishedVersion.id !== workflowVersionToActivate.id + workflow.lastPublishedVersionId && + workflowVersion.id !== workflow.lastPublishedVersionId ) { - await this.disableTrigger(currentPublishedVersion); - await workflowVersionRepository.update( - { id: currentPublishedVersion.id }, - { status: WorkflowVersionStatus.ARCHIVED }, + await this.performDeactivationSteps( + workflow.lastPublishedVersionId, + workflowVersionRepository, ); } - await this.setActiveVersionStatus( - workflowVersionToActivate, + await this.upgradeWorkflowVersion( + workflow, + workflowVersion.id, + workflowRepository, workflowVersionRepository, ); - await this.enableTrigger(workflowVersionToActivate); - - await workflowRepository.update( - { id: workflowId }, - { lastPublishedVersionId: workflowVersionToActivate.id }, + await this.setActiveVersionStatus( + workflowVersion, + workflowVersionRepository, ); + + await this.enableTrigger(workflowVersion); } private async performDeactivationSteps( workflowVersionId: string, workflowVersionRepository: WorkspaceRepository, ) { - const workflowVersion = await workflowVersionRepository.findOne({ + const workflowVersionNullable = await workflowVersionRepository.findOne({ where: { id: workflowVersionId }, }); - if (!workflowVersion) { - throw new WorkflowTriggerException( - 'Workflow version not found', - WorkflowTriggerExceptionCode.NOT_FOUND, + const workflowVersion = + await this.workflowCommonWorkspaceService.getValidWorkflowVersionOrFail( + workflowVersionNullable, ); - } if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { return; @@ -249,6 +209,23 @@ export class WorkflowTriggerWorkspaceService { workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersionRepository: WorkspaceRepository, ) { + const activeWorkflowVersions = await workflowVersionRepository.find({ + where: { + workflowId: workflowVersion.workflowId, + status: WorkflowVersionStatus.ACTIVE, + }, + }); + + if (activeWorkflowVersions.length > 0) { + throw new WorkflowTriggerException( + 'Cannot have more than one active workflow version', + WorkflowTriggerExceptionCode.FORBIDDEN, + { + userFriendlyMessage: msg`Cannot have more than one active workflow version`, + }, + ); + } + await workflowVersionRepository.update( { id: workflowVersion.id }, { status: WorkflowVersionStatus.ACTIVE }, @@ -287,6 +264,29 @@ export class WorkflowTriggerWorkspaceService { ); } + private async upgradeWorkflowVersion( + workflow: WorkflowWorkspaceEntity, + newPublishedVersionId: string, + workflowRepository: WorkspaceRepository, + workflowVersionRepository: WorkspaceRepository, + ) { + if (workflow.lastPublishedVersionId === newPublishedVersionId) { + return; + } + + if (workflow.lastPublishedVersionId) { + await workflowVersionRepository.update( + { id: workflow.lastPublishedVersionId }, + { status: WorkflowVersionStatus.ARCHIVED }, + ); + } + + await workflowRepository.update( + { id: workflow.id }, + { lastPublishedVersionId: newPublishedVersionId }, + ); + } + private async enableTrigger(workflowVersion: WorkflowVersionWorkspaceEntity) { assertWorkflowVersionTriggerIsDefined(workflowVersion);