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:
@ -6,7 +6,6 @@ import { StepNavigationButton } from '@/spreadsheet-import/components/StepNaviga
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
import { ImportedRow, ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
|
||||
import { getMatchedColumns } from '@/spreadsheet-import/utils/getMatchedColumns';
|
||||
import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableData';
|
||||
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
|
||||
import { setIgnoreColumn } from '@/spreadsheet-import/utils/setIgnoreColumn';
|
||||
@ -26,6 +25,7 @@ import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn'
|
||||
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
|
||||
import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
|
||||
import { SpreadsheetImportField } from '@/spreadsheet-import/types/SpreadsheetImportField';
|
||||
import { getMatchedColumnsWithFuse } from '@/spreadsheet-import/utils/getMatchedColumnsWithFuse';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
@ -82,8 +82,7 @@ export const MatchColumnsStep = <T extends string>({
|
||||
const { enqueueDialog } = useDialogManager();
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const dataExample = data.slice(0, 2);
|
||||
const { fields, autoMapHeaders, autoMapDistance } =
|
||||
useSpreadsheetImportInternal<T>();
|
||||
const { fields, autoMapHeaders } = useSpreadsheetImportInternal<T>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [columns, setColumns] = useRecoilState(
|
||||
initialComputedColumnsSelector(headerValues),
|
||||
@ -264,7 +263,13 @@ export const MatchColumnsStep = <T extends string>({
|
||||
(column) => column.type === SpreadsheetColumnType.empty,
|
||||
);
|
||||
if (autoMapHeaders && isInitialColumnsState) {
|
||||
setColumns(getMatchedColumns(columns, fields, data, autoMapDistance));
|
||||
const { matchedColumns } = getMatchedColumnsWithFuse(
|
||||
columns,
|
||||
fields,
|
||||
data,
|
||||
);
|
||||
|
||||
setColumns(matchedColumns);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@ -275,7 +280,7 @@ export const MatchColumnsStep = <T extends string>({
|
||||
<StyledContent>
|
||||
<Heading
|
||||
title={t`Match Columns`}
|
||||
description={t`Select the correct field for each column you'd like to import.`}
|
||||
description={t`⚠️ Please verify the auto mapping of the columns. You can also ignore or change the mapping of the columns.`}
|
||||
/>
|
||||
<ColumnGrid
|
||||
columns={columns}
|
||||
|
||||
@ -20,7 +20,7 @@ const getExpandableContainerTitle = <T extends string>(
|
||||
|
||||
return `Match ${fieldLabel} (${
|
||||
'matchedOptions' in column &&
|
||||
column.matchedOptions.filter((option) => !isDefined(option.value)).length
|
||||
column.matchedOptions?.filter((option) => !isDefined(option.value)).length
|
||||
} Unmatched)`;
|
||||
};
|
||||
|
||||
@ -70,7 +70,7 @@ export const UnmatchColumn = <T extends string>({
|
||||
containAnimation
|
||||
>
|
||||
<StyledContentWrapper>
|
||||
{column.matchedOptions.map((option) => (
|
||||
{column.matchedOptions?.map((option) => (
|
||||
<SubMatchingSelectRow
|
||||
option={option}
|
||||
column={column}
|
||||
|
||||
@ -25,10 +25,10 @@ import {
|
||||
// @ts-expect-error Todo: remove usage of react-data-grid`
|
||||
import { RowsChangeData } from 'react-data-grid';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconTrash } from 'twenty-ui/display';
|
||||
import { Button, Toggle } from 'twenty-ui/input';
|
||||
import { generateColumns } from './components/columns';
|
||||
import { ImportedStructuredRowMetadata } from './types';
|
||||
import { Button, Toggle } from 'twenty-ui/input';
|
||||
import { IconTrash } from 'twenty-ui/display';
|
||||
|
||||
const StyledContent = styled(Modal.Content)`
|
||||
padding-left: ${({ theme }) => theme.spacing(6)};
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import { MatchColumnsStepProps } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||
|
||||
import { SpreadsheetImportFields } from '@/spreadsheet-import/types';
|
||||
import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn';
|
||||
import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
|
||||
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'],
|
||||
) => {
|
||||
const matchedColumns: SpreadsheetColumn<T>[] = [];
|
||||
|
||||
const fieldsToSearch = new Fuse(fields, {
|
||||
keys: ['label'],
|
||||
includeScore: true,
|
||||
threshold: 0.3,
|
||||
});
|
||||
|
||||
for (const column of columns) {
|
||||
const fieldsThatMatch = fieldsToSearch.search(column.header);
|
||||
|
||||
const firstMatch = fieldsThatMatch[0]?.item ?? null;
|
||||
|
||||
if (isDefined(firstMatch)) {
|
||||
const newColumn = setColumn(column, firstMatch as any, data);
|
||||
|
||||
matchedColumns.push(newColumn);
|
||||
} else {
|
||||
matchedColumns.push(column);
|
||||
}
|
||||
}
|
||||
|
||||
return { matchedColumns };
|
||||
};
|
||||
Reference in New Issue
Block a user