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:
@ -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),
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user