update import auto matching (#12552)

<img width="800" alt="Screenshot 2025-06-11 at 17 45 13"
src="https://github.com/user-attachments/assets/ecc04d41-d74a-424a-9f83-14a793cf4268"
/>

closes https://github.com/twentyhq/core-team-issues/issues/905
This commit is contained in:
Etienne
2025-06-13 15:43:16 +02:00
committed by GitHub
parent 57d002d79a
commit 312632e686
14 changed files with 283 additions and 95 deletions
@@ -1,32 +1,68 @@
import { MatchColumnsStepProps } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { SpreadsheetImportFields } from '@/spreadsheet-import/types';
import {
SpreadsheetImportField,
SpreadsheetImportFields,
} from '@/spreadsheet-import/types';
import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn';
import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
import Fuse from 'fuse.js';
import { isDefined } from 'twenty-shared/utils';
export const getMatchedColumnsWithFuse = <T extends string>(
columns: SpreadsheetColumns<T>,
fields: SpreadsheetImportFields<T>,
data: MatchColumnsStepProps['data'],
) => {
export const getMatchedColumnsWithFuse = <T extends string>({
columns,
fields,
data,
}: {
columns: SpreadsheetColumns<T>;
fields: SpreadsheetImportFields<T>;
data: MatchColumnsStepProps['data'];
}) => {
const matchedColumns: SpreadsheetColumn<T>[] = [];
const fieldsToSearch = new Fuse(fields, {
keys: ['label'],
includeScore: true,
ignoreLocation: true,
threshold: 0.3,
});
const suggestedFieldsByColumnHeader: Record<
SpreadsheetColumn<T>['header'],
SpreadsheetImportField<T>[]
> = {};
for (const column of columns) {
const fieldsThatMatch = fieldsToSearch.search(column.header);
const firstMatch = fieldsThatMatch[0]?.item ?? null;
const firstMatch = fieldsThatMatch[0] || null;
const secondMatch = fieldsThatMatch[1] || null;
if (isDefined(firstMatch)) {
const newColumn = setColumn(column, firstMatch as any, data);
const isFirstMatchValid =
isDefined(firstMatch?.item) &&
isDefined(firstMatch?.score) &&
firstMatch.score < 0.4 &&
((isDefined(secondMatch?.score) &&
secondMatch.score !== firstMatch.score) ||
!isDefined(secondMatch));
const isFieldStillUnmatched = !matchedColumns.some(
(matchedColumn) =>
(matchedColumn.type === SpreadsheetColumnType.matched ||
matchedColumn.type === SpreadsheetColumnType.matchedCheckbox ||
matchedColumn.type === SpreadsheetColumnType.matchedSelect ||
matchedColumn.type === SpreadsheetColumnType.matchedSelectOptions) &&
matchedColumn?.value === firstMatch?.item?.key,
);
suggestedFieldsByColumnHeader[column.header] = fieldsThatMatch.map(
(match) => match.item as SpreadsheetImportField<T>,
);
if (isFirstMatchValid && isFieldStillUnmatched) {
const newColumn = setColumn(column, firstMatch.item as any, data);
matchedColumns.push(newColumn);
} else {
@@ -34,5 +70,5 @@ export const getMatchedColumnsWithFuse = <T extends string>(
}
}
return { matchedColumns };
return { matchedColumns, suggestedFieldsByColumnHeader };
};
@@ -0,0 +1,29 @@
import { getFieldMetadataTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFieldMetadataTypeLabel';
import { SpreadsheetImportFields } from '@/spreadsheet-import/types';
import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
import { FieldMetadataType } from 'twenty-shared/types';
export const spreadsheetBuildFieldOptions = <T extends string>(
fields: SpreadsheetImportFields<T>,
columns: SpreadsheetColumns<string>,
) => {
return fields
.filter((field) => field.fieldMetadataType !== FieldMetadataType.RICH_TEXT)
.map(({ Icon, label, key, fieldMetadataType }) => {
const isSelected =
columns.findIndex((column) => {
if ('value' in column) {
return column.value === key;
}
return false;
}) !== -1;
return {
Icon: Icon,
value: key,
label: label,
disabled: isSelected,
fieldMetadataTypeLabel: getFieldMetadataTypeLabel(fieldMetadataType),
} as const;
});
};