[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.
This commit is contained in:
Marie
2025-10-02 13:08:24 +02:00
committed by GitHub
parent 8098391c21
commit aac1314f5b
10 changed files with 160 additions and 50 deletions
@@ -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(() => {
@@ -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({
@@ -1,26 +0,0 @@
import { ViewFilterOperand } from 'twenty-shared/types';
const operandMapping: Record<string, ViewFilterOperand> = {
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];
};
@@ -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<StepFilter, 'value' | 'stepOutputKey'> & {
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);
}
}
@@ -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;
};
@@ -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',
}
+6 -1
View File
@@ -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';
@@ -0,0 +1,59 @@
import { ViewFilterOperand, ViewFilterOperandDeprecated } from '@/types';
const operandMapping: Record<string, ViewFilterOperand> = {
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];
};
@@ -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 {
@@ -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(),