Use view filters operands in step filters + migrate to twenty-shared (#13137)
Step operand will more or less be the same as view filter operand. This PR: - moves `ViewFilterOperand` to twenty-shared - use it as step operand - check what operand should be available based on the selected field type in filter action - rewrite the function that evaluates filters so it uses ViewFilterOperand instead ViewFilterOperand may be renamed in a future PR.
This commit is contained in:
+15
-16
@@ -10,7 +10,7 @@ import {
|
||||
StepFilter,
|
||||
StepFilterGroup,
|
||||
StepLogicalOperator,
|
||||
StepOperand,
|
||||
ViewFilterOperand,
|
||||
} from 'twenty-shared/src/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconLibraryPlus, IconPlus } from 'twenty-ui/display';
|
||||
@@ -21,6 +21,17 @@ type WorkflowStepFilterAddFilterRuleSelectProps = {
|
||||
stepFilterGroup: StepFilterGroup;
|
||||
};
|
||||
|
||||
const BASE_NEW_STEP_FILTER = {
|
||||
id: v4(),
|
||||
type: 'unknown',
|
||||
label: '',
|
||||
value: '',
|
||||
operand: ViewFilterOperand.Is,
|
||||
displayValue: '',
|
||||
stepFilterGroupId: '',
|
||||
stepOutputKey: '',
|
||||
};
|
||||
|
||||
export const WorkflowStepFilterAddFilterRuleSelect = ({
|
||||
stepFilterGroup,
|
||||
}: WorkflowStepFilterAddFilterRuleSelectProps) => {
|
||||
@@ -42,16 +53,10 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({
|
||||
closeDropdown(dropdownId);
|
||||
|
||||
const newStepFilter = {
|
||||
id: v4(),
|
||||
type: 'text',
|
||||
label: 'New Filter',
|
||||
value: '',
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: '',
|
||||
...BASE_NEW_STEP_FILTER,
|
||||
stepFilterGroupId: stepFilterGroup.id,
|
||||
stepOutputKey: '',
|
||||
positionInStepFilterGroup: newPositionInStepFilterGroup,
|
||||
};
|
||||
} satisfies StepFilter;
|
||||
|
||||
upsertStepFilterSettings({
|
||||
stepFilterToUpsert: newStepFilter,
|
||||
@@ -71,15 +76,9 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({
|
||||
};
|
||||
|
||||
const newStepFilter: StepFilter = {
|
||||
id: v4(),
|
||||
type: 'text',
|
||||
operand: StepOperand.EQ,
|
||||
value: '',
|
||||
displayValue: '',
|
||||
...BASE_NEW_STEP_FILTER,
|
||||
stepFilterGroupId: newStepFilterGroupId,
|
||||
positionInStepFilterGroup: 1,
|
||||
label: 'New Filter',
|
||||
stepOutputKey: '',
|
||||
};
|
||||
|
||||
upsertStepFilterSettings({
|
||||
|
||||
+37
-11
@@ -8,7 +8,7 @@ import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/
|
||||
import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import { StepFilter } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -30,6 +30,7 @@ export const WorkflowStepFilterFieldSelect = ({
|
||||
rawVariableName: stepFilter.stepOutputKey,
|
||||
part: 'stepId',
|
||||
});
|
||||
|
||||
const stepsOutputSchema = useRecoilValue(
|
||||
stepsOutputSchemaFamilySelector({
|
||||
workflowVersionId,
|
||||
@@ -37,6 +38,41 @@ export const WorkflowStepFilterFieldSelect = ({
|
||||
}),
|
||||
);
|
||||
|
||||
const handleChange = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(variableName: string) => {
|
||||
const stepId = extractRawVariableNamePart({
|
||||
rawVariableName: variableName,
|
||||
part: 'stepId',
|
||||
});
|
||||
const currentStepOutputSchema = snapshot
|
||||
.getLoadable(
|
||||
stepsOutputSchemaFamilySelector({
|
||||
workflowVersionId,
|
||||
stepIds: [stepId],
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const { variableLabel, variableType } =
|
||||
searchVariableThroughOutputSchema({
|
||||
stepOutputSchema: currentStepOutputSchema?.[0],
|
||||
rawVariableName: variableName,
|
||||
isFullRecord: false,
|
||||
});
|
||||
|
||||
upsertStepFilterSettings({
|
||||
stepFilterToUpsert: {
|
||||
...stepFilter,
|
||||
stepOutputKey: variableName,
|
||||
displayValue: variableLabel ?? '',
|
||||
type: variableType ?? 'unknown',
|
||||
},
|
||||
});
|
||||
},
|
||||
[upsertStepFilterSettings, stepFilter, workflowVersionId],
|
||||
);
|
||||
|
||||
if (!isDefined(stepId)) {
|
||||
return null;
|
||||
}
|
||||
@@ -50,16 +86,6 @@ export const WorkflowStepFilterFieldSelect = ({
|
||||
const isSelectedFieldNotFound = !isDefined(variableLabel);
|
||||
const label = isSelectedFieldNotFound ? t`No Field Selected` : variableLabel;
|
||||
|
||||
const handleChange = (variableName: string) => {
|
||||
upsertStepFilterSettings({
|
||||
stepFilterToUpsert: {
|
||||
...stepFilter,
|
||||
stepOutputKey: variableName,
|
||||
displayValue: label,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<WorkflowVariablesDropdown
|
||||
instanceId={`step-filter-field-${stepFilter.id}`}
|
||||
|
||||
+11
-16
@@ -1,36 +1,31 @@
|
||||
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
|
||||
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
|
||||
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
|
||||
import { getViewFilterOperands } from '@/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands';
|
||||
import { useContext } from 'react';
|
||||
import { StepFilter, StepOperand } from 'twenty-shared/src/types';
|
||||
import { StepFilter, ViewFilterOperand } from 'twenty-shared/src/types';
|
||||
|
||||
type WorkflowStepFilterOperandSelectProps = {
|
||||
stepFilter: StepFilter;
|
||||
};
|
||||
|
||||
const STEP_OPERAND_OPTIONS = [
|
||||
{ value: StepOperand.EQ, label: 'Equals' },
|
||||
{ value: StepOperand.NE, label: 'Not equals' },
|
||||
{ value: StepOperand.GT, label: 'Greater than' },
|
||||
{ value: StepOperand.GTE, label: 'Greater than or equal' },
|
||||
{ value: StepOperand.LT, label: 'Less than' },
|
||||
{ value: StepOperand.LTE, label: 'Less than or equal' },
|
||||
{ value: StepOperand.LIKE, label: 'Contains' },
|
||||
{ value: StepOperand.ILIKE, label: 'Contains (case insensitive)' },
|
||||
{ value: StepOperand.IN, label: 'In' },
|
||||
{ value: StepOperand.IS, label: 'Is' },
|
||||
];
|
||||
|
||||
export const WorkflowStepFilterOperandSelect = ({
|
||||
stepFilter,
|
||||
}: WorkflowStepFilterOperandSelectProps) => {
|
||||
const { readonly } = useContext(WorkflowStepFilterContext);
|
||||
|
||||
const { upsertStepFilterSettings } = useUpsertStepFilterSettings();
|
||||
const operands = getViewFilterOperands({ filterType: stepFilter.type });
|
||||
|
||||
const handleChange = (operand: StepOperand) => {
|
||||
const options = operands.map((operand) => ({
|
||||
value: operand,
|
||||
label: getOperandLabel(operand),
|
||||
}));
|
||||
|
||||
const handleChange = (operand: ViewFilterOperand) => {
|
||||
upsertStepFilterSettings({
|
||||
stepFilterToUpsert: {
|
||||
...stepFilter,
|
||||
@@ -45,7 +40,7 @@ export const WorkflowStepFilterOperandSelect = ({
|
||||
dropdownWidth={GenericDropdownContentWidth.Medium}
|
||||
dropdownId={`step-filter-operand-${stepFilter.id}`}
|
||||
value={stepFilter.operand}
|
||||
options={STEP_OPERAND_OPTIONS}
|
||||
options={options}
|
||||
onChange={handleChange}
|
||||
disabled={readonly}
|
||||
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import {
|
||||
StepFilter,
|
||||
StepFilterGroup,
|
||||
StepLogicalOperator,
|
||||
StepOperand,
|
||||
ViewFilterOperand,
|
||||
} from 'twenty-shared/types';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
@@ -28,7 +28,7 @@ const TEXT_STEP_FILTER: StepFilter = {
|
||||
type: 'text',
|
||||
label: 'Company Name',
|
||||
value: 'Acme',
|
||||
operand: StepOperand.LIKE,
|
||||
operand: ViewFilterOperand.Contains,
|
||||
};
|
||||
|
||||
const meta: Meta<typeof WorkflowStepFilterColumn> = {
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { StepFilter, StepOperand } from 'twenty-shared/types';
|
||||
import { StepFilter, ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||
@@ -16,7 +16,7 @@ const DEFAULT_STEP_FILTER: StepFilter = {
|
||||
displayValue: '',
|
||||
type: 'text',
|
||||
label: 'New Filter',
|
||||
operand: StepOperand.EQ,
|
||||
operand: ViewFilterOperand.Is,
|
||||
value: '',
|
||||
positionInStepFilterGroup: 0,
|
||||
};
|
||||
|
||||
+7
-28
@@ -1,14 +1,14 @@
|
||||
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
||||
import { WorkflowStepFilterOperandSelect } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterOperandSelect';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, within } from '@storybook/test';
|
||||
import { StepFilter, StepOperand } from 'twenty-shared/types';
|
||||
import { StepFilter, ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
|
||||
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { WorkflowStepFilterOperandSelect } from '../WorkflowStepFilterOperandSelect';
|
||||
|
||||
const DEFAULT_STEP_FILTER: StepFilter = {
|
||||
id: 'filter-1',
|
||||
@@ -17,23 +17,11 @@ const DEFAULT_STEP_FILTER: StepFilter = {
|
||||
displayValue: 'Company Name',
|
||||
type: 'text',
|
||||
label: 'Company Name',
|
||||
operand: StepOperand.EQ,
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: '',
|
||||
positionInStepFilterGroup: 0,
|
||||
};
|
||||
|
||||
const LIKE_OPERAND_FILTER: StepFilter = {
|
||||
id: 'filter-1',
|
||||
stepFilterGroupId: 'filter-group-1',
|
||||
stepOutputKey: 'company.name',
|
||||
displayValue: 'Company Name',
|
||||
type: 'text',
|
||||
label: 'Company Name',
|
||||
operand: StepOperand.LIKE,
|
||||
value: 'Acme',
|
||||
positionInStepFilterGroup: 0,
|
||||
};
|
||||
|
||||
const GREATER_THAN_FILTER: StepFilter = {
|
||||
id: 'filter-1',
|
||||
stepFilterGroupId: 'filter-group-1',
|
||||
@@ -41,7 +29,7 @@ const GREATER_THAN_FILTER: StepFilter = {
|
||||
displayValue: 'Employee Count',
|
||||
type: 'number',
|
||||
label: 'Employee Count',
|
||||
operand: StepOperand.GT,
|
||||
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||
value: '100',
|
||||
positionInStepFilterGroup: 0,
|
||||
};
|
||||
@@ -69,17 +57,6 @@ export default meta;
|
||||
type Story = StoryObj<typeof WorkflowStepFilterOperandSelect>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await expect(await canvas.findByText('Equals')).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLikeOperand: Story = {
|
||||
args: {
|
||||
stepFilter: LIKE_OPERAND_FILTER,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
@@ -94,6 +71,8 @@ export const WithGreaterThanOperand: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await expect(await canvas.findByText('Greater than')).toBeVisible();
|
||||
await expect(
|
||||
await canvas.findByText('Greater than or equal'),
|
||||
).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, within } from '@storybook/test';
|
||||
import { StepFilter, StepOperand } from 'twenty-shared/types';
|
||||
import { StepFilter, ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||
@@ -17,7 +17,7 @@ const TEXT_FILTER: StepFilter = {
|
||||
displayValue: 'Company Name',
|
||||
type: 'text',
|
||||
label: 'Company Name',
|
||||
operand: StepOperand.LIKE,
|
||||
operand: ViewFilterOperand.Contains,
|
||||
value: 'Acme',
|
||||
positionInStepFilterGroup: 0,
|
||||
};
|
||||
@@ -29,7 +29,7 @@ const NUMBER_FILTER: StepFilter = {
|
||||
displayValue: 'Employee Count',
|
||||
type: 'number',
|
||||
label: 'Employee Count',
|
||||
operand: StepOperand.GT,
|
||||
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||
value: '100',
|
||||
positionInStepFilterGroup: 0,
|
||||
};
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ import {
|
||||
StepFilter,
|
||||
StepFilterGroup,
|
||||
StepLogicalOperator,
|
||||
StepOperand,
|
||||
ViewFilterOperand,
|
||||
} from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@@ -48,10 +48,10 @@ export const useAddRootStepFilter = () => {
|
||||
|
||||
const newStepFilter: StepFilter = {
|
||||
id: v4(),
|
||||
type: 'text',
|
||||
type: 'unknown',
|
||||
label: 'New Filter',
|
||||
value: '',
|
||||
operand: StepOperand.EQ,
|
||||
operand: ViewFilterOperand.Is,
|
||||
displayValue: '',
|
||||
stepFilterGroupId: newStepFilterGroup.id,
|
||||
stepOutputKey: '',
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import { ViewFilterOperand } from 'twenty-shared/src/types';
|
||||
|
||||
const emptyOperands = [
|
||||
ViewFilterOperand.IsEmpty,
|
||||
ViewFilterOperand.IsNotEmpty,
|
||||
] as const;
|
||||
|
||||
const relationOperands = [
|
||||
ViewFilterOperand.Is,
|
||||
ViewFilterOperand.IsNot,
|
||||
] as const;
|
||||
|
||||
const defaultOperands = [
|
||||
ViewFilterOperand.Is,
|
||||
ViewFilterOperand.IsNot,
|
||||
ViewFilterOperand.Contains,
|
||||
ViewFilterOperand.DoesNotContain,
|
||||
ViewFilterOperand.GreaterThanOrEqual,
|
||||
ViewFilterOperand.LessThanOrEqual,
|
||||
...emptyOperands,
|
||||
] as const;
|
||||
|
||||
export const FILTER_OPERANDS_MAP = {
|
||||
TEXT: [
|
||||
ViewFilterOperand.Contains,
|
||||
ViewFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
],
|
||||
NUMBER: [
|
||||
ViewFilterOperand.GreaterThanOrEqual,
|
||||
ViewFilterOperand.LessThanOrEqual,
|
||||
...emptyOperands,
|
||||
],
|
||||
RAW_JSON: [
|
||||
ViewFilterOperand.Contains,
|
||||
ViewFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
],
|
||||
DATE_TIME: [
|
||||
ViewFilterOperand.Is,
|
||||
ViewFilterOperand.IsRelative,
|
||||
ViewFilterOperand.IsInPast,
|
||||
ViewFilterOperand.IsInFuture,
|
||||
ViewFilterOperand.IsToday,
|
||||
ViewFilterOperand.IsBefore,
|
||||
ViewFilterOperand.IsAfter,
|
||||
...emptyOperands,
|
||||
],
|
||||
DATE: [
|
||||
ViewFilterOperand.Is,
|
||||
ViewFilterOperand.IsRelative,
|
||||
ViewFilterOperand.IsInPast,
|
||||
ViewFilterOperand.IsInFuture,
|
||||
ViewFilterOperand.IsToday,
|
||||
ViewFilterOperand.IsBefore,
|
||||
ViewFilterOperand.IsAfter,
|
||||
...emptyOperands,
|
||||
],
|
||||
RATING: [ViewFilterOperand.Is, ViewFilterOperand.IsNot, ...emptyOperands],
|
||||
RELATION: [...relationOperands, ...emptyOperands],
|
||||
MULTI_SELECT: [
|
||||
ViewFilterOperand.Contains,
|
||||
ViewFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
],
|
||||
SELECT: [ViewFilterOperand.Is, ViewFilterOperand.IsNot, ...emptyOperands],
|
||||
ARRAY: [
|
||||
ViewFilterOperand.Contains,
|
||||
ViewFilterOperand.DoesNotContain,
|
||||
...emptyOperands,
|
||||
],
|
||||
BOOLEAN: [ViewFilterOperand.Is],
|
||||
UUID: [ViewFilterOperand.Is],
|
||||
NUMERIC: [
|
||||
ViewFilterOperand.GreaterThanOrEqual,
|
||||
ViewFilterOperand.LessThanOrEqual,
|
||||
...emptyOperands,
|
||||
],
|
||||
};
|
||||
|
||||
export const getViewFilterOperands = ({
|
||||
filterType,
|
||||
}: {
|
||||
filterType: string;
|
||||
}): readonly ViewFilterOperand[] => {
|
||||
switch (filterType) {
|
||||
case 'TEXT':
|
||||
return FILTER_OPERANDS_MAP.TEXT;
|
||||
case 'NUMBER':
|
||||
return FILTER_OPERANDS_MAP.NUMBER;
|
||||
case 'RAW_JSON':
|
||||
return FILTER_OPERANDS_MAP.RAW_JSON;
|
||||
case 'DATE_TIME':
|
||||
case 'DATE':
|
||||
return FILTER_OPERANDS_MAP.DATE_TIME;
|
||||
case 'RATING':
|
||||
return FILTER_OPERANDS_MAP.RATING;
|
||||
case 'RELATION':
|
||||
return FILTER_OPERANDS_MAP.RELATION;
|
||||
case 'MULTI_SELECT':
|
||||
return FILTER_OPERANDS_MAP.MULTI_SELECT;
|
||||
case 'SELECT':
|
||||
return FILTER_OPERANDS_MAP.SELECT;
|
||||
case 'ARRAY':
|
||||
return FILTER_OPERANDS_MAP.ARRAY;
|
||||
case 'BOOLEAN':
|
||||
return FILTER_OPERANDS_MAP.BOOLEAN;
|
||||
case 'UUID':
|
||||
return FILTER_OPERANDS_MAP.UUID;
|
||||
case 'NUMERIC':
|
||||
return FILTER_OPERANDS_MAP.NUMERIC;
|
||||
default:
|
||||
return defaultOperands;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user