Files
twenty_crm/packages/twenty-front/src/modules/spreadsheet-import/utils/setColumn.ts
Etienne 713d3defef 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
2025-06-17 11:07:51 +02:00

121 lines
3.6 KiB
TypeScript

import { MatchColumnsStepProps } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { SpreadsheetImportField } from '@/spreadsheet-import/types';
import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn';
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
import { SpreadsheetMatchedOptions } from '@/spreadsheet-import/types/SpreadsheetMatchedOptions';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { z } from 'zod';
import { uniqueEntries } from './uniqueEntries';
export const setColumn = <T extends string>(
oldColumn: SpreadsheetColumn<T>,
field?: SpreadsheetImportField<T>,
data?: MatchColumnsStepProps['data'],
): SpreadsheetColumn<T> => {
if (field?.fieldType.type === 'select') {
const fieldOptions = field.fieldType.options;
const uniqueData = uniqueEntries(
data || [],
oldColumn.index,
) as SpreadsheetMatchedOptions<T>[];
const matchedOptions = uniqueData.map((record) => {
const value = fieldOptions.find(
(fieldOption) =>
fieldOption.value === record.entry ||
fieldOption.label === record.entry,
)?.value;
return value
? ({ ...record, value } as SpreadsheetMatchedOptions<T>)
: (record as SpreadsheetMatchedOptions<T>);
});
const allMatched =
matchedOptions.filter((o) => o.value).length === uniqueData?.length;
return {
...oldColumn,
type: allMatched
? SpreadsheetColumnType.matchedSelectOptions
: SpreadsheetColumnType.matchedSelect,
value: field.key,
matchedOptions,
};
}
if (field?.fieldType.type === 'multiSelect') {
const fieldOptions = field.fieldType.options;
let entries: string[] = [];
try {
entries = [
...new Set(
data
?.flatMap((row) => {
const value = row[oldColumn.index];
if (!isDefined(value)) return [];
const options = JSON.parse(z.string().parse(value));
return z.array(z.string()).parse(options);
})
.filter((entry) => typeof entry === 'string'),
),
];
} catch {
return {
index: oldColumn.index,
header: oldColumn.header,
type: SpreadsheetColumnType.matchedError,
value: field.key,
errorMessage: t`column data is not compatible with Multi-Select.`,
};
}
const matchedOptions = entries.map((entry) => {
const value = fieldOptions.find(
(fieldOption) =>
fieldOption.value === entry || fieldOption.label === entry,
)?.value;
return value
? ({ entry, value } as SpreadsheetMatchedOptions<T>)
: ({ entry } as SpreadsheetMatchedOptions<T>);
});
const areAllMatched =
matchedOptions.filter((option) => option.value).length ===
entries?.length;
return {
...oldColumn,
type: areAllMatched
? SpreadsheetColumnType.matchedSelectOptions
: SpreadsheetColumnType.matchedSelect,
value: field.key,
matchedOptions,
};
}
if (field?.fieldType.type === 'checkbox') {
return {
index: oldColumn.index,
type: SpreadsheetColumnType.matchedCheckbox,
value: field.key,
header: oldColumn.header,
};
}
if (field?.fieldType.type === 'input') {
return {
index: oldColumn.index,
type: SpreadsheetColumnType.matched,
value: field.key,
header: oldColumn.header,
};
}
return {
index: oldColumn.index,
header: oldColumn.header,
type: SpreadsheetColumnType.empty,
};
};