Changed the auto matching of columns in import (#12181)
This PR changes the way we do automatching in the import feature. It uses [Fuse.js](https://www.fusejs.io/) to do a fuzzy text search on fields and sub-fields. The labels of sub-fields are now derived from the common config constant we have for sub-fields.
This commit is contained in:
@ -9,43 +9,80 @@ import {
|
||||
FieldRichTextV2Value,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const COMPOSITE_FIELD_IMPORT_LABELS = {
|
||||
[FieldMetadataType.FULL_NAME]: {
|
||||
firstNameLabel: 'First Name',
|
||||
lastNameLabel: 'Last Name',
|
||||
firstNameLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.FULL_NAME.labelBySubField.firstName,
|
||||
lastNameLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.FULL_NAME.labelBySubField.lastName,
|
||||
} satisfies CompositeFieldLabels<FieldFullNameValue>,
|
||||
[FieldMetadataType.CURRENCY]: {
|
||||
currencyCodeLabel: 'Currency Code',
|
||||
amountMicrosLabel: 'Amount',
|
||||
currencyCodeLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.CURRENCY.labelBySubField
|
||||
.currencyCode,
|
||||
amountMicrosLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.CURRENCY.labelBySubField
|
||||
.amountMicros,
|
||||
} satisfies CompositeFieldLabels<FieldCurrencyValue>,
|
||||
[FieldMetadataType.ADDRESS]: {
|
||||
addressStreet1Label: 'Address 1',
|
||||
addressStreet2Label: 'Address 2',
|
||||
addressCityLabel: 'City',
|
||||
addressPostcodeLabel: 'Post Code',
|
||||
addressStateLabel: 'State',
|
||||
addressCountryLabel: 'Country',
|
||||
addressLatLabel: 'Latitude',
|
||||
addressLngLabel: 'Longitude',
|
||||
} satisfies CompositeFieldLabels<FieldAddressValue>,
|
||||
addressStreet1Label:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ADDRESS.labelBySubField
|
||||
.addressStreet1,
|
||||
addressStreet2Label:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ADDRESS.labelBySubField
|
||||
.addressStreet2,
|
||||
addressCityLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ADDRESS.labelBySubField.addressCity,
|
||||
addressPostcodeLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ADDRESS.labelBySubField
|
||||
.addressPostcode,
|
||||
addressStateLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ADDRESS.labelBySubField
|
||||
.addressState,
|
||||
addressCountryLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ADDRESS.labelBySubField
|
||||
.addressCountry,
|
||||
} satisfies Omit<
|
||||
CompositeFieldLabels<FieldAddressValue>,
|
||||
'addressLatLabel' | 'addressLngLabel'
|
||||
>,
|
||||
[FieldMetadataType.LINKS]: {
|
||||
// primaryLinkLabelLabel excluded from composite field import labels since it's not used in Links input
|
||||
primaryLinkUrlLabel: 'Link URL',
|
||||
primaryLinkUrlLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.LINKS.labelBySubField
|
||||
.primaryLinkUrl,
|
||||
secondaryLinksLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.LINKS.labelBySubField
|
||||
.secondaryLinks,
|
||||
} satisfies Partial<CompositeFieldLabels<FieldLinksValue>>,
|
||||
[FieldMetadataType.EMAILS]: {
|
||||
primaryEmailLabel: 'Email',
|
||||
primaryEmailLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.EMAILS.labelBySubField.primaryEmail,
|
||||
additionalEmailsLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.EMAILS.labelBySubField
|
||||
.additionalEmails,
|
||||
} satisfies Partial<CompositeFieldLabels<FieldEmailsValue>>,
|
||||
[FieldMetadataType.PHONES]: {
|
||||
primaryPhoneCountryCodeLabel: 'Phone country code',
|
||||
primaryPhoneNumberLabel: 'Phone number',
|
||||
primaryPhoneCountryCodeLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.PHONES.labelBySubField
|
||||
.primaryPhoneCountryCode,
|
||||
primaryPhoneNumberLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.PHONES.labelBySubField
|
||||
.primaryPhoneNumber,
|
||||
} satisfies Partial<CompositeFieldLabels<FieldPhonesValue>>,
|
||||
[FieldMetadataType.RICH_TEXT_V2]: {
|
||||
blocknoteLabel: 'BlockNote',
|
||||
markdownLabel: 'Markdown',
|
||||
blocknoteLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.RICH_TEXT_V2.labelBySubField
|
||||
.blocknote,
|
||||
markdownLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.RICH_TEXT_V2.labelBySubField
|
||||
.markdown,
|
||||
} satisfies Partial<CompositeFieldLabels<FieldRichTextV2Value>>,
|
||||
[FieldMetadataType.ACTOR]: {
|
||||
sourceLabel: 'Source',
|
||||
sourceLabel:
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ACTOR.labelBySubField.source,
|
||||
nameLabel: SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS.ACTOR.labelBySubField.name,
|
||||
} satisfies Partial<CompositeFieldLabels<FieldActorValue>>,
|
||||
};
|
||||
|
||||
@ -30,8 +30,6 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
ADDRESS: {
|
||||
addressCityLabel,
|
||||
addressCountryLabel,
|
||||
addressLatLabel,
|
||||
addressLngLabel,
|
||||
addressPostcodeLabel,
|
||||
addressStateLabel,
|
||||
addressStreet1Label,
|
||||
@ -88,9 +86,7 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
`${addressPostcodeLabel} (${field.name})`
|
||||
] ||
|
||||
importedStructuredRow[`${addressStateLabel} (${field.name})`] ||
|
||||
importedStructuredRow[`${addressCountryLabel} (${field.name})`] ||
|
||||
importedStructuredRow[`${addressLatLabel} (${field.name})`] ||
|
||||
importedStructuredRow[`${addressLngLabel} (${field.name})`],
|
||||
importedStructuredRow[`${addressCountryLabel} (${field.name})`],
|
||||
)
|
||||
) {
|
||||
recordToBuild[field.name] = {
|
||||
@ -112,13 +108,7 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
addressCountry: castToString(
|
||||
importedStructuredRow[`${addressCountryLabel} (${field.name})`],
|
||||
),
|
||||
addressLat: Number(
|
||||
importedStructuredRow[`${addressLatLabel} (${field.name})`],
|
||||
),
|
||||
addressLng: Number(
|
||||
importedStructuredRow[`${addressLngLabel} (${field.name})`],
|
||||
),
|
||||
} satisfies FieldAddressValue;
|
||||
} satisfies Partial<FieldAddressValue>;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { FieldLinksValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { SpreadsheetImportFieldValidationDefinition } from '@/spreadsheet-import/types';
|
||||
import { absoluteUrlSchema, isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@ -50,9 +51,11 @@ export const getSpreadSheetFieldValidationDefinitions = (
|
||||
case FieldMetadataType.LINKS:
|
||||
return [
|
||||
{
|
||||
rule: 'function',
|
||||
isValid: (value: string) =>
|
||||
absoluteUrlSchema.safeParse(value).success,
|
||||
rule: 'object',
|
||||
isValid: ({
|
||||
primaryLinkUrl,
|
||||
}: Pick<FieldLinksValue, 'primaryLinkUrl' | 'secondaryLinks'>) =>
|
||||
absoluteUrlSchema.safeParse(primaryLinkUrl).success,
|
||||
errorMessage: fieldName + ' is not valid',
|
||||
level: 'error',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user