Skip to content

Commit b90cb3e

Browse files
authored
Add filter fields on update record trigger (#12354)
Fixes twentyhq/core-team-issues#928 <img width="503" alt="Capture d’écran 2025-05-28 à 15 04 08" src="https://github.com/user-attachments/assets/b83ceced-4b3a-454c-83c1-1176f6836d96" />
1 parent 630e478 commit b90cb3e

File tree

7 files changed

+252
-46
lines changed

7 files changed

+252
-46
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
2+
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
3+
import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput';
4+
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
5+
import { SUPPORTED_FIELD_METADATA_TYPES } from '@/workflow/constants/SupportedFieldMetadataTypes';
6+
import { isDefined } from 'twenty-shared/utils';
7+
import { useIcons } from 'twenty-ui/display';
8+
9+
export const WorkflowFieldsMultiSelect = ({
10+
label,
11+
objectMetadataItem,
12+
handleFieldsChange,
13+
readonly,
14+
defaultFields,
15+
placeholder,
16+
}: {
17+
label: string;
18+
placeholder: string;
19+
objectMetadataItem: ObjectMetadataItem;
20+
handleFieldsChange: (field: FieldMultiSelectValue | string) => void;
21+
readonly: boolean;
22+
defaultFields: string[] | undefined | null;
23+
}) => {
24+
const { getIcon } = useIcons();
25+
26+
const inlineFieldMetadataItems = objectMetadataItem?.fields
27+
.filter(
28+
(fieldMetadataItem) =>
29+
!fieldMetadataItem.isSystem &&
30+
fieldMetadataItem.isActive &&
31+
SUPPORTED_FIELD_METADATA_TYPES.includes(fieldMetadataItem.type),
32+
)
33+
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
34+
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
35+
);
36+
37+
const inlineFieldDefinitions = isDefined(objectMetadataItem)
38+
? inlineFieldMetadataItems.map((fieldMetadataItem) =>
39+
formatFieldMetadataItemAsFieldDefinition({
40+
field: fieldMetadataItem,
41+
objectMetadataItem: objectMetadataItem,
42+
showLabel: true,
43+
labelWidth: 90,
44+
}),
45+
)
46+
: [];
47+
48+
return (
49+
<FormMultiSelectFieldInput
50+
testId="workflow-fields-multi-select"
51+
label={label}
52+
defaultValue={defaultFields}
53+
options={inlineFieldDefinitions.map((field) => ({
54+
label: field.label,
55+
value: field.metadata.fieldName,
56+
icon: getIcon(field.iconName),
57+
color: 'gray',
58+
}))}
59+
onChange={handleFieldsChange}
60+
placeholder={placeholder}
61+
readonly={readonly}
62+
/>
63+
);
64+
};
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
2+
import { expect } from '@storybook/jest';
3+
import type { Meta, StoryObj } from '@storybook/react';
4+
import { within } from '@storybook/testing-library';
5+
import { FieldMetadataType } from '~/generated/graphql';
6+
import { WorkflowFieldsMultiSelect } from '../WorkflowEditUpdateEventFieldsMultiSelect';
7+
8+
const meta: Meta<typeof WorkflowFieldsMultiSelect> = {
9+
title: 'Modules/Workflow/WorkflowFieldsMultiSelect',
10+
component: WorkflowFieldsMultiSelect,
11+
parameters: {
12+
layout: 'centered',
13+
},
14+
};
15+
16+
export default meta;
17+
type Story = StoryObj<typeof WorkflowFieldsMultiSelect>;
18+
19+
const mockObjectMetadataItem: ObjectMetadataItem = {
20+
id: '1',
21+
nameSingular: 'company',
22+
namePlural: 'companies',
23+
labelSingular: 'Company',
24+
labelPlural: 'Companies',
25+
description: 'A company',
26+
icon: 'IconBuilding',
27+
isSystem: false,
28+
isCustom: false,
29+
isActive: true,
30+
createdAt: '',
31+
updatedAt: '',
32+
isLabelSyncedWithName: true,
33+
isRemote: false,
34+
isSearchable: true,
35+
labelIdentifierFieldMetadataId: '1',
36+
indexMetadatas: [],
37+
fields: [
38+
{
39+
id: '1',
40+
name: 'name',
41+
label: 'Name',
42+
type: FieldMetadataType.TEXT,
43+
description: 'Company name',
44+
isCustom: false,
45+
isActive: true,
46+
isSystem: false,
47+
isNullable: false,
48+
createdAt: '',
49+
updatedAt: '',
50+
},
51+
{
52+
id: '2',
53+
name: 'domainName',
54+
label: 'Domain Name',
55+
type: FieldMetadataType.TEXT,
56+
description: 'Company domain name',
57+
isCustom: false,
58+
isActive: true,
59+
isSystem: false,
60+
isNullable: true,
61+
createdAt: '',
62+
updatedAt: '',
63+
},
64+
{
65+
id: '3',
66+
name: 'employees',
67+
label: 'Employees',
68+
type: FieldMetadataType.NUMBER,
69+
description: 'Number of employees',
70+
isCustom: false,
71+
isActive: true,
72+
isSystem: false,
73+
isNullable: true,
74+
createdAt: '',
75+
updatedAt: '',
76+
},
77+
],
78+
};
79+
80+
export const Default: Story = {
81+
args: {
82+
label: 'Fields to update',
83+
placeholder: 'Select fields to update',
84+
objectMetadataItem: mockObjectMetadataItem,
85+
handleFieldsChange: () => {},
86+
readonly: false,
87+
defaultFields: [],
88+
},
89+
play: async ({ canvasElement }) => {
90+
const canvas = within(canvasElement);
91+
expect(
92+
await canvas.findByTestId('workflow-fields-multi-select'),
93+
).toBeVisible();
94+
},
95+
};
96+
97+
export const WithDefaultValues: Story = {
98+
args: {
99+
...Default.args,
100+
defaultFields: ['name', 'domainName'],
101+
},
102+
play: async ({ canvasElement }) => {
103+
const canvas = within(canvasElement);
104+
expect(await canvas.findByText('Name')).toBeVisible();
105+
expect(await canvas.findByText('Domain Name')).toBeVisible();
106+
},
107+
};
108+
109+
export const ReadOnly: Story = {
110+
args: {
111+
...Default.args,
112+
readonly: true,
113+
defaultFields: ['name', 'domainName'],
114+
},
115+
play: async ({ canvasElement }) => {
116+
const canvas = within(canvasElement);
117+
expect(await canvas.findByText('Name')).toBeVisible();
118+
expect(await canvas.findByText('Domain Name')).toBeVisible();
119+
},
120+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { FieldMetadataType } from 'twenty-shared/types';
2+
3+
export const SUPPORTED_FIELD_METADATA_TYPES = [
4+
FieldMetadataType.TEXT,
5+
FieldMetadataType.NUMBER,
6+
FieldMetadataType.DATE,
7+
FieldMetadataType.BOOLEAN,
8+
FieldMetadataType.SELECT,
9+
FieldMetadataType.MULTI_SELECT,
10+
FieldMetadataType.EMAILS,
11+
FieldMetadataType.LINKS,
12+
FieldMetadataType.FULL_NAME,
13+
FieldMetadataType.ADDRESS,
14+
FieldMetadataType.PHONES,
15+
FieldMetadataType.CURRENCY,
16+
FieldMetadataType.DATE_TIME,
17+
FieldMetadataType.RAW_JSON,
18+
FieldMetadataType.UUID,
19+
];

packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export const workflowDatabaseEventTriggerSchema = baseTriggerSchema.extend({
164164
input: z.object({}).passthrough().optional(),
165165
outputSchema: z.object({}).passthrough(),
166166
objectType: z.string().optional(),
167+
fields: z.array(z.string()).optional().nullable(),
167168
}),
168169
});
169170

packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { useEffect, useState } from 'react';
55

66
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
77
import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput';
8-
import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput';
98
import { FormSingleRecordPicker } from '@/object-record/record-field/form-types/components/FormSingleRecordPicker';
9+
import { WorkflowFieldsMultiSelect } from '@/workflow/components/WorkflowEditUpdateEventFieldsMultiSelect';
10+
import { SUPPORTED_FIELD_METADATA_TYPES } from '@/workflow/constants/SupportedFieldMetadataTypes';
1011
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
1112
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
1213
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
@@ -18,7 +19,6 @@ import { HorizontalSeparator, useIcons } from 'twenty-ui/display';
1819
import { SelectOption } from 'twenty-ui/input';
1920
import { JsonValue } from 'type-fest';
2021
import { useDebouncedCallback } from 'use-debounce';
21-
import { FieldMetadataType } from '~/generated-metadata/graphql';
2222

2323
type WorkflowEditActionUpdateRecordProps = {
2424
action: WorkflowUpdateRecordAction;
@@ -39,24 +39,6 @@ type UpdateRecordFormData = {
3939
[field: string]: unknown;
4040
};
4141

42-
const AVAILABLE_FIELD_METADATA_TYPES = [
43-
FieldMetadataType.TEXT,
44-
FieldMetadataType.NUMBER,
45-
FieldMetadataType.DATE,
46-
FieldMetadataType.BOOLEAN,
47-
FieldMetadataType.SELECT,
48-
FieldMetadataType.MULTI_SELECT,
49-
FieldMetadataType.EMAILS,
50-
FieldMetadataType.LINKS,
51-
FieldMetadataType.FULL_NAME,
52-
FieldMetadataType.ADDRESS,
53-
FieldMetadataType.PHONES,
54-
FieldMetadataType.CURRENCY,
55-
FieldMetadataType.DATE_TIME,
56-
FieldMetadataType.RAW_JSON,
57-
FieldMetadataType.UUID,
58-
];
59-
6042
export const WorkflowEditActionUpdateRecord = ({
6143
action,
6244
actionOptions,
@@ -106,7 +88,7 @@ export const WorkflowEditActionUpdateRecord = ({
10688
(fieldMetadataItem) =>
10789
!fieldMetadataItem.isSystem &&
10890
fieldMetadataItem.isActive &&
109-
AVAILABLE_FIELD_METADATA_TYPES.includes(fieldMetadataItem.type),
91+
SUPPORTED_FIELD_METADATA_TYPES.includes(fieldMetadataItem.type),
11092
)
11193
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
11294
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
@@ -222,22 +204,16 @@ export const WorkflowEditActionUpdateRecord = ({
222204
/>
223205
)}
224206

225-
{isDefined(inlineFieldDefinitions) && (
226-
<FormMultiSelectFieldInput
227-
testId="workflow-edit-action-record-update-fields-to-update"
207+
{isDefined(selectedObjectMetadataItem) && (
208+
<WorkflowFieldsMultiSelect
228209
label="Fields to update"
229-
defaultValue={formData.fieldsToUpdate}
230-
options={inlineFieldDefinitions.map((field) => ({
231-
label: field.label,
232-
value: field.metadata.fieldName,
233-
icon: getIcon(field.iconName),
234-
color: 'gray',
235-
}))}
236-
onChange={(fieldsToUpdate) =>
210+
placeholder="Select fields to update"
211+
objectMetadataItem={selectedObjectMetadataItem}
212+
handleFieldsChange={(fieldsToUpdate) =>
237213
handleFieldChange('fieldsToUpdate', fieldsToUpdate)
238214
}
239-
placeholder="Select fields to update"
240-
readonly={isFormDisabled}
215+
readonly={isFormDisabled ?? false}
216+
defaultFields={formData.fieldsToUpdate}
241217
/>
242218
)}
243219

packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionUpdateRecord.stories.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,7 @@ export const DisabledWithEmptyValues: Story = {
119119
expect(openRecordSelectButton).not.toBeInTheDocument();
120120

121121
const firstSelectedUpdatableField = await within(
122-
await canvas.findByTestId(
123-
'workflow-edit-action-record-update-fields-to-update',
124-
),
122+
await canvas.findByTestId('workflow-fields-multi-select'),
125123
).findByText('Creation date');
126124

127125
await userEvent.click(firstSelectedUpdatableField);
@@ -189,9 +187,7 @@ export const DisabledWithDefaultStaticValues: Story = {
189187
expect(openRecordSelectButton).not.toBeInTheDocument();
190188

191189
const firstSelectedUpdatableField = await within(
192-
await canvas.findByTestId(
193-
'workflow-edit-action-record-update-fields-to-update',
194-
),
190+
await canvas.findByTestId('workflow-fields-multi-select'),
195191
).findByText('Creation date');
196192

197193
await userEvent.click(firstSelectedUpdatableField);
@@ -253,9 +249,7 @@ export const DisabledWithDefaultVariableValues: Story = {
253249
expect(openRecordSelectButton).not.toBeInTheDocument();
254250

255251
const firstSelectedUpdatableField = await within(
256-
await canvas.findByTestId(
257-
'workflow-edit-action-record-update-fields-to-update',
258-
),
252+
await canvas.findByTestId('workflow-fields-multi-select'),
259253
).findByText('Creation date');
260254

261255
await userEvent.click(firstSelectedUpdatableField);

0 commit comments

Comments
 (0)