Basic import for select in CSV (#6047)

Enables basic support for Select import and field matching in CSV. 
It's not pretty! But it's better than what we had before.
We should iterate on that quickly

<img width="591" alt="Screenshot 2024-06-26 at 18 41 16"
src="https://github.com/twentyhq/twenty/assets/6399865/99f67f39-3f0f-4074-aac6-3200954be08a">
This commit is contained in:
Félix Malfait
2024-06-26 22:37:07 +02:00
committed by GitHub
parent 1eb9c582f3
commit 7b816e500c
5 changed files with 86 additions and 43 deletions

View File

@ -1,11 +1,11 @@
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { IconComponent, useIcons } from 'twenty-ui'; import { useIcons } from 'twenty-ui';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords'; import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
import { getSpreadSheetValidation } from '@/object-record/spreadsheet-import/util/getSpreadSheetValidation'; import { getSpreadSheetValidation } from '@/object-record/spreadsheet-import/util/getSpreadSheetValidation';
import { useSpreadsheetImport } from '@/spreadsheet-import/hooks/useSpreadsheetImport'; import { useSpreadsheetImport } from '@/spreadsheet-import/hooks/useSpreadsheetImport';
import { SpreadsheetOptions, Validation } from '@/spreadsheet-import/types'; import { Field, SpreadsheetOptions } from '@/spreadsheet-import/types';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -32,15 +32,7 @@ export const useSpreadsheetRecordImport = (objectNameSingular: string) => {
) )
.sort((a, b) => a.name.localeCompare(b.name)); .sort((a, b) => a.name.localeCompare(b.name));
const templateFields: { const templateFields: Field<string>[] = [];
icon: IconComponent;
label: string;
key: string;
fieldType: {
type: 'input' | 'checkbox';
};
validations?: Validation[];
}[] = [];
for (const field of fields) { for (const field of fields) {
if (field.type === FieldMetadataType.FullName) { if (field.type === FieldMetadataType.FullName) {
templateFields.push({ templateFields.push({
@ -80,6 +72,24 @@ export const useSpreadsheetRecordImport = (objectNameSingular: string) => {
field.label + ' (ID)', field.label + ' (ID)',
), ),
}); });
} else if (field.type === FieldMetadataType.Select) {
templateFields.push({
icon: getIcon(field.icon),
label: field.label,
key: field.name,
fieldType: {
type: 'select',
options:
field.options?.map((option) => ({
label: option.label,
value: option.value,
})) || [],
},
validations: getSpreadSheetValidation(
field.type,
field.label + ' (ID)',
),
});
} else { } else {
templateFields.push({ templateFields.push({
icon: getIcon(field.icon), icon: getIcon(field.icon),

View File

@ -125,7 +125,7 @@ export const MatchColumnSelect = ({
<DropdownMenu <DropdownMenu
data-select-disable data-select-disable
ref={dropdownContainerRef} ref={dropdownContainerRef}
width={refs.domReference.current?.clientWidth} // width={refs.domReference.current?.clientWidth}
> >
<DropdownMenuSearchInput <DropdownMenuSearchInput
value={searchFilter} value={searchFilter}

View File

@ -143,6 +143,10 @@ export const ValidationStep = <T extends string>({
(importColumn) => (importColumn) =>
(importColumn.type === ColumnType.matched && (importColumn.type === ColumnType.matched &&
importColumn.value === column.key) || importColumn.value === column.key) ||
(importColumn.type === ColumnType.matchedSelect &&
importColumn.value === column.key) ||
(importColumn.type === ColumnType.matchedSelectOptions &&
importColumn.value === column.key) ||
column.key === 'select-row', column.key === 'select-row',
).length > 0; ).length > 0;

View File

@ -20,10 +20,13 @@ describe('setColumn', () => {
value: 'oldValue', value: 'oldValue',
}; };
it('should return a matchedSelect column if field type is "select"', () => { it('should return a matchedSelectOptions column if field type is "select"', () => {
const field = { const field = {
...defaultField, ...defaultField,
fieldType: { type: 'select' }, fieldType: {
type: 'select',
options: [{ value: 'John' }, { value: 'Alice' }],
},
} as Field<'Name'>; } as Field<'Name'>;
const data = [['John'], ['Alice']]; const data = [['John'], ['Alice']];
@ -32,14 +35,16 @@ describe('setColumn', () => {
expect(result).toEqual({ expect(result).toEqual({
index: 0, index: 0,
header: 'Name', header: 'Name',
type: ColumnType.matchedSelect, type: ColumnType.matchedSelectOptions,
value: 'Name', value: 'Name',
matchedOptions: [ matchedOptions: [
{ {
entry: 'John', entry: 'John',
value: 'John',
}, },
{ {
entry: 'Alice', entry: 'Alice',
value: 'Alice',
}, },
], ],
}); });

View File

@ -2,6 +2,7 @@ import {
Column, Column,
ColumnType, ColumnType,
MatchColumnsStepProps, MatchColumnsStepProps,
MatchedOptions,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep'; } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Field } from '@/spreadsheet-import/types'; import { Field } from '@/spreadsheet-import/types';
@ -12,33 +13,56 @@ export const setColumn = <T extends string>(
field?: Field<T>, field?: Field<T>,
data?: MatchColumnsStepProps<T>['data'], data?: MatchColumnsStepProps<T>['data'],
): Column<T> => { ): Column<T> => {
switch (field?.fieldType.type) { if (field?.fieldType.type === 'select') {
case 'select': const fieldOptions = field.fieldType.options;
return { const uniqueData = uniqueEntries(
...oldColumn, data || [],
type: ColumnType.matchedSelect, oldColumn.index,
value: field.key, ) as MatchedOptions<T>[];
matchedOptions: uniqueEntries(data || [], oldColumn.index), const matchedOptions = uniqueData.map((record) => {
}; const value = fieldOptions.find(
case 'checkbox': (fieldOption) =>
return { fieldOption.value === record.entry ||
index: oldColumn.index, fieldOption.label === record.entry,
type: ColumnType.matchedCheckbox, )?.value;
value: field.key, return value
header: oldColumn.header, ? ({ ...record, value } as MatchedOptions<T>)
}; : (record as MatchedOptions<T>);
case 'input': });
return { const allMatched =
index: oldColumn.index, matchedOptions.filter((o) => o.value).length === uniqueData?.length;
type: ColumnType.matched,
value: field.key, return {
header: oldColumn.header, ...oldColumn,
}; type: allMatched
default: ? ColumnType.matchedSelectOptions
return { : ColumnType.matchedSelect,
index: oldColumn.index, value: field.key,
header: oldColumn.header, matchedOptions,
type: ColumnType.empty, };
};
} }
if (field?.fieldType.type === 'checkbox') {
return {
index: oldColumn.index,
type: ColumnType.matchedCheckbox,
value: field.key,
header: oldColumn.header,
};
}
if (field?.fieldType.type === 'input') {
return {
index: oldColumn.index,
type: ColumnType.matched,
value: field.key,
header: oldColumn.header,
};
}
return {
index: oldColumn.index,
header: oldColumn.header,
type: ColumnType.empty,
};
}; };