From aac1314f5be44b65bbf9a607995641c9c27a6f90 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:08:24 +0200 Subject: [PATCH] [Fix] Handle deprecated viewFilterOperand in workflows run & on interface (#14837) In this PR https://github.com/twentyhq/twenty/pull/14785 we deprecated viewFilterOperand (from camelCase to capital snakeCase values), but we had not migrated the existing workflow steps values. Hence they cannot be executed! Let's fix that by dealing with both operands, new and deprecated, to mitigate the issue. Then we will add a command to migrate the values. --- .../WorkflowEditActionFilterBodyEffect.tsx | 35 +++++++---- .../1-5/1-5-migrate-views-to-core.command.ts | 2 +- ...iew-filter-operand-to-core-operand.util.ts | 26 -------- .../utils/evaluate-filter-conditions.util.ts | 44 ++++++++++---- .../twenty-shared/src/types/StepFilters.ts | 14 +++++ .../src/types/ViewFilterOperandDeprecated.ts | 19 ++++++ packages/twenty-shared/src/types/index.ts | 7 ++- ...iew-filter-operand-to-core-operand.util.ts | 59 +++++++++++++++++++ packages/twenty-shared/src/utils/index.ts | 1 + .../schemas/filter-action-settings-schema.ts | 3 +- 10 files changed, 160 insertions(+), 50 deletions(-) delete mode 100644 packages/twenty-server/src/modules/view/utils/convert-view-filter-operand-to-core-operand.util.ts create mode 100644 packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts create mode 100644 packages/twenty-shared/src/utils/filter/utils/convert-view-filter-operand-to-core-operand.util.ts diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilterBodyEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilterBodyEffect.tsx index 4b4af3778d..6905e6ef01 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilterBodyEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilterBodyEffect.tsx @@ -1,19 +1,30 @@ import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; -import { type FilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilter'; import { currentStepFilterGroupsComponentState } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/currentStepFilterGroupsComponentState'; import { currentStepFiltersComponentState } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/currentStepFiltersComponentState'; import { hasInitializedCurrentStepFilterGroupsComponentFamilyState } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/hasInitializedCurrentStepFilterGroupsComponentFamilyState'; import { hasInitializedCurrentStepFiltersComponentFamilyState } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/hasInitializedCurrentStepFiltersComponentFamilyState'; -import { useEffect } from 'react'; -import { isDefined } from 'twenty-shared/utils'; +import { useEffect, useMemo } from 'react'; +import { + type StepFilterGroup, + type StepFilterWithPotentiallyDeprecatedOperand, +} from 'twenty-shared/types'; +import { + convertViewFilterOperandToCoreOperand, + isDefined, +} from 'twenty-shared/utils'; + +type FilterSettingsWithPotentiallyDeprecatedOperand = { + stepFilterGroups?: StepFilterGroup[]; + stepFilters?: StepFilterWithPotentiallyDeprecatedOperand[]; +}; export const WorkflowEditActionFilterBodyEffect = ({ stepId, defaultValue, }: { stepId: string; - defaultValue?: FilterSettings; + defaultValue?: FilterSettingsWithPotentiallyDeprecatedOperand; }) => { const [ hasInitializedCurrentStepFilters, @@ -39,19 +50,23 @@ export const WorkflowEditActionFilterBodyEffect = ({ currentStepFilterGroupsComponentState, ); + const stepFiltersConverted = useMemo(() => { + return defaultValue?.stepFilters?.map((filter) => ({ + ...filter, + operand: convertViewFilterOperandToCoreOperand(filter.operand), + })); + }, [defaultValue?.stepFilters]); + useEffect(() => { - if ( - !hasInitializedCurrentStepFilters && - isDefined(defaultValue?.stepFilters) - ) { - setCurrentStepFilters(defaultValue.stepFilters ?? []); + if (!hasInitializedCurrentStepFilters && isDefined(stepFiltersConverted)) { + setCurrentStepFilters(stepFiltersConverted ?? []); setHasInitializedCurrentStepFilters(true); } }, [ setCurrentStepFilters, hasInitializedCurrentStepFilters, setHasInitializedCurrentStepFilters, - defaultValue?.stepFilters, + stepFiltersConverted, ]); useEffect(() => { diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts index 701bf5a258..39659105b8 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-5/1-5-migrate-views-to-core.command.ts @@ -2,6 +2,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { Command } from 'nest-commander'; import { type ViewFilterOperand as SharedViewFilterOperand } from 'twenty-shared/types'; +import { convertViewFilterOperandToCoreOperand } from 'twenty-shared/utils'; import { DataSource, In, Repository, type QueryRunner } from 'typeorm'; import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; @@ -30,7 +31,6 @@ import { type ViewFilterWorkspaceEntity } from 'src/modules/view/standard-object import { type ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; import { type ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity'; import { type ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { convertViewFilterOperandToCoreOperand } from 'src/modules/view/utils/convert-view-filter-operand-to-core-operand.util'; import { convertViewFilterWorkspaceValueToCoreValue } from 'src/modules/view/utils/convert-view-filter-workspace-value-to-core-value'; @Command({ diff --git a/packages/twenty-server/src/modules/view/utils/convert-view-filter-operand-to-core-operand.util.ts b/packages/twenty-server/src/modules/view/utils/convert-view-filter-operand-to-core-operand.util.ts deleted file mode 100644 index 6985b21f8e..0000000000 --- a/packages/twenty-server/src/modules/view/utils/convert-view-filter-operand-to-core-operand.util.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ViewFilterOperand } from 'twenty-shared/types'; - -const operandMapping: Record = { - Is: ViewFilterOperand.IS, - IsNotNull: ViewFilterOperand.IS_NOT_NULL, - IsNot: ViewFilterOperand.IS_NOT, - LessThanOrEqual: ViewFilterOperand.LESS_THAN_OR_EQUAL, - GreaterThanOrEqual: ViewFilterOperand.GREATER_THAN_OR_EQUAL, - IsBefore: ViewFilterOperand.IS_BEFORE, - IsAfter: ViewFilterOperand.IS_AFTER, - Contains: ViewFilterOperand.CONTAINS, - DoesNotContain: ViewFilterOperand.DOES_NOT_CONTAIN, - IsEmpty: ViewFilterOperand.IS_EMPTY, - IsNotEmpty: ViewFilterOperand.IS_NOT_EMPTY, - IsRelative: ViewFilterOperand.IS_RELATIVE, - IsInPast: ViewFilterOperand.IS_IN_PAST, - IsInFuture: ViewFilterOperand.IS_IN_FUTURE, - IsToday: ViewFilterOperand.IS_TODAY, - VectorSearch: ViewFilterOperand.VECTOR_SEARCH, -}; - -export const convertViewFilterOperandToCoreOperand = ( - sharedOperand: string, -): ViewFilterOperand => { - return operandMapping[sharedOperand]; -}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts index 84cd856768..ff7924fd78 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts @@ -8,23 +8,45 @@ import { type StepFilter, type StepFilterGroup, ViewFilterOperand, + type ViewFilterOperandDeprecated, } from 'twenty-shared/types'; +import { convertViewFilterOperandToCoreOperand as convertViewFilterOperandDeprecated } from 'twenty-shared/utils'; import { parseAndEvaluateRelativeDateFilter } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/utils/parse-and-evaluate-relative-date-filter.util'; -type ResolvedFilter = Omit & { +type ResolvedFilterWithPotentiallyDeprecatedOperand = Omit< + StepFilter, + 'value' | 'stepOutputKey' | 'operand' +> & { rightOperand: unknown; leftOperand: unknown; + operand: ViewFilterOperand | ViewFilterOperandDeprecated; }; -function evaluateFilter(filter: ResolvedFilter): boolean { +type ResolvedFilter = Omit< + StepFilter, + 'value' | 'stepOutputKey' | 'operand' +> & { + rightOperand: unknown; + leftOperand: unknown; + operand: ViewFilterOperand; +}; + +function evaluateFilter( + filter: ResolvedFilterWithPotentiallyDeprecatedOperand, +): boolean { + const filterWithConvertedOperand = { + ...filter, + operand: convertViewFilterOperandDeprecated(filter.operand), + }; + switch (filter.type) { case 'NUMBER': case 'NUMERIC': - return evaluateNumberFilter(filter); + return evaluateNumberFilter(filterWithConvertedOperand); case 'DATE': case 'DATE_TIME': - return evaluateDateFilter(filter); + return evaluateDateFilter(filterWithConvertedOperand); case 'TEXT': case 'MULTI_SELECT': case 'FULL_NAME': @@ -34,19 +56,19 @@ function evaluateFilter(filter: ResolvedFilter): boolean { case 'LINKS': case 'ARRAY': case 'RAW_JSON': - return evaluateTextAndArrayFilter(filter); + return evaluateTextAndArrayFilter(filterWithConvertedOperand); case 'SELECT': - return evaluateSelectFilter(filter); + return evaluateSelectFilter(filterWithConvertedOperand); case 'BOOLEAN': - return evaluateBooleanFilter(filter); + return evaluateBooleanFilter(filterWithConvertedOperand); case 'UUID': - return evaluateUuidFilter(filter); + return evaluateUuidFilter(filterWithConvertedOperand); case 'RELATION': - return evaluateRelationFilter(filter); + return evaluateRelationFilter(filterWithConvertedOperand); case 'CURRENCY': - return evaluateCurrencyFilter(filter); + return evaluateCurrencyFilter(filterWithConvertedOperand); default: - return evaluateDefaultFilter(filter); + return evaluateDefaultFilter(filterWithConvertedOperand); } } diff --git a/packages/twenty-shared/src/types/StepFilters.ts b/packages/twenty-shared/src/types/StepFilters.ts index 145a276bc0..8bb9a918b4 100644 --- a/packages/twenty-shared/src/types/StepFilters.ts +++ b/packages/twenty-shared/src/types/StepFilters.ts @@ -1,3 +1,4 @@ +import { type ViewFilterOperandDeprecated } from '@/types/ViewFilterOperandDeprecated'; import { type ViewFilterOperand } from './ViewFilterOperand'; export enum StepLogicalOperator { @@ -24,3 +25,16 @@ export type StepFilter = { compositeFieldSubFieldName?: string; isFullRecord?: boolean; }; + +export type StepFilterWithPotentiallyDeprecatedOperand = { + id: string; + type: string; + stepOutputKey: string; + operand: ViewFilterOperand | ViewFilterOperandDeprecated; + value: string; + stepFilterGroupId: string; + positionInStepFilterGroup?: number; + fieldMetadataId?: string; + compositeFieldSubFieldName?: string; + isFullRecord?: boolean; +}; diff --git a/packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts b/packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts new file mode 100644 index 0000000000..04c38a1c21 --- /dev/null +++ b/packages/twenty-shared/src/types/ViewFilterOperandDeprecated.ts @@ -0,0 +1,19 @@ +export enum ViewFilterOperandDeprecated { + Is = 'is', + IsNotNull = 'isNotNull', + IsNot = 'isNot', + LessThanOrEqual = 'lessThanOrEqual', + GreaterThanOrEqual = 'greaterThanOrEqual', + IsBefore = 'isBefore', + IsAfter = 'isAfter', + Contains = 'contains', + DoesNotContain = 'doesNotContain', + IsEmpty = 'isEmpty', + IsNotEmpty = 'isNotEmpty', + IsRelative = 'isRelative', + IsInPast = 'isInPast', + IsInFuture = 'isInFuture', + IsToday = 'isToday', + VectorSearch = 'vectorSearch', +} + \ No newline at end of file diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index a45d030bce..2516e89fd3 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -82,7 +82,12 @@ export { DEFAULT_RELATIVE_DATE_VALUE } from './RelativeDateValue'; export type { RestrictedFieldPermissions } from './RestrictedFieldPermissions'; export type { RestrictedFieldsPermissions } from './RestrictedFieldsPermissions'; export { SettingsPath } from './SettingsPath'; -export type { StepFilterGroup, StepFilter } from './StepFilters'; +export type { + StepFilterGroup, + StepFilter, + StepFilterWithPotentiallyDeprecatedOperand, +} from './StepFilters'; export { StepLogicalOperator } from './StepFilters'; export { TwoFactorAuthenticationStrategy } from './TwoFactorAuthenticationStrategy'; export { ViewFilterOperand } from './ViewFilterOperand'; +export { ViewFilterOperandDeprecated } from './ViewFilterOperandDeprecated'; diff --git a/packages/twenty-shared/src/utils/filter/utils/convert-view-filter-operand-to-core-operand.util.ts b/packages/twenty-shared/src/utils/filter/utils/convert-view-filter-operand-to-core-operand.util.ts new file mode 100644 index 0000000000..51755bb46d --- /dev/null +++ b/packages/twenty-shared/src/utils/filter/utils/convert-view-filter-operand-to-core-operand.util.ts @@ -0,0 +1,59 @@ +import { ViewFilterOperand, ViewFilterOperandDeprecated } from '@/types'; + +const operandMapping: Record = { + Is: ViewFilterOperand.IS, + IsNotNull: ViewFilterOperand.IS_NOT_NULL, + IsNot: ViewFilterOperand.IS_NOT, + LessThanOrEqual: ViewFilterOperand.LESS_THAN_OR_EQUAL, + GreaterThanOrEqual: ViewFilterOperand.GREATER_THAN_OR_EQUAL, + IsBefore: ViewFilterOperand.IS_BEFORE, + IsAfter: ViewFilterOperand.IS_AFTER, + Contains: ViewFilterOperand.CONTAINS, + DoesNotContain: ViewFilterOperand.DOES_NOT_CONTAIN, + IsEmpty: ViewFilterOperand.IS_EMPTY, + IsNotEmpty: ViewFilterOperand.IS_NOT_EMPTY, + IsRelative: ViewFilterOperand.IS_RELATIVE, + IsInPast: ViewFilterOperand.IS_IN_PAST, + IsInFuture: ViewFilterOperand.IS_IN_FUTURE, + IsToday: ViewFilterOperand.IS_TODAY, + VectorSearch: ViewFilterOperand.VECTOR_SEARCH, + [ViewFilterOperandDeprecated.Is]: ViewFilterOperand.IS, + [ViewFilterOperandDeprecated.IsNotNull]: ViewFilterOperand.IS_NOT_NULL, + [ViewFilterOperandDeprecated.IsNot]: ViewFilterOperand.IS_NOT, + [ViewFilterOperandDeprecated.LessThanOrEqual]: ViewFilterOperand.LESS_THAN_OR_EQUAL, + [ViewFilterOperandDeprecated.GreaterThanOrEqual]: ViewFilterOperand.GREATER_THAN_OR_EQUAL, + [ViewFilterOperandDeprecated.IsBefore]: ViewFilterOperand.IS_BEFORE, + [ViewFilterOperandDeprecated.IsAfter]: ViewFilterOperand.IS_AFTER, + [ViewFilterOperandDeprecated.Contains]: ViewFilterOperand.CONTAINS, + [ViewFilterOperandDeprecated.DoesNotContain]: ViewFilterOperand.DOES_NOT_CONTAIN, + [ViewFilterOperandDeprecated.IsEmpty]: ViewFilterOperand.IS_EMPTY, + [ViewFilterOperandDeprecated.IsNotEmpty]: ViewFilterOperand.IS_NOT_EMPTY, + [ViewFilterOperandDeprecated.IsRelative]: ViewFilterOperand.IS_RELATIVE, + [ViewFilterOperandDeprecated.IsInPast]: ViewFilterOperand.IS_IN_PAST, + [ViewFilterOperandDeprecated.IsInFuture]: ViewFilterOperand.IS_IN_FUTURE, + [ViewFilterOperandDeprecated.IsToday]: ViewFilterOperand.IS_TODAY, + [ViewFilterOperandDeprecated.VectorSearch]: ViewFilterOperand.VECTOR_SEARCH, + [ViewFilterOperand.IS]: ViewFilterOperand.IS, + [ViewFilterOperand.IS_NOT_NULL]: ViewFilterOperand.IS_NOT_NULL, + [ViewFilterOperand.IS_NOT]: ViewFilterOperand.IS_NOT, + [ViewFilterOperand.LESS_THAN_OR_EQUAL]: ViewFilterOperand.LESS_THAN_OR_EQUAL, + [ViewFilterOperand.GREATER_THAN_OR_EQUAL]: + ViewFilterOperand.GREATER_THAN_OR_EQUAL, + [ViewFilterOperand.IS_BEFORE]: ViewFilterOperand.IS_BEFORE, + [ViewFilterOperand.IS_AFTER]: ViewFilterOperand.IS_AFTER, + [ViewFilterOperand.CONTAINS]: ViewFilterOperand.CONTAINS, + [ViewFilterOperand.DOES_NOT_CONTAIN]: ViewFilterOperand.DOES_NOT_CONTAIN, + [ViewFilterOperand.IS_EMPTY]: ViewFilterOperand.IS_EMPTY, + [ViewFilterOperand.IS_NOT_EMPTY]: ViewFilterOperand.IS_NOT_EMPTY, + [ViewFilterOperand.IS_RELATIVE]: ViewFilterOperand.IS_RELATIVE, + [ViewFilterOperand.IS_IN_PAST]: ViewFilterOperand.IS_IN_PAST, + [ViewFilterOperand.IS_IN_FUTURE]: ViewFilterOperand.IS_IN_FUTURE, + [ViewFilterOperand.IS_TODAY]: ViewFilterOperand.IS_TODAY, + [ViewFilterOperand.VECTOR_SEARCH]: ViewFilterOperand.VECTOR_SEARCH, +}; + +export const convertViewFilterOperandToCoreOperand = ( + sharedOperand: string, +): ViewFilterOperand => { + return operandMapping[sharedOperand]; +}; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index 0e5d49165f..33b5860f68 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -36,6 +36,7 @@ export type { export { turnRecordFilterGroupsIntoGqlOperationFilter } from './filter/turnRecordFilterGroupIntoGqlOperationFilter'; export { turnRecordFilterIntoRecordGqlOperationFilter } from './filter/turnRecordFilterIntoGqlOperationFilter'; export { combineFilters } from './filter/utils/combineFilters'; +export { convertViewFilterOperandToCoreOperand } from './filter/utils/convert-view-filter-operand-to-core-operand.util'; export { convertViewFilterValueToString } from './filter/utils/convertViewFilterValueToString'; export { createAnyFieldRecordFilterBaseProperties } from './filter/utils/createAnyFieldRecordFilterBaseProperties'; export { diff --git a/packages/twenty-shared/src/workflow/schemas/filter-action-settings-schema.ts b/packages/twenty-shared/src/workflow/schemas/filter-action-settings-schema.ts index 4393a9a6bc..4c7da8b800 100644 --- a/packages/twenty-shared/src/workflow/schemas/filter-action-settings-schema.ts +++ b/packages/twenty-shared/src/workflow/schemas/filter-action-settings-schema.ts @@ -1,3 +1,4 @@ +import { ViewFilterOperandDeprecated } from '@/types'; import { z } from 'zod'; import { StepLogicalOperator } from '../../types/StepFilters'; import { ViewFilterOperand } from '../../types/ViewFilterOperand'; @@ -19,7 +20,7 @@ export const workflowFilterActionSettingsSchema = id: z.string(), type: z.string(), stepOutputKey: z.string(), - operand: z.enum(ViewFilterOperand), + operand: z.enum(ViewFilterOperand).or(z.enum(ViewFilterOperandDeprecated)), value: z.string(), stepFilterGroupId: z.string(), positionInStepFilterGroup: z.number().optional(),