From ebc6bbabafc97dab40b2da4457f0ace8fc8c3726 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:45:44 +0200 Subject: [PATCH] [Fix] Command to migrate operand values for workflows (#14849) In [this PR](https://github.com/twentyhq/twenty/pull/14785) we got rid of what we now call ViewFilterOperandDeprecated, a camelCase version of ViewFilterOperand, which we thought we only used in the FE. We did not notice that this enum was used to persist filters used in workflows, reflected in workflowVersion and workflowRun. As a result workflow runs were broken. [In this mitigation PR](https://github.com/twentyhq/twenty/pull/14837) (and [this one](https://github.com/twentyhq/twenty/pull/14841)) we updated the code handle both enum values from ViewFilterOperandDeprecated and ViewFilterOperand, but we still want to get rid of ViewFilterOperandDeprecated. the command in this PR replaces the occurences of enum values of ViewFilterOperandDeprecated. When this has been merged, deployed and run on the workspaces, we will be able to remove ViewFilterOperandDeprecated altogether; that will have to be done in 1.10 though not before. --- ...rate-workflow-step-filter-operand-value.ts | 210 ++++++++++++++++++ .../1-10-upgrade-version-command.module.ts | 13 ++ .../upgrade-version-command.module.ts | 2 + .../upgrade.command.ts | 10 + .../src/types/ViewFilterOperandDeprecated.ts | 2 +- 5 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-workflow-step-filter-operand-value.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-workflow-step-filter-operand-value.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-workflow-step-filter-operand-value.ts new file mode 100644 index 0000000000..874a444c30 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-workflow-step-filter-operand-value.ts @@ -0,0 +1,210 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { isString } from 'class-validator'; +import { Command } from 'nest-commander'; +import { + convertViewFilterOperandToCoreOperand, + isDefined, +} from 'twenty-shared/utils'; +import { In, Raw, Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + type RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { + WorkflowRunState, + WorkflowRunWorkspaceEntity, +} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; +import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; +import { isWorkflowFilterAction } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/guards/is-workflow-filter-action.guard'; +import { isWorkflowFindRecordsAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/guards/is-workflow-find-records-action.guard'; +import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; +@Command({ + name: 'upgrade:1-10:migrate-workflow-step-filter-operand-value', + description: + 'Migrate workflowVersion.steps[].settings.input.stepFilters[].operand to use new operand enum', +}) +export class MigrateWorkflowStepFilterOperandValueCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + workspaceId, + index, + total, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `[${index + 1}/${total}] Migrating workflow step filter operand values for workspace ${workspaceId}`, + ); + + // workflowVersions + const workflowVersionRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, + ); + + const workflowVersionsToMigrate = await workflowVersionRepository.find({ + where: [ + { + steps: Raw( + (_alias) => `"workflowVersion"."steps"::text LIKE :search`, + { + search: '%operand%', + }, + ), + }, + ], + }); + + this.logger.log( + `Found ${workflowVersionsToMigrate.length} workflowVersions to migrate`, + ); + + for (const workflowVersion of workflowVersionsToMigrate) { + let steps: WorkflowAction[] | null | undefined; + + steps = workflowVersion.steps; + + let hasChanged = false; + + for (const step of steps ?? []) { + if (isWorkflowFilterAction(step)) { + for (const filter of step.settings.input.stepFilters ?? []) { + if (isDefined(filter.operand) && isString(filter.operand)) { + const newOperand = convertViewFilterOperandToCoreOperand( + filter.operand, + ); + + if (newOperand && newOperand !== filter.operand) { + filter.operand = newOperand; + hasChanged = true; + } + } + } + } + if (isWorkflowFindRecordsAction(step)) { + for (const filter of step.settings.input.filter?.recordFilters ?? + []) { + if (isString(filter.operand)) { + const newOperand = convertViewFilterOperandToCoreOperand( + filter.operand, + ); + + if (newOperand && newOperand !== filter.operand) { + filter.operand = newOperand; + hasChanged = true; + } + } + } + } + } + + if (hasChanged) { + this.logger.log( + `${options.dryRun ? 'DRY RUN - Would be' : ''}Updating workflowVersion ${workflowVersion.id} in workspace ${workspaceId}`, + ); + + if (!options.dryRun) { + await workflowVersionRepository.update( + { id: workflowVersion.id }, + { steps }, + ); + } + + this.logger.log( + `${options.dryRun ? 'DRY RUN - Would have' : ''} Updated workflowVersion ${workflowVersion.id} in workspace ${workspaceId}`, + ); + } + } + + //workflowRuns + const workflowRunRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'workflowRun', + { shouldBypassPermissionChecks: true }, + ); + + const workflowRunsToMigrate = await workflowRunRepository.find({ + where: { + workflowVersionId: In( + workflowVersionsToMigrate.map( + (workflowVersion) => workflowVersion.id, + ), + ), + }, + }); + + this.logger.log( + `Found ${workflowRunsToMigrate.length} workflowRuns to migrate`, + ); + + for (const workflowRun of workflowRunsToMigrate) { + let state: WorkflowRunState | null | undefined = workflowRun.state; + + if (!isDefined(state)) { + continue; + } + + let hasChanged = false; + + for (const step of state.flow.steps) { + if (isWorkflowFindRecordsAction(step)) { + const filter = step.settings.input.filter; + + for (const recordFilter of filter?.recordFilters ?? []) { + if (isString(recordFilter.operand)) { + const newOperand = convertViewFilterOperandToCoreOperand( + recordFilter.operand, + ); + + if (newOperand && newOperand !== recordFilter.operand) { + recordFilter.operand = newOperand; + hasChanged = true; + } + } + } + } + if (isWorkflowFilterAction(step)) { + for (const filter of step.settings.input.stepFilters ?? []) { + if (isString(filter.operand)) { + const newOperand = convertViewFilterOperandToCoreOperand( + filter.operand, + ); + + if (newOperand && newOperand !== filter.operand) { + filter.operand = newOperand; + hasChanged = true; + } + } + } + } + } + + if (hasChanged) { + this.logger.log( + `${options.dryRun ? 'DRY RUN - Would be' : ''}Updating workflowRun ${workflowRun.id} in workspace ${workspaceId}`, + ); + + if (!options.dryRun) { + await workflowRunRepository.update({ id: workflowRun.id }, { state }); + } + + this.logger.log( + `${options.dryRun ? 'DRY RUN - Would have' : ''}Updated workflowRun ${workflowRun.id} in workspace ${workspaceId}`, + ); + } + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts new file mode 100644 index 0000000000..7b1f525163 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { MigrateWorkflowStepFilterOperandValueCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-workflow-step-filter-operand-value'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; + +@Module({ + imports: [TypeOrmModule.forFeature([Workspace]), WorkspaceDataSourceModule], + providers: [MigrateWorkflowStepFilterOperandValueCommand], + exports: [MigrateWorkflowStepFilterOperandValueCommand], +}) +export class V1_10_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts index 4ab8f43180..47445b7df3 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { V0_54_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-54/0-54-upgrade-version-command.module'; import { V0_55_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-55/0-55-upgrade-version-command.module'; import { V1_1_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module'; +import { V1_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module'; import { V1_2_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-2/1-2-upgrade-version-command.module'; import { V1_3_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module'; import { V1_5_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-5/1-5-upgrade-version-command.module'; @@ -24,6 +25,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp V1_5_UpgradeVersionCommandModule, V1_6_UpgradeVersionCommandModule, V1_7_UpgradeVersionCommandModule, + V1_10_UpgradeVersionCommandModule, WorkspaceSyncMetadataModule, ], providers: [UpgradeCommand], diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index 3d13e1ac61..9d952e0524 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -19,6 +19,7 @@ import { AddEnqueuedStatusToWorkflowRunCommand } from 'src/database/commands/upg import { FixSchemaArrayTypeCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command'; import { FixUpdateStandardFieldsIsLabelSyncedWithName } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command'; import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command'; +import { MigrateWorkflowStepFilterOperandValueCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-workflow-step-filter-operand-value'; import { AddEnqueuedStatusToWorkflowRunV2Command } from 'src/database/commands/upgrade-version-command/1-2/1-2-add-enqueued-status-to-workflow-run-v2.command'; import { AddNextStepIdsToWorkflowVersionTriggers } from 'src/database/commands/upgrade-version-command/1-2/1-2-add-next-step-ids-to-workflow-version-triggers.command'; import { RemoveWorkflowRunsWithoutState } from 'src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command'; @@ -83,6 +84,9 @@ export class UpgradeCommand extends UpgradeCommandRunner { // 1.7 Commands protected readonly backfillWorkflowManualTriggerAvailabilityCommand: BackfillWorkflowManualTriggerAvailabilityCommand, + + // 1.10 Commands + protected readonly migrateWorkflowStepFilterOperandValueCommand: MigrateWorkflowStepFilterOperandValueCommand, ) { super( workspaceRepository, @@ -176,6 +180,11 @@ export class UpgradeCommand extends UpgradeCommandRunner { afterSyncMetadata: [], }; + const commands_1100: VersionCommands = { + beforeSyncMetadata: [this.migrateWorkflowStepFilterOperandValueCommand], + afterSyncMetadata: [], + }; + this.allCommands = { '0.53.0': commands_053, '0.54.0': commands_054, @@ -189,6 +198,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { '1.5.0': commands_150, '1.6.0': commands_160, '1.7.0': commands_170, + '1.10.0': commands_1100, }; } diff --git a/packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts b/packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts index 813f41acac..dea35cfa8f 100644 --- a/packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts +++ b/packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts @@ -14,5 +14,5 @@ export enum ViewFilterOperandDeprecated { IsInPast = 'isInPast', IsInFuture = 'isInFuture', IsToday = 'isToday', - VectorSearch = 'vectorSearch', + VectorSearch = 'search', }