Skip to content

Commit 3ca31f9

Browse files
committed
Support Emails and Phones in spreadsheet import
1 parent 4630680 commit 3ca31f9

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

packages/twenty-front/src/modules/object-record/spreadsheet-import/constants/CompositeFieldImportLabels.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {
22
FieldAddressValue,
33
FieldCurrencyValue,
4+
FieldEmailsValue,
45
FieldFullNameValue,
56
FieldLinksValue,
7+
FieldPhonesValue,
68
} from '@/object-record/record-field/types/FieldMetadata';
79
import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels';
810
import { FieldMetadataType } from '~/generated-metadata/graphql';
@@ -30,6 +32,13 @@ export const COMPOSITE_FIELD_IMPORT_LABELS = {
3032
primaryLinkUrlLabel: 'Link URL',
3133
primaryLinkLabelLabel: 'Link Label',
3234
} satisfies Partial<CompositeFieldLabels<FieldLinksValue>>,
35+
[FieldMetadataType.Emails]: {
36+
primaryEmailLabel: 'Email',
37+
} satisfies Partial<CompositeFieldLabels<FieldEmailsValue>>,
38+
[FieldMetadataType.Phones]: {
39+
primaryPhoneCountryCodeLabel: 'Phone country code',
40+
primaryPhoneNumberLabel: 'Phone number',
41+
} satisfies Partial<CompositeFieldLabels<FieldPhonesValue>>,
3342
[FieldMetadataType.Actor]: {
3443
sourceLabel: 'Source',
3544
},

packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildAvailableFieldsForImport.ts

+37
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const useBuildAvailableFieldsForImport = () => {
1515
) => {
1616
const availableFieldsForImport: AvailableFieldForImport[] = [];
1717

18+
// Todo: refactor this to avoid this else if syntax with duplicated code
1819
for (const fieldMetadataItem of fieldMetadataItems) {
1920
if (fieldMetadataItem.type === FieldMetadataType.FullName) {
2021
const { firstNameLabel, lastNameLabel } =
@@ -155,6 +156,42 @@ export const useBuildAvailableFieldsForImport = () => {
155156
fieldMetadataItem.label,
156157
),
157158
});
159+
} else if (fieldMetadataItem.type === FieldMetadataType.Emails) {
160+
Object.entries(
161+
COMPOSITE_FIELD_IMPORT_LABELS[FieldMetadataType.Emails],
162+
).forEach(([_, fieldLabel]) => {
163+
availableFieldsForImport.push({
164+
icon: getIcon(fieldMetadataItem.icon),
165+
label: `${fieldLabel} (${fieldMetadataItem.label})`,
166+
key: `${fieldLabel} (${fieldMetadataItem.name})`,
167+
fieldType: {
168+
type: 'input',
169+
},
170+
fieldValidationDefinitions:
171+
getSpreadSheetFieldValidationDefinitions(
172+
fieldMetadataItem.type,
173+
`${fieldLabel} (${fieldMetadataItem.label})`,
174+
),
175+
});
176+
});
177+
} else if (fieldMetadataItem.type === FieldMetadataType.Phones) {
178+
Object.entries(
179+
COMPOSITE_FIELD_IMPORT_LABELS[FieldMetadataType.Phones],
180+
).forEach(([_, fieldLabel]) => {
181+
availableFieldsForImport.push({
182+
icon: getIcon(fieldMetadataItem.icon),
183+
label: `${fieldLabel} (${fieldMetadataItem.label})`,
184+
key: `${fieldLabel} (${fieldMetadataItem.name})`,
185+
fieldType: {
186+
type: 'input',
187+
},
188+
fieldValidationDefinitions:
189+
getSpreadSheetFieldValidationDefinitions(
190+
fieldMetadataItem.type,
191+
`${fieldLabel} (${fieldMetadataItem.label})`,
192+
),
193+
});
194+
});
158195
} else {
159196
availableFieldsForImport.push({
160197
icon: getIcon(fieldMetadataItem.icon),

packages/twenty-front/src/modules/object-record/spreadsheet-import/util/buildRecordFromImportedStructuredRow.ts

+43-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
22
import {
33
FieldAddressValue,
4+
FieldEmailsValue,
45
FieldLinksValue,
6+
FieldPhonesValue,
57
} from '@/object-record/record-field/types/FieldMetadata';
68
import { COMPOSITE_FIELD_IMPORT_LABELS } from '@/object-record/spreadsheet-import/constants/CompositeFieldImportLabels';
79
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
@@ -31,6 +33,8 @@ export const buildRecordFromImportedStructuredRow = (
3133
CURRENCY: { amountMicrosLabel, currencyCodeLabel },
3234
FULL_NAME: { firstNameLabel, lastNameLabel },
3335
LINKS: { primaryLinkLabelLabel, primaryLinkUrlLabel },
36+
EMAILS: { primaryEmailLabel },
37+
PHONES: { primaryPhoneNumberLabel, primaryPhoneCountryCodeLabel },
3438
} = COMPOSITE_FIELD_IMPORT_LABELS;
3539

3640
for (const field of fields) {
@@ -129,14 +133,48 @@ export const buildRecordFromImportedStructuredRow = (
129133
}
130134
break;
131135
}
132-
case FieldMetadataType.Link:
133-
if (importedFieldValue !== undefined) {
136+
case FieldMetadataType.Phones: {
137+
if (
138+
isDefined(
139+
importedStructuredRow[
140+
`${primaryPhoneCountryCodeLabel} (${field.name})`
141+
] ||
142+
importedStructuredRow[
143+
`${primaryPhoneNumberLabel} (${field.name})`
144+
],
145+
)
146+
) {
134147
recordToBuild[field.name] = {
135-
label: field.name,
136-
url: importedFieldValue || null,
137-
};
148+
primaryPhoneCountryCode: castToString(
149+
importedStructuredRow[
150+
`${primaryPhoneCountryCodeLabel} (${field.name})`
151+
],
152+
),
153+
primaryPhoneNumber: castToString(
154+
importedStructuredRow[
155+
`${primaryPhoneNumberLabel} (${field.name})`
156+
],
157+
),
158+
additionalPhones: null,
159+
} satisfies FieldPhonesValue;
138160
}
139161
break;
162+
}
163+
case FieldMetadataType.Emails: {
164+
if (
165+
isDefined(
166+
importedStructuredRow[`${primaryEmailLabel} (${field.name})`],
167+
)
168+
) {
169+
recordToBuild[field.name] = {
170+
primaryEmail: castToString(
171+
importedStructuredRow[`${primaryEmailLabel} (${field.name})`],
172+
),
173+
additionalEmails: null,
174+
} satisfies FieldEmailsValue;
175+
}
176+
break;
177+
}
140178
case FieldMetadataType.Relation:
141179
if (
142180
isDefined(importedFieldValue) &&

packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export const sanitizeRecordInput = ({
5050
return undefined;
5151
}
5252

53+
// Todo: we should check that the fieldValue is a valid value
54+
// (e.g. a string for a string field, following the right composite structure for composite fields)
5355
return [fieldName, fieldValue];
5456
})
5557
.filter(isDefined),

0 commit comments

Comments
 (0)