Import - Upsert on composite fields (#12615)
To test : - Import a record with Id column (for upsert-ing) + some subfields in each composite fields. Check that only matched subfields are updated (Main issue) - Import a record with a multi-select field - Check it works + Match multi-select field on a non multi-select column, check it does not work. (Specific bug fixed in second commit is : undefined value in multi select column (corresponding to no item selected) caused error in multi-select parsing). closes https://github.com/twentyhq/core-team-issues/issues/990
This commit is contained in:
@ -1,12 +1,5 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import {
|
import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
FieldActorForInputValue,
|
|
||||||
FieldAddressValue,
|
|
||||||
FieldEmailsValue,
|
|
||||||
FieldLinksValue,
|
|
||||||
FieldPhonesValue,
|
|
||||||
FieldRichTextV2Value,
|
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
|
||||||
import { COMPOSITE_FIELD_SUB_FIELD_LABELS } from '@/settings/data-model/constants/CompositeFieldSubFieldLabel';
|
import { COMPOSITE_FIELD_SUB_FIELD_LABELS } from '@/settings/data-model/constants/CompositeFieldSubFieldLabel';
|
||||||
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
@ -15,12 +8,42 @@ import { z } from 'zod';
|
|||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { castToString } from '~/utils/castToString';
|
import { castToString } from '~/utils/castToString';
|
||||||
import { convertCurrencyAmountToCurrencyMicros } from '~/utils/convertCurrencyToCurrencyMicros';
|
import { convertCurrencyAmountToCurrencyMicros } from '~/utils/convertCurrencyToCurrencyMicros';
|
||||||
|
import { isEmptyObject } from '~/utils/isEmptyObject';
|
||||||
|
|
||||||
type BuildRecordFromImportedStructuredRowArgs = {
|
type BuildRecordFromImportedStructuredRowArgs = {
|
||||||
importedStructuredRow: ImportedStructuredRow<any>;
|
importedStructuredRow: ImportedStructuredRow<any>;
|
||||||
fields: FieldMetadataItem[];
|
fields: FieldMetadataItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildCompositeFieldRecord = (
|
||||||
|
fieldName: string,
|
||||||
|
importedStructuredRow: ImportedStructuredRow<any>,
|
||||||
|
compositeFieldConfig: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
labelKey: string;
|
||||||
|
transform?: (value: any) => any;
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
): Record<string, any> | undefined => {
|
||||||
|
const compositeFieldRecord = Object.entries(compositeFieldConfig).reduce(
|
||||||
|
(
|
||||||
|
acc,
|
||||||
|
[compositeFieldKey, { labelKey: compositeFieldLabelKey, transform }],
|
||||||
|
) => {
|
||||||
|
const value =
|
||||||
|
importedStructuredRow[`${compositeFieldLabelKey} (${fieldName})`];
|
||||||
|
|
||||||
|
return isDefined(value)
|
||||||
|
? { ...acc, [compositeFieldKey]: transform?.(value) || value }
|
||||||
|
: acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
return isEmptyObject(compositeFieldRecord) ? undefined : compositeFieldRecord;
|
||||||
|
};
|
||||||
|
|
||||||
export const buildRecordFromImportedStructuredRow = ({
|
export const buildRecordFromImportedStructuredRow = ({
|
||||||
fields,
|
fields,
|
||||||
importedStructuredRow,
|
importedStructuredRow,
|
||||||
@ -115,10 +138,115 @@ export const buildRecordFromImportedStructuredRow = ({
|
|||||||
RICH_TEXT_V2: { blocknote: blocknoteLabel, markdown: markdownLabel },
|
RICH_TEXT_V2: { blocknote: blocknoteLabel, markdown: markdownLabel },
|
||||||
} = COMPOSITE_FIELD_SUB_FIELD_LABELS;
|
} = COMPOSITE_FIELD_SUB_FIELD_LABELS;
|
||||||
|
|
||||||
|
const COMPOSITE_FIELD_CONFIGS = {
|
||||||
|
[FieldMetadataType.CURRENCY]: {
|
||||||
|
amountMicros: {
|
||||||
|
labelKey: amountMicrosLabel,
|
||||||
|
transform: (value: any) =>
|
||||||
|
convertCurrencyAmountToCurrencyMicros(Number(value)),
|
||||||
|
},
|
||||||
|
currencyCode: { labelKey: currencyCodeLabel },
|
||||||
|
},
|
||||||
|
|
||||||
|
[FieldMetadataType.ADDRESS]: {
|
||||||
|
addressStreet1: {
|
||||||
|
labelKey: addressStreet1Label,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
addressStreet2: {
|
||||||
|
labelKey: addressStreet2Label,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
addressCity: { labelKey: addressCityLabel, transform: castToString },
|
||||||
|
addressPostcode: {
|
||||||
|
labelKey: addressPostcodeLabel,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
addressState: { labelKey: addressStateLabel, transform: castToString },
|
||||||
|
addressCountry: {
|
||||||
|
labelKey: addressCountryLabel,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[FieldMetadataType.LINKS]: {
|
||||||
|
primaryLinkLabel: {
|
||||||
|
labelKey: primaryLinkLabelLabel,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
primaryLinkUrl: {
|
||||||
|
labelKey: primaryLinkUrlLabel,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
secondaryLinks: {
|
||||||
|
labelKey: secondaryLinksLabel,
|
||||||
|
transform: linkArrayJSONSchema.parse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[FieldMetadataType.PHONES]: {
|
||||||
|
primaryPhoneCountryCode: {
|
||||||
|
labelKey: primaryPhoneCountryCodeLabel,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
primaryPhoneNumber: {
|
||||||
|
labelKey: primaryPhoneNumberLabel,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
primaryPhoneCallingCode: {
|
||||||
|
labelKey: primaryPhoneCallingCodeLabel,
|
||||||
|
transform: castToString,
|
||||||
|
},
|
||||||
|
additionalPhones: {
|
||||||
|
labelKey: additionalPhonesLabel,
|
||||||
|
transform: phoneArrayJSONSchema.parse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[FieldMetadataType.RICH_TEXT_V2]: {
|
||||||
|
blocknote: { labelKey: blocknoteLabel, transform: castToString },
|
||||||
|
markdown: { labelKey: markdownLabel, transform: castToString },
|
||||||
|
},
|
||||||
|
|
||||||
|
[FieldMetadataType.EMAILS]: {
|
||||||
|
primaryEmail: { labelKey: primaryEmailLabel, transform: castToString },
|
||||||
|
additionalEmails: {
|
||||||
|
labelKey: additionalEmailsLabel,
|
||||||
|
transform: stringArrayJSONSchema.parse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[FieldMetadataType.FULL_NAME]: {
|
||||||
|
firstName: { labelKey: firstNameLabel },
|
||||||
|
lastName: { labelKey: lastNameLabel },
|
||||||
|
},
|
||||||
|
[FieldMetadataType.ACTOR]: {
|
||||||
|
source: { labelKey: 'source', transform: () => 'IMPORT' },
|
||||||
|
context: { labelKey: 'context', transform: () => ({}) },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
const importedFieldValue = importedStructuredRow[field.name];
|
const importedFieldValue = importedStructuredRow[field.name];
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
|
case FieldMetadataType.CURRENCY:
|
||||||
|
case FieldMetadataType.ADDRESS:
|
||||||
|
case FieldMetadataType.LINKS:
|
||||||
|
case FieldMetadataType.PHONES:
|
||||||
|
case FieldMetadataType.RICH_TEXT_V2:
|
||||||
|
case FieldMetadataType.EMAILS:
|
||||||
|
case FieldMetadataType.FULL_NAME: {
|
||||||
|
const compositeData = buildCompositeFieldRecord(
|
||||||
|
field.name,
|
||||||
|
importedStructuredRow,
|
||||||
|
COMPOSITE_FIELD_CONFIGS[field.type],
|
||||||
|
);
|
||||||
|
if (isDefined(compositeData)) {
|
||||||
|
recordToBuild[field.name] = compositeData;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case FieldMetadataType.BOOLEAN:
|
case FieldMetadataType.BOOLEAN:
|
||||||
recordToBuild[field.name] =
|
recordToBuild[field.name] =
|
||||||
importedFieldValue === 'true' || importedFieldValue === true;
|
importedFieldValue === 'true' || importedFieldValue === true;
|
||||||
@ -127,193 +255,13 @@ export const buildRecordFromImportedStructuredRow = ({
|
|||||||
case FieldMetadataType.NUMERIC:
|
case FieldMetadataType.NUMERIC:
|
||||||
recordToBuild[field.name] = Number(importedFieldValue);
|
recordToBuild[field.name] = Number(importedFieldValue);
|
||||||
break;
|
break;
|
||||||
case FieldMetadataType.CURRENCY:
|
|
||||||
if (
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${amountMicrosLabel} (${field.name})`],
|
|
||||||
) ||
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${currencyCodeLabel} (${field.name})`],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = {
|
|
||||||
amountMicros: convertCurrencyAmountToCurrencyMicros(
|
|
||||||
Number(
|
|
||||||
importedStructuredRow[`${amountMicrosLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
currencyCode:
|
|
||||||
importedStructuredRow[`${currencyCodeLabel} (${field.name})`] ||
|
|
||||||
'USD',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FieldMetadataType.ADDRESS: {
|
|
||||||
if (
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${addressStreet1Label} (${field.name})`] ||
|
|
||||||
importedStructuredRow[`${addressStreet2Label} (${field.name})`] ||
|
|
||||||
importedStructuredRow[`${addressCityLabel} (${field.name})`] ||
|
|
||||||
importedStructuredRow[
|
|
||||||
`${addressPostcodeLabel} (${field.name})`
|
|
||||||
] ||
|
|
||||||
importedStructuredRow[`${addressStateLabel} (${field.name})`] ||
|
|
||||||
importedStructuredRow[`${addressCountryLabel} (${field.name})`],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = {
|
|
||||||
addressStreet1: castToString(
|
|
||||||
importedStructuredRow[`${addressStreet1Label} (${field.name})`],
|
|
||||||
),
|
|
||||||
addressStreet2: castToString(
|
|
||||||
importedStructuredRow[`${addressStreet2Label} (${field.name})`],
|
|
||||||
),
|
|
||||||
addressCity: castToString(
|
|
||||||
importedStructuredRow[`${addressCityLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
addressPostcode: castToString(
|
|
||||||
importedStructuredRow[`${addressPostcodeLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
addressState: castToString(
|
|
||||||
importedStructuredRow[`${addressStateLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
addressCountry: castToString(
|
|
||||||
importedStructuredRow[`${addressCountryLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
} satisfies Partial<FieldAddressValue>;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.LINKS: {
|
|
||||||
if (
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${primaryLinkUrlLabel} (${field.name})`] ||
|
|
||||||
importedStructuredRow[
|
|
||||||
`${primaryLinkLabelLabel} (${field.name})`
|
|
||||||
] ||
|
|
||||||
importedStructuredRow[`${secondaryLinksLabel} (${field.name})`],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = {
|
|
||||||
primaryLinkLabel: castToString(
|
|
||||||
importedStructuredRow[`${primaryLinkLabelLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
primaryLinkUrl: castToString(
|
|
||||||
importedStructuredRow[`${primaryLinkUrlLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
secondaryLinks: linkArrayJSONSchema.parse(
|
|
||||||
importedStructuredRow[`${secondaryLinksLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
} satisfies FieldLinksValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.PHONES: {
|
|
||||||
if (
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[
|
|
||||||
`${primaryPhoneCountryCodeLabel} (${field.name})`
|
|
||||||
] ||
|
|
||||||
importedStructuredRow[
|
|
||||||
`${primaryPhoneNumberLabel} (${field.name})`
|
|
||||||
] ||
|
|
||||||
importedStructuredRow[
|
|
||||||
`${primaryPhoneCallingCodeLabel} (${field.name})`
|
|
||||||
] ||
|
|
||||||
importedStructuredRow[`${additionalPhonesLabel} (${field.name})`],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = {
|
|
||||||
primaryPhoneCountryCode: castToString(
|
|
||||||
importedStructuredRow[
|
|
||||||
`${primaryPhoneCountryCodeLabel} (${field.name})`
|
|
||||||
],
|
|
||||||
),
|
|
||||||
primaryPhoneNumber: castToString(
|
|
||||||
importedStructuredRow[
|
|
||||||
`${primaryPhoneNumberLabel} (${field.name})`
|
|
||||||
],
|
|
||||||
),
|
|
||||||
primaryPhoneCallingCode: castToString(
|
|
||||||
importedStructuredRow[
|
|
||||||
`${primaryPhoneCallingCodeLabel} (${field.name})`
|
|
||||||
],
|
|
||||||
),
|
|
||||||
additionalPhones: phoneArrayJSONSchema.parse(
|
|
||||||
importedStructuredRow[`${additionalPhonesLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
} satisfies FieldPhonesValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.RICH_TEXT_V2: {
|
|
||||||
if (
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${blocknoteLabel} (${field.name})`] ||
|
|
||||||
importedStructuredRow[`${markdownLabel} (${field.name})`],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = {
|
|
||||||
blocknote: castToString(
|
|
||||||
importedStructuredRow[`${blocknoteLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
markdown: castToString(
|
|
||||||
importedStructuredRow[`${markdownLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
} satisfies FieldRichTextV2Value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.EMAILS: {
|
|
||||||
if (
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${primaryEmailLabel} (${field.name})`],
|
|
||||||
) ||
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${additionalEmailsLabel} (${field.name})`],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = {
|
|
||||||
primaryEmail: castToString(
|
|
||||||
importedStructuredRow[`${primaryEmailLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
additionalEmails: stringArrayJSONSchema.parse(
|
|
||||||
importedStructuredRow[`${additionalEmailsLabel} (${field.name})`],
|
|
||||||
),
|
|
||||||
} satisfies FieldEmailsValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.UUID:
|
|
||||||
if (
|
|
||||||
isDefined(importedFieldValue) &&
|
|
||||||
isNonEmptyString(importedFieldValue)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = importedFieldValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FieldMetadataType.RELATION:
|
case FieldMetadataType.RELATION:
|
||||||
if (
|
if (
|
||||||
isDefined(importedFieldValue) &&
|
isDefined(importedFieldValue) &&
|
||||||
isNonEmptyString(importedFieldValue)
|
isNonEmptyString(importedFieldValue)
|
||||||
) {
|
)
|
||||||
recordToBuild[field.name + 'Id'] = importedFieldValue;
|
recordToBuild[field.name + 'Id'] = importedFieldValue;
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FieldMetadataType.FULL_NAME:
|
|
||||||
if (
|
|
||||||
isDefined(
|
|
||||||
importedStructuredRow[`${firstNameLabel} (${field.name})`] ??
|
|
||||||
importedStructuredRow[`${lastNameLabel} (${field.name})`],
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recordToBuild[field.name] = {
|
|
||||||
firstName:
|
|
||||||
importedStructuredRow[`${firstNameLabel} (${field.name})`] ?? '',
|
|
||||||
lastName:
|
|
||||||
importedStructuredRow[`${lastNameLabel} (${field.name})`] ?? '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case FieldMetadataType.ACTOR:
|
case FieldMetadataType.ACTOR:
|
||||||
recordToBuild[field.name] = {
|
recordToBuild[field.name] = {
|
||||||
@ -338,7 +286,9 @@ export const buildRecordFromImportedStructuredRow = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
recordToBuild[field.name] = importedFieldValue;
|
if (isDefined(importedFieldValue)) {
|
||||||
|
recordToBuild[field.name] = importedFieldValue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,6 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
|||||||
onSelectSuggestedOption,
|
onSelectSuggestedOption,
|
||||||
onCancelSelect,
|
onCancelSelect,
|
||||||
onDoNotImportSelect,
|
onDoNotImportSelect,
|
||||||
options,
|
|
||||||
suggestedOptions,
|
suggestedOptions,
|
||||||
}: {
|
}: {
|
||||||
selectedValue: SelectOption | undefined;
|
selectedValue: SelectOption | undefined;
|
||||||
@ -40,9 +39,6 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
|||||||
onSelectSuggestedOption: (selectedSuggestedOption: SelectOption) => void;
|
onSelectSuggestedOption: (selectedSuggestedOption: SelectOption) => void;
|
||||||
onCancelSelect: () => void;
|
onCancelSelect: () => void;
|
||||||
onDoNotImportSelect: () => void;
|
onDoNotImportSelect: () => void;
|
||||||
options: readonly ReadonlyDeep<
|
|
||||||
SelectOption & { fieldMetadataTypeLabel?: string }
|
|
||||||
>[];
|
|
||||||
suggestedOptions: readonly ReadonlyDeep<
|
suggestedOptions: readonly ReadonlyDeep<
|
||||||
SelectOption & { fieldMetadataTypeLabel?: string }
|
SelectOption & { fieldMetadataTypeLabel?: string }
|
||||||
>[];
|
>[];
|
||||||
@ -121,7 +117,6 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
|||||||
key={option.value}
|
key={option.value}
|
||||||
selected={selectedValue?.value === option.value}
|
selected={selectedValue?.value === option.value}
|
||||||
onClick={() => handleSuggestedOptionClick(option)}
|
onClick={() => handleSuggestedOptionClick(option)}
|
||||||
disabled={option.disabled}
|
|
||||||
LeftIcon={option.Icon}
|
LeftIcon={option.Icon}
|
||||||
text={option.label}
|
text={option.label}
|
||||||
contextualText={option.fieldMetadataTypeLabel}
|
contextualText={option.fieldMetadataTypeLabel}
|
||||||
@ -140,10 +135,6 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
|||||||
key={field.id}
|
key={field.id}
|
||||||
selected={selectedValue?.value === field.name}
|
selected={selectedValue?.value === field.name}
|
||||||
onClick={() => handleFieldClick(field)}
|
onClick={() => handleFieldClick(field)}
|
||||||
disabled={
|
|
||||||
options.find((option) => option.value === field.name)
|
|
||||||
?.disabled && selectedValue?.value !== field.name
|
|
||||||
}
|
|
||||||
LeftIcon={getIcon(field.icon)}
|
LeftIcon={getIcon(field.icon)}
|
||||||
text={field.label}
|
text={field.label}
|
||||||
contextualText={getFieldMetadataTypeLabel(field.type)}
|
contextualText={getFieldMetadataTypeLabel(field.type)}
|
||||||
|
|||||||
@ -150,7 +150,6 @@ export const MatchColumnToFieldSelect = ({
|
|||||||
onSelectSuggestedOption={handleSelectSuggestedOption}
|
onSelectSuggestedOption={handleSelectSuggestedOption}
|
||||||
onCancelSelect={handleCancelSelectClick}
|
onCancelSelect={handleCancelSelectClick}
|
||||||
onDoNotImportSelect={handleDoNotImportSelect}
|
onDoNotImportSelect={handleDoNotImportSelect}
|
||||||
options={options}
|
|
||||||
suggestedOptions={suggestedOptions}
|
suggestedOptions={suggestedOptions}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import { setColumn } from '@/spreadsheet-import/utils/setColumn';
|
|||||||
import { setIgnoreColumn } from '@/spreadsheet-import/utils/setIgnoreColumn';
|
import { setIgnoreColumn } from '@/spreadsheet-import/utils/setIgnoreColumn';
|
||||||
import { setSubColumn } from '@/spreadsheet-import/utils/setSubColumn';
|
import { setSubColumn } from '@/spreadsheet-import/utils/setSubColumn';
|
||||||
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|
||||||
|
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
|
||||||
@ -77,7 +75,6 @@ export const MatchColumnsStep = <T extends string>({
|
|||||||
onError,
|
onError,
|
||||||
}: MatchColumnsStepProps) => {
|
}: MatchColumnsStepProps) => {
|
||||||
const { enqueueDialog } = useDialogManager();
|
const { enqueueDialog } = useDialogManager();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
|
||||||
const dataExample = data.slice(0, 2);
|
const dataExample = data.slice(0, 2);
|
||||||
const { fields } = useSpreadsheetImportInternal<T>();
|
const { fields } = useSpreadsheetImportInternal<T>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -131,10 +128,6 @@ export const MatchColumnsStep = <T extends string>({
|
|||||||
if (columnIndex === index) {
|
if (columnIndex === index) {
|
||||||
return setColumn(column, field, data);
|
return setColumn(column, field, data);
|
||||||
} else if (index === existingFieldIndex) {
|
} else if (index === existingFieldIndex) {
|
||||||
enqueueSnackBar('Another column unselected', {
|
|
||||||
detailedMessage: 'Columns cannot duplicate',
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
return setColumn(column);
|
return setColumn(column);
|
||||||
} else {
|
} else {
|
||||||
return column;
|
return column;
|
||||||
@ -143,15 +136,7 @@ export const MatchColumnsStep = <T extends string>({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[columns, onRevertIgnore, onIgnore, fields, setColumns, data],
|
||||||
columns,
|
|
||||||
onRevertIgnore,
|
|
||||||
onIgnore,
|
|
||||||
fields,
|
|
||||||
setColumns,
|
|
||||||
data,
|
|
||||||
enqueueSnackBar,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleContinue = useCallback(
|
const handleContinue = useCallback(
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn'
|
|||||||
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
|
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
|
||||||
import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions';
|
import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { uniqueEntries } from './uniqueEntries';
|
import { uniqueEntries } from './uniqueEntries';
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ export const setColumn = <T extends string>(
|
|||||||
data
|
data
|
||||||
?.flatMap((row) => {
|
?.flatMap((row) => {
|
||||||
const value = row[oldColumn.index];
|
const value = row[oldColumn.index];
|
||||||
|
if (!isDefined(value)) return [];
|
||||||
const options = JSON.parse(z.string().parse(value));
|
const options = JSON.parse(z.string().parse(value));
|
||||||
return z.array(z.string()).parse(options);
|
return z.array(z.string()).parse(options);
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user