diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx
index 63f9e1dbc1..817b64aa1c 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx
@@ -3,13 +3,18 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { useObjectMetadataSelectHelpers } from '@/object-metadata/hooks/useObjectMetadataSelectHelpers';
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { FormFieldInput } from '@/object-record/record-field/ui/components/FormFieldInput';
-import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
+import { isFieldRelationManyToOne } from '@/object-record/record-field/ui/types/guards/isFieldRelationManyToOne';
import { Select } from '@/ui/input/components/Select';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useViewOrDefaultView } from '@/views/hooks/useViewOrDefaultView';
import { type WorkflowCreateRecordAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
+import {
+ buildUpdatedRecordActionFormData,
+ type RecordActionFormData,
+ type RelationManyToOneField,
+} from '@/workflow/workflow-steps/workflow-actions/utils/buildUpdatedRecordActionFormData';
import { shouldDisplayFormField } from '@/workflow/workflow-steps/workflow-actions/utils/shouldDisplayFormField';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { t } from '@lingui/core/macro';
@@ -20,16 +25,8 @@ import { HorizontalSeparator } from 'twenty-ui/layout';
import { type SelectOption } from 'twenty-ui/input';
import { type JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce';
-import { RelationType } from '~/generated-metadata/graphql';
-type RelationManyToOneField = {
- id: string;
-};
-
-type CreateRecordFormData = {
- objectName: string;
- [field: string]: RelationManyToOneField | JsonValue;
-};
+type CreateRecordFormData = RecordActionFormData;
type WorkflowEditActionCreateRecordProps = {
action: WorkflowCreateRecordAction;
@@ -146,20 +143,12 @@ export const WorkflowEditActionCreateRecord = ({
return;
}
- const isFieldRelationManyToOne =
- isFieldRelation(fieldDefinition) &&
- fieldDefinition.metadata.relationType === RelationType.MANY_TO_ONE;
-
- const fieldValue = isFieldRelationManyToOne
- ? {
- id: updatedValue,
- }
- : updatedValue;
-
- const newFormData: CreateRecordFormData = {
- ...formData,
- [fieldName]: fieldValue,
- };
+ const newFormData = buildUpdatedRecordActionFormData({
+ formData,
+ fieldName,
+ fieldDefinition,
+ updatedValue,
+ });
setFormData(newFormData);
@@ -231,11 +220,7 @@ export const WorkflowEditActionCreateRecord = ({
{inlineFieldDefinitions?.map((fieldDefinition) => {
- const isFieldRelationManyToOne =
- isFieldRelation(fieldDefinition) &&
- fieldDefinition.metadata.relationType === RelationType.MANY_TO_ONE;
-
- const currentValue = isFieldRelationManyToOne
+ const currentValue = isFieldRelationManyToOne(fieldDefinition)
? (
formData[
fieldDefinition.metadata.fieldName
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx
index 56289706ab..edc9c685df 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx
@@ -4,7 +4,7 @@ import { useObjectMetadataSelectHelpers } from '@/object-metadata/hooks/useObjec
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { FormFieldInput } from '@/object-record/record-field/ui/components/FormFieldInput';
import { FormSingleRecordPicker } from '@/object-record/record-field/ui/form-types/components/FormSingleRecordPicker';
-import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
+import { isFieldRelationManyToOne } from '@/object-record/record-field/ui/types/guards/isFieldRelationManyToOne';
import { Select } from '@/ui/input/components/Select';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useViewOrDefaultView } from '@/views/hooks/useViewOrDefaultView';
@@ -12,6 +12,11 @@ import { WorkflowFieldsMultiSelect } from '@/workflow/components/WorkflowEditUpd
import { type WorkflowUpsertRecordAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
+import {
+ buildUpdatedRecordActionFormData,
+ type RecordActionFormData,
+ type RelationManyToOneField,
+} from '@/workflow/workflow-steps/workflow-actions/utils/buildUpdatedRecordActionFormData';
import { shouldDisplayFormField } from '@/workflow/workflow-steps/workflow-actions/utils/shouldDisplayFormField';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { t } from '@lingui/core/macro';
@@ -22,16 +27,8 @@ import { HorizontalSeparator } from 'twenty-ui/layout';
import { type SelectOption } from 'twenty-ui/input';
import { type JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce';
-import { RelationType } from '~/generated-metadata/graphql';
-type RelationManyToOneField = {
- id: string;
-};
-
-type UpsertRecordFormData = {
- objectName: string;
- [field: string]: RelationManyToOneField | JsonValue;
-};
+type UpsertRecordFormData = RecordActionFormData;
type WorkflowEditActionUpsertRecordProps = {
action: WorkflowUpsertRecordAction;
@@ -163,20 +160,12 @@ export const WorkflowEditActionUpsertRecord = ({
return;
}
- const isFieldRelationManyToOne =
- isFieldRelation(fieldDefinition) &&
- fieldDefinition.metadata.relationType === RelationType.MANY_TO_ONE;
-
- const fieldValue = isFieldRelationManyToOne
- ? {
- id: updatedValue,
- }
- : updatedValue;
-
- const newFormData: UpsertRecordFormData = {
- ...formData,
- [fieldName]: fieldValue,
- };
+ const newFormData = buildUpdatedRecordActionFormData({
+ formData,
+ fieldName,
+ fieldDefinition,
+ updatedValue,
+ });
setFormData(newFormData);
@@ -291,11 +280,7 @@ export const WorkflowEditActionUpsertRecord = ({
);
}
- const isFieldRelationManyToOne =
- isFieldRelation(fieldDefinition) &&
- fieldDefinition.metadata.relationType === RelationType.MANY_TO_ONE;
-
- const currentValue = isFieldRelationManyToOne
+ const currentValue = isFieldRelationManyToOne(fieldDefinition)
? (
formData[
fieldDefinition.metadata.fieldName
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/buildUpdatedRecordActionFormData.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/buildUpdatedRecordActionFormData.test.ts
new file mode 100644
index 0000000000..ff6434183a
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/buildUpdatedRecordActionFormData.test.ts
@@ -0,0 +1,104 @@
+import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
+import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
+import {
+ buildUpdatedRecordActionFormData,
+ type RecordActionFormData,
+} from '@/workflow/workflow-steps/workflow-actions/utils/buildUpdatedRecordActionFormData';
+import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
+
+const relationToOneFieldDefinition = {
+ fieldMetadataId: 'field-metadata-id',
+ label: 'Company',
+ type: FieldMetadataType.RELATION,
+ iconName: 'IconBuildingSkyscraper',
+ metadata: {
+ fieldName: 'company',
+ relationType: RelationType.MANY_TO_ONE,
+ objectMetadataNameSingular: 'person',
+ relationObjectMetadataNameSingular: 'company',
+ relationObjectMetadataNamePlural: 'companies',
+ },
+} as unknown as FieldDefinition;
+
+const textFieldDefinition = {
+ fieldMetadataId: 'field-metadata-id',
+ label: 'City',
+ type: FieldMetadataType.TEXT,
+ iconName: 'IconMap',
+ metadata: {
+ fieldName: 'city',
+ objectMetadataNameSingular: 'person',
+ },
+} as unknown as FieldDefinition;
+
+const formData: RecordActionFormData = {
+ objectName: 'person',
+ city: 'Paris',
+};
+
+describe('buildUpdatedRecordActionFormData', () => {
+ it('should wrap a selected record id in an object when the field is a to-one relation', () => {
+ const result = buildUpdatedRecordActionFormData({
+ formData,
+ fieldName: 'company',
+ fieldDefinition: relationToOneFieldDefinition,
+ updatedValue: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
+ });
+
+ expect(result).toEqual({
+ objectName: 'person',
+ city: 'Paris',
+ company: { id: '20202020-3ec3-4fe3-8997-b76aa0bfa408' },
+ });
+ });
+
+ it('should wrap a variable in an object when the field is a to-one relation', () => {
+ const result = buildUpdatedRecordActionFormData({
+ formData,
+ fieldName: 'company',
+ fieldDefinition: relationToOneFieldDefinition,
+ updatedValue: '{{trigger.object.id}}',
+ });
+
+ expect(result.company).toEqual({ id: '{{trigger.object.id}}' });
+ });
+
+ it('should remove the field when a to-one relation is cleared', () => {
+ const result = buildUpdatedRecordActionFormData({
+ formData: { ...formData, company: { id: 'previous-record-id' } },
+ fieldName: 'company',
+ fieldDefinition: relationToOneFieldDefinition,
+ updatedValue: null,
+ });
+
+ expect(result).not.toHaveProperty('company');
+ expect(result).toEqual({ objectName: 'person', city: 'Paris' });
+ });
+
+ it('should keep null values as-is when the field is not a relation', () => {
+ const result = buildUpdatedRecordActionFormData({
+ formData,
+ fieldName: 'city',
+ fieldDefinition: textFieldDefinition,
+ updatedValue: null,
+ });
+
+ expect(result.city).toBeNull();
+ });
+
+ it('should not mutate the form data it receives', () => {
+ const initialFormData: RecordActionFormData = {
+ objectName: 'person',
+ company: { id: 'previous-record-id' },
+ };
+
+ buildUpdatedRecordActionFormData({
+ formData: initialFormData,
+ fieldName: 'company',
+ fieldDefinition: relationToOneFieldDefinition,
+ updatedValue: null,
+ });
+
+ expect(initialFormData.company).toEqual({ id: 'previous-record-id' });
+ });
+});
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/buildUpdatedRecordActionFormData.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/buildUpdatedRecordActionFormData.ts
new file mode 100644
index 0000000000..648ebf80b3
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/buildUpdatedRecordActionFormData.ts
@@ -0,0 +1,42 @@
+import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
+import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
+import { isFieldRelationManyToOne } from '@/object-record/record-field/ui/types/guards/isFieldRelationManyToOne';
+import { isDefined } from 'twenty-shared/utils';
+import { type JsonValue } from 'type-fest';
+
+export type RelationManyToOneField = {
+ id: string;
+};
+
+export type RecordActionFormData = {
+ objectName: string;
+ [field: string]: RelationManyToOneField | JsonValue;
+};
+
+export const buildUpdatedRecordActionFormData = ({
+ formData,
+ fieldName,
+ fieldDefinition,
+ updatedValue,
+}: {
+ formData: RecordActionFormData;
+ fieldName: keyof RecordActionFormData;
+ fieldDefinition: FieldDefinition;
+ updatedValue: JsonValue;
+}): RecordActionFormData => {
+ const isRelationManyToOne = isFieldRelationManyToOne(fieldDefinition);
+
+ const updatedFormData: RecordActionFormData = { ...formData };
+
+ if (isRelationManyToOne && !isDefined(updatedValue)) {
+ delete updatedFormData[fieldName];
+
+ return updatedFormData;
+ }
+
+ updatedFormData[fieldName] = isRelationManyToOne
+ ? { id: updatedValue }
+ : updatedValue;
+
+ return updatedFormData;
+};