mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-26 13:42:44 +03:00
Support Emails and Phones in Spreadsheet import (#7312)
This is a fast follow on v0.30 release: - removing phone (deprecated PHONE field type) search from command menu. I could have replaced it by a phone (PHONES field type) search but as we are about to release the new search in v0.31 it does not seem to worse the investment - supporting EMAILS and PHONES field types in spreadsheet import Note: while working on Spreadsheet import I found the code quite complex and with areas having duplicated code. It does not seem to be a high priority as I was able to maintain it at a low cost but it's not a peaceful code surface to navigate!
This commit is contained in:
parent
e4959ad534
commit
c2a8cd0a2f
@ -179,7 +179,6 @@ export const CommandMenu = () => {
|
|||||||
'emails',
|
'emails',
|
||||||
['primaryEmail'],
|
['primaryEmail'],
|
||||||
),
|
),
|
||||||
{ phone: { ilike: `%${commandMenuSearch}%` } },
|
|
||||||
])
|
])
|
||||||
: undefined,
|
: undefined,
|
||||||
limit: 3,
|
limit: 3,
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
FieldAddressValue,
|
FieldAddressValue,
|
||||||
FieldCurrencyValue,
|
FieldCurrencyValue,
|
||||||
|
FieldEmailsValue,
|
||||||
FieldFullNameValue,
|
FieldFullNameValue,
|
||||||
FieldLinksValue,
|
FieldLinksValue,
|
||||||
|
FieldPhonesValue,
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels';
|
import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
@ -30,6 +32,13 @@ export const COMPOSITE_FIELD_IMPORT_LABELS = {
|
|||||||
primaryLinkUrlLabel: 'Link URL',
|
primaryLinkUrlLabel: 'Link URL',
|
||||||
primaryLinkLabelLabel: 'Link Label',
|
primaryLinkLabelLabel: 'Link Label',
|
||||||
} satisfies Partial<CompositeFieldLabels<FieldLinksValue>>,
|
} satisfies Partial<CompositeFieldLabels<FieldLinksValue>>,
|
||||||
|
[FieldMetadataType.Emails]: {
|
||||||
|
primaryEmailLabel: 'Email',
|
||||||
|
} satisfies Partial<CompositeFieldLabels<FieldEmailsValue>>,
|
||||||
|
[FieldMetadataType.Phones]: {
|
||||||
|
primaryPhoneCountryCodeLabel: 'Phone country code',
|
||||||
|
primaryPhoneNumberLabel: 'Phone number',
|
||||||
|
} satisfies Partial<CompositeFieldLabels<FieldPhonesValue>>,
|
||||||
[FieldMetadataType.Actor]: {
|
[FieldMetadataType.Actor]: {
|
||||||
sourceLabel: 'Source',
|
sourceLabel: 'Source',
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,7 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
) => {
|
) => {
|
||||||
const availableFieldsForImport: AvailableFieldForImport[] = [];
|
const availableFieldsForImport: AvailableFieldForImport[] = [];
|
||||||
|
|
||||||
|
// Todo: refactor this to avoid this else if syntax with duplicated code
|
||||||
for (const fieldMetadataItem of fieldMetadataItems) {
|
for (const fieldMetadataItem of fieldMetadataItems) {
|
||||||
if (fieldMetadataItem.type === FieldMetadataType.FullName) {
|
if (fieldMetadataItem.type === FieldMetadataType.FullName) {
|
||||||
const { firstNameLabel, lastNameLabel } =
|
const { firstNameLabel, lastNameLabel } =
|
||||||
@ -155,6 +156,42 @@ export const useBuildAvailableFieldsForImport = () => {
|
|||||||
fieldMetadataItem.label,
|
fieldMetadataItem.label,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
} else if (fieldMetadataItem.type === FieldMetadataType.Emails) {
|
||||||
|
Object.entries(
|
||||||
|
COMPOSITE_FIELD_IMPORT_LABELS[FieldMetadataType.Emails],
|
||||||
|
).forEach(([_, fieldLabel]) => {
|
||||||
|
availableFieldsForImport.push({
|
||||||
|
icon: getIcon(fieldMetadataItem.icon),
|
||||||
|
label: `${fieldLabel} (${fieldMetadataItem.label})`,
|
||||||
|
key: `${fieldLabel} (${fieldMetadataItem.name})`,
|
||||||
|
fieldType: {
|
||||||
|
type: 'input',
|
||||||
|
},
|
||||||
|
fieldValidationDefinitions:
|
||||||
|
getSpreadSheetFieldValidationDefinitions(
|
||||||
|
fieldMetadataItem.type,
|
||||||
|
`${fieldLabel} (${fieldMetadataItem.label})`,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (fieldMetadataItem.type === FieldMetadataType.Phones) {
|
||||||
|
Object.entries(
|
||||||
|
COMPOSITE_FIELD_IMPORT_LABELS[FieldMetadataType.Phones],
|
||||||
|
).forEach(([_, fieldLabel]) => {
|
||||||
|
availableFieldsForImport.push({
|
||||||
|
icon: getIcon(fieldMetadataItem.icon),
|
||||||
|
label: `${fieldLabel} (${fieldMetadataItem.label})`,
|
||||||
|
key: `${fieldLabel} (${fieldMetadataItem.name})`,
|
||||||
|
fieldType: {
|
||||||
|
type: 'input',
|
||||||
|
},
|
||||||
|
fieldValidationDefinitions:
|
||||||
|
getSpreadSheetFieldValidationDefinitions(
|
||||||
|
fieldMetadataItem.type,
|
||||||
|
`${fieldLabel} (${fieldMetadataItem.label})`,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
availableFieldsForImport.push({
|
availableFieldsForImport.push({
|
||||||
icon: getIcon(fieldMetadataItem.icon),
|
icon: getIcon(fieldMetadataItem.icon),
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import {
|
import {
|
||||||
FieldAddressValue,
|
FieldAddressValue,
|
||||||
|
FieldEmailsValue,
|
||||||
FieldLinksValue,
|
FieldLinksValue,
|
||||||
|
FieldPhonesValue,
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { COMPOSITE_FIELD_IMPORT_LABELS } from '@/object-record/spreadsheet-import/constants/CompositeFieldImportLabels';
|
import { COMPOSITE_FIELD_IMPORT_LABELS } from '@/object-record/spreadsheet-import/constants/CompositeFieldImportLabels';
|
||||||
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||||
@ -31,6 +33,8 @@ export const buildRecordFromImportedStructuredRow = (
|
|||||||
CURRENCY: { amountMicrosLabel, currencyCodeLabel },
|
CURRENCY: { amountMicrosLabel, currencyCodeLabel },
|
||||||
FULL_NAME: { firstNameLabel, lastNameLabel },
|
FULL_NAME: { firstNameLabel, lastNameLabel },
|
||||||
LINKS: { primaryLinkLabelLabel, primaryLinkUrlLabel },
|
LINKS: { primaryLinkLabelLabel, primaryLinkUrlLabel },
|
||||||
|
EMAILS: { primaryEmailLabel },
|
||||||
|
PHONES: { primaryPhoneNumberLabel, primaryPhoneCountryCodeLabel },
|
||||||
} = COMPOSITE_FIELD_IMPORT_LABELS;
|
} = COMPOSITE_FIELD_IMPORT_LABELS;
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
@ -129,14 +133,48 @@ export const buildRecordFromImportedStructuredRow = (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FieldMetadataType.Link:
|
case FieldMetadataType.Phones: {
|
||||||
if (importedFieldValue !== undefined) {
|
if (
|
||||||
|
isDefined(
|
||||||
|
importedStructuredRow[
|
||||||
|
`${primaryPhoneCountryCodeLabel} (${field.name})`
|
||||||
|
] ||
|
||||||
|
importedStructuredRow[
|
||||||
|
`${primaryPhoneNumberLabel} (${field.name})`
|
||||||
|
],
|
||||||
|
)
|
||||||
|
) {
|
||||||
recordToBuild[field.name] = {
|
recordToBuild[field.name] = {
|
||||||
label: field.name,
|
primaryPhoneCountryCode: castToString(
|
||||||
url: importedFieldValue || null,
|
importedStructuredRow[
|
||||||
};
|
`${primaryPhoneCountryCodeLabel} (${field.name})`
|
||||||
|
],
|
||||||
|
),
|
||||||
|
primaryPhoneNumber: castToString(
|
||||||
|
importedStructuredRow[
|
||||||
|
`${primaryPhoneNumberLabel} (${field.name})`
|
||||||
|
],
|
||||||
|
),
|
||||||
|
additionalPhones: null,
|
||||||
|
} satisfies FieldPhonesValue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case FieldMetadataType.Emails: {
|
||||||
|
if (
|
||||||
|
isDefined(
|
||||||
|
importedStructuredRow[`${primaryEmailLabel} (${field.name})`],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
recordToBuild[field.name] = {
|
||||||
|
primaryEmail: castToString(
|
||||||
|
importedStructuredRow[`${primaryEmailLabel} (${field.name})`],
|
||||||
|
),
|
||||||
|
additionalEmails: null,
|
||||||
|
} satisfies FieldEmailsValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case FieldMetadataType.Relation:
|
case FieldMetadataType.Relation:
|
||||||
if (
|
if (
|
||||||
isDefined(importedFieldValue) &&
|
isDefined(importedFieldValue) &&
|
||||||
|
@ -50,6 +50,8 @@ export const sanitizeRecordInput = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Todo: we should check that the fieldValue is a valid value
|
||||||
|
// (e.g. a string for a string field, following the right composite structure for composite fields)
|
||||||
return [fieldName, fieldValue];
|
return [fieldName, fieldValue];
|
||||||
})
|
})
|
||||||
.filter(isDefined),
|
.filter(isDefined),
|
||||||
|
Loading…
Reference in New Issue
Block a user