This reverts commit cc71394863.
Regression introduced in https://github.com/twentyhq/twenty/pull/13213
The import/export use an upsert logic and when it goes through the
"update" path it fails due to the connect not being implemented yet
(should be in https://github.com/twentyhq/core-team-issues/issues/1230)
---------
Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@ -1,503 +0,0 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useBuildSpreadsheetImportFields } from '@/object-record/spreadsheet-import/hooks/useBuildSpreadSheetImportFields';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<JestObjectMetadataItemSetter>{children}</JestObjectMetadataItemSetter>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
|
||||
jest.mock('twenty-ui/display', () => ({
|
||||
useIcons: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('useBuildSpreadSheetImportFields', () => {
|
||||
const mockGetIcon = jest.fn().mockReturnValue('MockIcon');
|
||||
const mockUseIcons = useIcons as jest.MockedFunction<typeof useIcons>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseIcons.mockReturnValue({
|
||||
getIcon: mockGetIcon,
|
||||
getIcons: () => ({}),
|
||||
});
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const createMockFieldMetadataItem = (
|
||||
overrides: Partial<FieldMetadataItem> = {},
|
||||
): FieldMetadataItem => ({
|
||||
id: 'test-field-id',
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
icon: 'IconTest',
|
||||
isActive: true,
|
||||
isCustom: false,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createMockObjectMetadataItem = (
|
||||
overrides: Partial<ObjectMetadataItem> = {},
|
||||
): ObjectMetadataItem =>
|
||||
({
|
||||
id: 'test-object-id',
|
||||
nameSingular: 'testObject',
|
||||
namePlural: 'testObjects',
|
||||
labelSingular: 'Test Object',
|
||||
labelPlural: 'Test Objects',
|
||||
description: 'Test object description',
|
||||
icon: 'IconTest',
|
||||
isCustom: false,
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
isLabelSyncedWithName: false,
|
||||
isRemote: false,
|
||||
isSearchable: true,
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
fields: [],
|
||||
...overrides,
|
||||
}) as ObjectMetadataItem;
|
||||
|
||||
it('should build importFields for basic field types', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setObjectMetadataItems = useSetRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
setObjectMetadataItems([]);
|
||||
|
||||
return useBuildSpreadsheetImportFields();
|
||||
},
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
const fieldMetadataItems: FieldMetadataItem[] = [
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'textField',
|
||||
label: 'Text Field',
|
||||
}),
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.NUMBER,
|
||||
name: 'numberField',
|
||||
label: 'Number Field',
|
||||
}),
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.BOOLEAN,
|
||||
name: 'booleanField',
|
||||
label: 'Boolean Field',
|
||||
}),
|
||||
];
|
||||
|
||||
const spreadsheetImportFields =
|
||||
result.current.buildSpreadsheetImportFields(fieldMetadataItems);
|
||||
|
||||
expect(spreadsheetImportFields).toHaveLength(3);
|
||||
|
||||
expect(spreadsheetImportFields[0]).toMatchObject({
|
||||
label: 'Text Field',
|
||||
key: 'textField',
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.TEXT,
|
||||
isNestedField: false,
|
||||
});
|
||||
|
||||
expect(spreadsheetImportFields[1]).toMatchObject({
|
||||
label: 'Number Field',
|
||||
key: 'numberField',
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: FieldMetadataType.NUMBER,
|
||||
isNestedField: false,
|
||||
});
|
||||
|
||||
expect(spreadsheetImportFields[2]).toMatchObject({
|
||||
label: 'Boolean Field',
|
||||
key: 'booleanField',
|
||||
fieldType: { type: 'checkbox' },
|
||||
fieldMetadataType: FieldMetadataType.BOOLEAN,
|
||||
isNestedField: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should build importFields for select types', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setObjectMetadataItems = useSetRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
setObjectMetadataItems([]);
|
||||
|
||||
return useBuildSpreadsheetImportFields();
|
||||
},
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
const fieldMetadataItems: FieldMetadataItem[] = [
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'selectField',
|
||||
label: 'Select Field',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Option 1',
|
||||
value: 'opt1',
|
||||
color: 'red',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Option 2',
|
||||
value: 'opt2',
|
||||
color: 'blue',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.MULTI_SELECT,
|
||||
name: 'multiSelectField',
|
||||
label: 'Multi Select Field',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Tag 1',
|
||||
value: 'tag1',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Tag 2',
|
||||
value: 'tag2',
|
||||
color: 'yellow',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const spreadsheetImportFields =
|
||||
result.current.buildSpreadsheetImportFields(fieldMetadataItems);
|
||||
|
||||
expect(spreadsheetImportFields).toHaveLength(2);
|
||||
|
||||
expect(spreadsheetImportFields[0]).toMatchObject({
|
||||
label: 'Select Field',
|
||||
key: 'selectField',
|
||||
fieldType: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Option 1', value: 'opt1', color: 'red' },
|
||||
{ label: 'Option 2', value: 'opt2', color: 'blue' },
|
||||
],
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.SELECT,
|
||||
});
|
||||
|
||||
expect(spreadsheetImportFields[1]).toMatchObject({
|
||||
label: 'Multi Select Field',
|
||||
key: 'multiSelectField',
|
||||
fieldType: {
|
||||
type: 'multiSelect',
|
||||
options: [
|
||||
{ label: 'Tag 1', value: 'tag1', color: 'green' },
|
||||
{ label: 'Tag 2', value: 'tag2', color: 'yellow' },
|
||||
],
|
||||
},
|
||||
fieldMetadataType: FieldMetadataType.MULTI_SELECT,
|
||||
});
|
||||
});
|
||||
|
||||
it('should build importFields for composite types (full name)', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setObjectMetadataItems = useSetRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
setObjectMetadataItems([]);
|
||||
|
||||
return useBuildSpreadsheetImportFields();
|
||||
},
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
const fieldMetadataItems: FieldMetadataItem[] = [
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
name: 'fullName',
|
||||
label: 'Full Name',
|
||||
}),
|
||||
];
|
||||
|
||||
const spreadsheetImportFields =
|
||||
result.current.buildSpreadsheetImportFields(fieldMetadataItems);
|
||||
|
||||
expect(spreadsheetImportFields.length).toBe(2);
|
||||
|
||||
const firstNameField = spreadsheetImportFields.find((field) =>
|
||||
field.key.includes('First Name'),
|
||||
);
|
||||
const lastNameField = spreadsheetImportFields.find((field) =>
|
||||
field.key.includes('Last Name'),
|
||||
);
|
||||
|
||||
expect(firstNameField).toBeDefined();
|
||||
expect(lastNameField).toBeDefined();
|
||||
|
||||
expect(firstNameField?.isNestedField).toBe(true);
|
||||
expect(firstNameField?.isCompositeSubField).toBe(true);
|
||||
expect(lastNameField?.isNestedField).toBe(true);
|
||||
expect(lastNameField?.isCompositeSubField).toBe(true);
|
||||
});
|
||||
|
||||
it('should filter out ACTOR fields', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setObjectMetadataItems = useSetRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
setObjectMetadataItems([]);
|
||||
|
||||
return useBuildSpreadsheetImportFields();
|
||||
},
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
const fieldMetadataItems: FieldMetadataItem[] = [
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.ACTOR,
|
||||
name: 'actorField',
|
||||
label: 'Actor Field',
|
||||
}),
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'textField',
|
||||
label: 'Text Field',
|
||||
}),
|
||||
];
|
||||
|
||||
const spreadsheetImportFields =
|
||||
result.current.buildSpreadsheetImportFields(fieldMetadataItems);
|
||||
|
||||
expect(spreadsheetImportFields).toHaveLength(1);
|
||||
expect(spreadsheetImportFields[0].fieldMetadataType).toBe(
|
||||
FieldMetadataType.TEXT,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty array for unsupported field types', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setObjectMetadataItems = useSetRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
setObjectMetadataItems([]);
|
||||
|
||||
return useBuildSpreadsheetImportFields();
|
||||
},
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
const fieldMetadataItems: FieldMetadataItem[] = [
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.POSITION,
|
||||
name: 'positionField',
|
||||
label: 'Position Field',
|
||||
}),
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
name: 'tsVectorField',
|
||||
label: 'TS Vector Field',
|
||||
}),
|
||||
];
|
||||
|
||||
const spreadsheetImportFields =
|
||||
result.current.buildSpreadsheetImportFields(fieldMetadataItems);
|
||||
|
||||
expect(spreadsheetImportFields).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should build importFields for relation field type', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setObjectMetadataItems = useSetRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
|
||||
const targetObjectMetadata = createMockObjectMetadataItem({
|
||||
id: 'target-object-id',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
labelSingular: 'Company',
|
||||
labelPlural: 'Companies',
|
||||
fields: [
|
||||
createMockFieldMetadataItem({
|
||||
id: 'company-id-field',
|
||||
name: 'id',
|
||||
label: 'ID',
|
||||
type: FieldMetadataType.UUID,
|
||||
}),
|
||||
createMockFieldMetadataItem({
|
||||
id: 'company-name-field',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
}),
|
||||
createMockFieldMetadataItem({
|
||||
id: 'company-email-field',
|
||||
name: 'emails',
|
||||
label: 'Emails',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
}),
|
||||
],
|
||||
indexMetadatas: [
|
||||
{
|
||||
id: 'primary-key-index',
|
||||
name: 'primaryKeyIndex',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
isUnique: true,
|
||||
indexFieldMetadatas: [
|
||||
{
|
||||
id: 'index-field-1',
|
||||
fieldMetadataId: 'company-id-field',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'unique-name-index',
|
||||
name: 'uniqueNameIndex',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
isUnique: true,
|
||||
indexFieldMetadatas: [
|
||||
{
|
||||
id: 'index-field-2',
|
||||
fieldMetadataId: 'company-name-field',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'unique-email-index',
|
||||
name: 'uniqueEmailIndex',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
isUnique: true,
|
||||
indexFieldMetadatas: [
|
||||
{
|
||||
id: 'index-field-3',
|
||||
fieldMetadataId: 'company-email-field',
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as IndexMetadataItem[],
|
||||
});
|
||||
|
||||
setObjectMetadataItems([targetObjectMetadata]);
|
||||
|
||||
return useBuildSpreadsheetImportFields();
|
||||
},
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
const fieldMetadataItems: FieldMetadataItem[] = [
|
||||
createMockFieldMetadataItem({
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
label: 'Company',
|
||||
relation: {
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
targetObjectMetadata: {
|
||||
id: 'target-object-id',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
},
|
||||
} as any,
|
||||
}),
|
||||
];
|
||||
|
||||
const spreadsheetImportFields =
|
||||
result.current.buildSpreadsheetImportFields(fieldMetadataItems);
|
||||
|
||||
expect(spreadsheetImportFields).toHaveLength(4);
|
||||
|
||||
const idField = spreadsheetImportFields.find((field) =>
|
||||
field.key.includes('id (company)'),
|
||||
);
|
||||
expect(idField).toBeDefined();
|
||||
expect(idField).toMatchObject({
|
||||
label: 'Company / ID',
|
||||
key: 'id (company)',
|
||||
fieldMetadataItemId: 'test-field-id',
|
||||
fieldMetadataType: FieldMetadataType.RELATION,
|
||||
isNestedField: true,
|
||||
isRelationConnectField: true,
|
||||
uniqueFieldMetadataItem: {
|
||||
id: 'company-id-field',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
},
|
||||
});
|
||||
|
||||
const nameField = spreadsheetImportFields.find((field) =>
|
||||
field.key.includes('name (company)'),
|
||||
);
|
||||
expect(nameField).toBeDefined();
|
||||
expect(nameField).toMatchObject({
|
||||
label: 'Company / Name',
|
||||
key: 'name (company)',
|
||||
fieldMetadataItemId: 'test-field-id',
|
||||
fieldMetadataType: FieldMetadataType.RELATION,
|
||||
isNestedField: true,
|
||||
isRelationConnectField: true,
|
||||
uniqueFieldMetadataItem: {
|
||||
id: 'company-name-field',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
},
|
||||
});
|
||||
|
||||
const primaryEmailField = spreadsheetImportFields.find((field) =>
|
||||
field.key.includes('primaryEmail-emails (company)'),
|
||||
);
|
||||
expect(primaryEmailField).toBeDefined();
|
||||
expect(primaryEmailField).toMatchObject({
|
||||
isNestedField: true,
|
||||
isCompositeSubField: true,
|
||||
isRelationConnectField: true,
|
||||
compositeSubFieldKey: 'primaryEmail',
|
||||
uniqueFieldMetadataItem: {
|
||||
id: 'company-email-field',
|
||||
name: 'emails',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -383,13 +383,9 @@ describe('useSpreadsheetCompanyImport', () => {
|
||||
expect(spreadsheetImportDialogAfterOpen.options?.onSubmit).toBeInstanceOf(
|
||||
Function,
|
||||
);
|
||||
expect(spreadsheetImportDialogAfterOpen.options).toHaveProperty(
|
||||
'spreadsheetImportFields',
|
||||
);
|
||||
expect(spreadsheetImportDialogAfterOpen.options).toHaveProperty('fields');
|
||||
expect(
|
||||
Array.isArray(
|
||||
spreadsheetImportDialogAfterOpen.options?.spreadsheetImportFields,
|
||||
),
|
||||
Array.isArray(spreadsheetImportDialogAfterOpen.options?.fields),
|
||||
).toBe(true);
|
||||
|
||||
act(() => {
|
||||
|
||||
@ -0,0 +1,177 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
|
||||
import { AvailableFieldForImport } from '@/object-record/spreadsheet-import/types/AvailableFieldForImport';
|
||||
import { getSpreadSheetFieldValidationDefinitions } from '@/object-record/spreadsheet-import/utils/getSpreadSheetFieldValidationDefinitions';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useBuildAvailableFieldsForImport = () => {
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const buildAvailableFieldsForImport = (
|
||||
fieldMetadataItems: FieldMetadataItem[],
|
||||
) => {
|
||||
const availableFieldsForImport: AvailableFieldForImport[] = [];
|
||||
|
||||
const createBaseField = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
overrides: Partial<AvailableFieldForImport> = {},
|
||||
customLabel?: string,
|
||||
): AvailableFieldForImport => ({
|
||||
Icon: getIcon(fieldMetadataItem.icon),
|
||||
label: customLabel ?? fieldMetadataItem.label,
|
||||
key: fieldMetadataItem.name,
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
customLabel ?? fieldMetadataItem.label,
|
||||
),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const handleCompositeFieldWithLabels = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
fieldType: CompositeFieldType,
|
||||
) => {
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[fieldType].subFields.forEach(
|
||||
({ subFieldName, subFieldLabel, isImportable }) => {
|
||||
if (!isImportable) return;
|
||||
const label = `${fieldMetadataItem.label} / ${subFieldLabel}`;
|
||||
|
||||
availableFieldsForImport.push(
|
||||
createBaseField(fieldMetadataItem, {
|
||||
label,
|
||||
key: `${subFieldLabel} (${fieldMetadataItem.name})`,
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
label,
|
||||
subFieldName,
|
||||
),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleSelectField = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
isMulti = false,
|
||||
) => {
|
||||
availableFieldsForImport.push(
|
||||
createBaseField(fieldMetadataItem, {
|
||||
fieldType: {
|
||||
type: isMulti ? 'multiSelect' : 'select',
|
||||
options:
|
||||
fieldMetadataItem.options?.map((option) => ({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
color: option.color,
|
||||
})) || [],
|
||||
},
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
`${fieldMetadataItem.label} (ID)`,
|
||||
),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const fieldTypeHandlers: Record<
|
||||
string,
|
||||
(fieldMetadataItem: FieldMetadataItem) => void
|
||||
> = {
|
||||
[FieldMetadataType.FULL_NAME]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.FULL_NAME,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.ADDRESS]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.ADDRESS,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.LINKS]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.LINKS,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.EMAILS]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.EMAILS,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.PHONES]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.PHONES,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.RICH_TEXT_V2]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.CURRENCY]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.CURRENCY,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.ACTOR]: (fieldMetadataItem) => {
|
||||
handleCompositeFieldWithLabels(
|
||||
fieldMetadataItem,
|
||||
FieldMetadataType.ACTOR,
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.RELATION]: (fieldMetadataItem) => {
|
||||
const label = `${fieldMetadataItem.label} (ID)`;
|
||||
availableFieldsForImport.push(
|
||||
createBaseField(fieldMetadataItem, {
|
||||
label,
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
label,
|
||||
),
|
||||
}),
|
||||
);
|
||||
},
|
||||
[FieldMetadataType.SELECT]: (fieldMetadataItem) => {
|
||||
handleSelectField(fieldMetadataItem, false);
|
||||
},
|
||||
[FieldMetadataType.MULTI_SELECT]: (fieldMetadataItem) => {
|
||||
handleSelectField(fieldMetadataItem, true);
|
||||
},
|
||||
[FieldMetadataType.BOOLEAN]: (fieldMetadataItem) => {
|
||||
availableFieldsForImport.push(
|
||||
createBaseField(fieldMetadataItem, {
|
||||
fieldType: { type: 'checkbox' },
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
default: (fieldMetadataItem) => {
|
||||
availableFieldsForImport.push(createBaseField(fieldMetadataItem));
|
||||
},
|
||||
};
|
||||
|
||||
for (const fieldMetadataItem of fieldMetadataItems) {
|
||||
const handler =
|
||||
fieldTypeHandlers[fieldMetadataItem.type] || fieldTypeHandlers.default;
|
||||
handler(fieldMetadataItem);
|
||||
}
|
||||
|
||||
return availableFieldsForImport;
|
||||
};
|
||||
|
||||
return { buildAvailableFieldsForImport };
|
||||
};
|
||||
@ -1,284 +0,0 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
|
||||
import { getSpreadSheetFieldValidationDefinitions } from '@/object-record/spreadsheet-import/utils/getSpreadSheetFieldValidationDefinitions';
|
||||
import { getRelationConnectSubFieldKey } from '@/object-record/spreadsheet-import/utils/spreadSheetGetRelationConnectSubFieldKey';
|
||||
import { getCompositeSubFieldKey } from '@/object-record/spreadsheet-import/utils/spreadsheetImportGetCompositeSubFieldKey';
|
||||
import { getCompositeSubFieldLabelWithFieldLabel } from '@/object-record/spreadsheet-import/utils/spreadsheetImportGetCompositeSubFieldLabelWithFieldLabel';
|
||||
import { getRelationConnectSubFieldLabel } from '@/object-record/spreadsheet-import/utils/spreadsheetImportGetRelationConnectSubFieldLabel';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||
import {
|
||||
SpreadsheetImportField,
|
||||
SpreadsheetImportFields,
|
||||
} from '@/spreadsheet-import/types';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
assertUnreachable,
|
||||
getUniqueConstraintsFields,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useBuildSpreadsheetImportFields = () => {
|
||||
const { getIcon } = useIcons();
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const buildSpreadsheetImportFields = (
|
||||
fieldMetadataItems: FieldMetadataItem[],
|
||||
): SpreadsheetImportFields => {
|
||||
return fieldMetadataItems
|
||||
.filter((field) => field.type !== FieldMetadataType.ACTOR)
|
||||
.flatMap((fieldMetadataItem) =>
|
||||
buildSpreadsheetImportField(fieldMetadataItem),
|
||||
);
|
||||
};
|
||||
|
||||
const buildSpreadsheetImportField = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
relationConnectFieldOverrides?: Partial<SpreadsheetImportField>,
|
||||
) => {
|
||||
switch (fieldMetadataItem.type) {
|
||||
case FieldMetadataType.ADDRESS:
|
||||
case FieldMetadataType.CURRENCY:
|
||||
case FieldMetadataType.EMAILS:
|
||||
case FieldMetadataType.FULL_NAME:
|
||||
case FieldMetadataType.LINKS:
|
||||
case FieldMetadataType.PHONES:
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
return handleCompositeFields({
|
||||
fieldMetadataItem,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
});
|
||||
case FieldMetadataType.RELATION:
|
||||
return handleRelationField(fieldMetadataItem);
|
||||
case FieldMetadataType.SELECT:
|
||||
case FieldMetadataType.MULTI_SELECT:
|
||||
return [
|
||||
handleSelectField(
|
||||
fieldMetadataItem,
|
||||
fieldMetadataItem.type === FieldMetadataType.MULTI_SELECT,
|
||||
relationConnectFieldOverrides,
|
||||
),
|
||||
];
|
||||
case FieldMetadataType.BOOLEAN:
|
||||
return [
|
||||
createBaseField(fieldMetadataItem, {
|
||||
fieldType: { type: 'checkbox' },
|
||||
...(isDefined(relationConnectFieldOverrides)
|
||||
? relationConnectFieldOverrides
|
||||
: {}),
|
||||
}),
|
||||
];
|
||||
case FieldMetadataType.DATE_TIME:
|
||||
case FieldMetadataType.DATE:
|
||||
case FieldMetadataType.NUMBER:
|
||||
case FieldMetadataType.NUMERIC:
|
||||
case FieldMetadataType.TEXT:
|
||||
case FieldMetadataType.UUID:
|
||||
case FieldMetadataType.ARRAY:
|
||||
case FieldMetadataType.RATING:
|
||||
case FieldMetadataType.RAW_JSON:
|
||||
return [
|
||||
createBaseField(fieldMetadataItem, relationConnectFieldOverrides),
|
||||
];
|
||||
|
||||
case FieldMetadataType.POSITION:
|
||||
case FieldMetadataType.MORPH_RELATION:
|
||||
case FieldMetadataType.ACTOR:
|
||||
case FieldMetadataType.TS_VECTOR:
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
return [];
|
||||
|
||||
default:
|
||||
return assertUnreachable(fieldMetadataItem.type);
|
||||
}
|
||||
};
|
||||
|
||||
const createBaseField = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
overrides: Partial<SpreadsheetImportField> = {},
|
||||
): SpreadsheetImportField => {
|
||||
return {
|
||||
Icon: getIcon(fieldMetadataItem.icon),
|
||||
label: fieldMetadataItem.label,
|
||||
key: fieldMetadataItem.name,
|
||||
fieldMetadataItemId: fieldMetadataItem.id,
|
||||
fieldType: { type: 'input' },
|
||||
fieldMetadataType: fieldMetadataItem.type,
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
fieldMetadataItem.label,
|
||||
),
|
||||
isNestedField: false,
|
||||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
const handleCompositeFields = ({
|
||||
fieldMetadataItem,
|
||||
fieldType,
|
||||
}: {
|
||||
fieldMetadataItem: FieldMetadataItem;
|
||||
fieldType: CompositeFieldType;
|
||||
}) => {
|
||||
const spreadsheetImportFields: SpreadsheetImportField[] = [];
|
||||
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[fieldType].subFields.forEach(
|
||||
({ subFieldName, isImportable, subFieldLabel }) => {
|
||||
if (!isImportable) return;
|
||||
const label = getCompositeSubFieldLabelWithFieldLabel(
|
||||
fieldMetadataItem,
|
||||
subFieldLabel,
|
||||
);
|
||||
|
||||
spreadsheetImportFields.push(
|
||||
createBaseField(fieldMetadataItem, {
|
||||
label,
|
||||
key: getCompositeSubFieldKey(fieldMetadataItem, subFieldName),
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
label,
|
||||
subFieldName,
|
||||
),
|
||||
isNestedField: true,
|
||||
isCompositeSubField: true,
|
||||
compositeSubFieldKey: subFieldName,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return spreadsheetImportFields;
|
||||
};
|
||||
|
||||
const handleCompositeFieldFromRelationConnectField = ({
|
||||
fieldMetadataItem,
|
||||
uniqueConstraintField,
|
||||
uniqueConstraintType,
|
||||
}: {
|
||||
fieldMetadataItem: FieldMetadataItem;
|
||||
uniqueConstraintField: FieldMetadataItem;
|
||||
uniqueConstraintType: CompositeFieldType;
|
||||
}) => {
|
||||
const spreadsheetImportFields: SpreadsheetImportField[] = [];
|
||||
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[
|
||||
uniqueConstraintType
|
||||
].subFields.forEach(
|
||||
({ subFieldName, isImportable, isIncludedInUniqueConstraint }) => {
|
||||
if (!isImportable || !isIncludedInUniqueConstraint) return;
|
||||
|
||||
spreadsheetImportFields.push(
|
||||
createBaseField(fieldMetadataItem, {
|
||||
label: getRelationConnectSubFieldLabel(
|
||||
fieldMetadataItem,
|
||||
uniqueConstraintField,
|
||||
subFieldName,
|
||||
),
|
||||
key: getRelationConnectSubFieldKey(
|
||||
fieldMetadataItem,
|
||||
uniqueConstraintField,
|
||||
subFieldName,
|
||||
),
|
||||
fieldValidationDefinitions:
|
||||
getSpreadSheetFieldValidationDefinitions(
|
||||
uniqueConstraintField.type,
|
||||
uniqueConstraintField.name,
|
||||
subFieldName,
|
||||
),
|
||||
isNestedField: true,
|
||||
isCompositeSubField: true,
|
||||
compositeSubFieldKey: subFieldName,
|
||||
uniqueFieldMetadataItem: uniqueConstraintField,
|
||||
isRelationConnectField: true,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return spreadsheetImportFields;
|
||||
};
|
||||
|
||||
const handleSelectField = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
isMulti = false,
|
||||
subFieldOverrides?: Record<string, any>,
|
||||
) =>
|
||||
createBaseField(fieldMetadataItem, {
|
||||
fieldType: {
|
||||
type: isMulti ? 'multiSelect' : 'select',
|
||||
options:
|
||||
fieldMetadataItem.options?.map((option) => ({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
color: option.color,
|
||||
})) || [],
|
||||
...(isDefined(subFieldOverrides) ? subFieldOverrides : {}),
|
||||
},
|
||||
fieldValidationDefinitions: getSpreadSheetFieldValidationDefinitions(
|
||||
fieldMetadataItem.type,
|
||||
`${fieldMetadataItem.label} (ID)`,
|
||||
),
|
||||
});
|
||||
|
||||
const handleRelationField = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
const spreadsheetImportFields: SpreadsheetImportField[] = [];
|
||||
|
||||
const isManyToOneRelation =
|
||||
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE;
|
||||
|
||||
const targetObjectMetadataItem = objectMetadataItems?.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
fieldMetadataItem.relation?.targetObjectMetadata.id,
|
||||
);
|
||||
|
||||
if (isManyToOneRelation && isDefined(targetObjectMetadataItem)) {
|
||||
const uniqueConstraintFields = getUniqueConstraintsFields<
|
||||
FieldMetadataItem,
|
||||
ObjectMetadataItem
|
||||
>(targetObjectMetadataItem);
|
||||
|
||||
//todo - update logic when composite unique indexes will be supported
|
||||
for (const uniqueConstraintField of uniqueConstraintFields.flat()) {
|
||||
if (isCompositeFieldType(uniqueConstraintField.type)) {
|
||||
spreadsheetImportFields.push(
|
||||
...handleCompositeFieldFromRelationConnectField({
|
||||
fieldMetadataItem,
|
||||
uniqueConstraintField,
|
||||
uniqueConstraintType: uniqueConstraintField.type,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
spreadsheetImportFields.push(
|
||||
...buildSpreadsheetImportField(uniqueConstraintField, {
|
||||
isNestedField: true,
|
||||
isCompositeSubField: false,
|
||||
isRelationConnectField: true,
|
||||
fieldMetadataItemId: fieldMetadataItem.id,
|
||||
fieldMetadataType: FieldMetadataType.RELATION,
|
||||
uniqueFieldMetadataItem: uniqueConstraintField,
|
||||
label: getRelationConnectSubFieldLabel(
|
||||
fieldMetadataItem,
|
||||
uniqueConstraintField,
|
||||
),
|
||||
key: getRelationConnectSubFieldKey(
|
||||
fieldMetadataItem,
|
||||
uniqueConstraintField,
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return spreadsheetImportFields;
|
||||
};
|
||||
|
||||
return { buildSpreadsheetImportFields };
|
||||
};
|
||||
@ -1,8 +1,8 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useBatchCreateManyRecords } from '@/object-record/hooks/useBatchCreateManyRecords';
|
||||
import { useBuildSpreadsheetImportFields } from '@/object-record/spreadsheet-import/hooks/useBuildSpreadSheetImportFields';
|
||||
import { useBuildAvailableFieldsForImport } from '@/object-record/spreadsheet-import/hooks/useBuildAvailableFieldsForImport';
|
||||
import { buildRecordFromImportedStructuredRow } from '@/object-record/spreadsheet-import/utils/buildRecordFromImportedStructuredRow';
|
||||
import { spreadsheetImportFilterAvailableFieldMetadataItems } from '@/object-record/spreadsheet-import/utils/spreadsheetImportFilterAvailableFieldMetadataItems';
|
||||
import { spreadsheetImportFilterAvailableFieldMetadataItems } from '@/object-record/spreadsheet-import/utils/spreadsheetImportFilterAvailableFieldMetadataItems.ts';
|
||||
import { spreadsheetImportGetUnicityRowHook } from '@/object-record/spreadsheet-import/utils/spreadsheetImportGetUnicityRowHook';
|
||||
import { SpreadsheetImportCreateRecordsBatchSize } from '@/spreadsheet-import/constants/SpreadsheetImportCreateRecordsBatchSize';
|
||||
import { useOpenSpreadsheetImportDialog } from '@/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog';
|
||||
@ -10,13 +10,12 @@ import { spreadsheetImportCreatedRecordsProgressState } from '@/spreadsheet-impo
|
||||
import { SpreadsheetImportDialogOptions } from '@/spreadsheet-import/types';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useOpenObjectRecordsSpreadsheetImportDialog = (
|
||||
objectNameSingular: string,
|
||||
) => {
|
||||
const { openSpreadsheetImportDialog } = useOpenSpreadsheetImportDialog();
|
||||
const { buildSpreadsheetImportFields } = useBuildSpreadsheetImportFields();
|
||||
|
||||
const { openSpreadsheetImportDialog } = useOpenSpreadsheetImportDialog<any>();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
@ -36,19 +35,28 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
|
||||
abortController,
|
||||
});
|
||||
|
||||
const { buildAvailableFieldsForImport } = useBuildAvailableFieldsForImport();
|
||||
|
||||
const openObjectRecordsSpreadsheetImportDialog = (
|
||||
options?: Omit<
|
||||
SpreadsheetImportDialogOptions,
|
||||
SpreadsheetImportDialogOptions<any>,
|
||||
'fields' | 'isOpen' | 'onClose'
|
||||
>,
|
||||
) => {
|
||||
//All fields that can be imported (included matchable and auto-filled)
|
||||
const availableFieldMetadataItemsToImport =
|
||||
spreadsheetImportFilterAvailableFieldMetadataItems(
|
||||
objectMetadataItem.fields,
|
||||
);
|
||||
|
||||
const spreadsheetImportFields = buildSpreadsheetImportFields(
|
||||
availableFieldMetadataItemsToImport,
|
||||
const availableFieldMetadataItemsForMatching =
|
||||
availableFieldMetadataItemsToImport.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.type !== FieldMetadataType.ACTOR,
|
||||
);
|
||||
|
||||
const availableFieldsForMatching = buildAvailableFieldsForImport(
|
||||
availableFieldMetadataItemsForMatching,
|
||||
);
|
||||
|
||||
openSpreadsheetImportDialog({
|
||||
@ -58,8 +66,7 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
|
||||
const fieldMapping: Record<string, any> =
|
||||
buildRecordFromImportedStructuredRow({
|
||||
importedStructuredRow: record,
|
||||
fieldMetadataItems: availableFieldMetadataItemsToImport,
|
||||
spreadsheetImportFields,
|
||||
fields: availableFieldMetadataItemsToImport,
|
||||
});
|
||||
|
||||
return fieldMapping;
|
||||
@ -76,7 +83,7 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
|
||||
});
|
||||
}
|
||||
},
|
||||
spreadsheetImportFields,
|
||||
fields: availableFieldsForMatching,
|
||||
availableFieldMetadataItems: availableFieldMetadataItemsToImport,
|
||||
onAbortSubmit: () => {
|
||||
abortController.abort();
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
import {
|
||||
SpreadsheetImportFieldType,
|
||||
SpreadsheetImportFieldValidationDefinition,
|
||||
} from '@/spreadsheet-import/types';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { IconComponent } from 'twenty-ui/display';
|
||||
|
||||
export type AvailableFieldForImport = {
|
||||
Icon: IconComponent;
|
||||
label: string;
|
||||
key: string;
|
||||
fieldType: SpreadsheetImportFieldType;
|
||||
fieldValidationDefinitions?: SpreadsheetImportFieldValidationDefinition[];
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
};
|
||||
@ -1,20 +1,15 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
|
||||
import { buildRecordFromImportedStructuredRow } from '@/object-record/spreadsheet-import/utils/buildRecordFromImportedStructuredRow';
|
||||
import {
|
||||
ImportedStructuredRow,
|
||||
SpreadsheetImportField,
|
||||
} from '@/spreadsheet-import/types';
|
||||
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { RelationType } from '~/generated/graphql';
|
||||
|
||||
describe('buildRecordFromImportedStructuredRow', () => {
|
||||
it('should successfully build a record from imported structured row', () => {
|
||||
const importedStructuredRow: ImportedStructuredRow = {
|
||||
const importedStructuredRow: ImportedStructuredRow<string> = {
|
||||
booleanField: 'true',
|
||||
numberField: '30',
|
||||
multiSelectField: '["tag1", "tag2", "tag3"]',
|
||||
'nameField (relationField)': 'John Doe',
|
||||
relationField: 'company-123',
|
||||
selectField: 'option1',
|
||||
arrayField: '["item1", "item2", "item3"]',
|
||||
jsonField: '{"key": "value", "nested": {"prop": "data"}}',
|
||||
@ -127,9 +122,6 @@ describe('buildRecordFromImportedStructuredRow', () => {
|
||||
updatedAt: '2023-01-01',
|
||||
icon: 'IconBuilding',
|
||||
description: null,
|
||||
relation: {
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
} as FieldMetadataItemRelation,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
@ -345,25 +337,9 @@ describe('buildRecordFromImportedStructuredRow', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const spreadsheetImportFields = [
|
||||
{
|
||||
fieldMetadataItemId: '6',
|
||||
isNestedField: false,
|
||||
isRelationConnectField: true,
|
||||
label: 'Relation Field / Name Field',
|
||||
key: 'nameField (relationField)',
|
||||
fieldMetadataType: FieldMetadataType.RELATION,
|
||||
uniqueFieldMetadataItem: {
|
||||
name: 'nameField',
|
||||
type: FieldMetadataType.TEXT,
|
||||
},
|
||||
},
|
||||
] as SpreadsheetImportField[];
|
||||
|
||||
const result = buildRecordFromImportedStructuredRow({
|
||||
importedStructuredRow,
|
||||
fieldMetadataItems: fields,
|
||||
spreadsheetImportFields,
|
||||
fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -374,14 +350,7 @@ describe('buildRecordFromImportedStructuredRow', () => {
|
||||
booleanField: true,
|
||||
numberField: 30,
|
||||
multiSelectField: ['tag1', 'tag2', 'tag3'],
|
||||
relationField: {
|
||||
connect: {
|
||||
where: {
|
||||
nameField: 'John Doe',
|
||||
},
|
||||
},
|
||||
},
|
||||
relationFieldId: undefined,
|
||||
relationFieldId: 'company-123',
|
||||
selectField: 'option1',
|
||||
arrayField: ['item1', 'item2', 'item3'],
|
||||
jsonField: { key: 'value', nested: { prop: 'data' } },
|
||||
@ -437,8 +406,8 @@ describe('buildRecordFromImportedStructuredRow', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully build a record from imported structured row with primary phone number (without calling code)', () => {
|
||||
const importedStructuredRow: ImportedStructuredRow = {
|
||||
it('should handle case where user provides only a primaryPhoneNumber without calling code', () => {
|
||||
const importedStructuredRow: ImportedStructuredRow<string> = {
|
||||
'Primary Phone Number (phoneField)': '5550123',
|
||||
};
|
||||
|
||||
@ -461,8 +430,7 @@ describe('buildRecordFromImportedStructuredRow', () => {
|
||||
|
||||
const result = buildRecordFromImportedStructuredRow({
|
||||
importedStructuredRow,
|
||||
fieldMetadataItems: fields,
|
||||
spreadsheetImportFields: [],
|
||||
fields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -472,64 +440,4 @@ describe('buildRecordFromImportedStructuredRow', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully build a record from imported structured row with relation composite subfield', () => {
|
||||
const importedStructuredRow: ImportedStructuredRow = {
|
||||
'emailField (relationField)': 'john.doe@example.com',
|
||||
};
|
||||
|
||||
const fields: FieldMetadataItem[] = [
|
||||
{
|
||||
id: '6',
|
||||
name: 'relationField',
|
||||
label: 'Relation Field',
|
||||
type: FieldMetadataType.RELATION,
|
||||
isNullable: true,
|
||||
isActive: true,
|
||||
isCustom: false,
|
||||
isSystem: false,
|
||||
createdAt: '2023-01-01',
|
||||
updatedAt: '2023-01-01',
|
||||
icon: 'IconBuilding',
|
||||
description: null,
|
||||
relation: {
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
} as FieldMetadataItemRelation,
|
||||
},
|
||||
];
|
||||
|
||||
const spreadsheetImportFields = [
|
||||
{
|
||||
fieldMetadataItemId: '6',
|
||||
isNestedField: false,
|
||||
isRelationConnectField: true,
|
||||
label: 'Relation Field / Email Field',
|
||||
key: 'emailField (relationField)',
|
||||
fieldMetadataType: FieldMetadataType.RELATION,
|
||||
uniqueFieldMetadataItem: {
|
||||
name: 'emailField',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
},
|
||||
compositeSubFieldKey: 'primaryEmail',
|
||||
},
|
||||
] as SpreadsheetImportField[];
|
||||
|
||||
const result = buildRecordFromImportedStructuredRow({
|
||||
importedStructuredRow,
|
||||
fieldMetadataItems: fields,
|
||||
spreadsheetImportFields,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
relationField: {
|
||||
connect: {
|
||||
where: {
|
||||
emailField: {
|
||||
primaryEmail: 'john.doe@example.com',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -79,7 +79,7 @@ describe('spreadsheetImportGetUnicityRowHook', () => {
|
||||
|
||||
it('should return row with error if row is not unique - index on composite field', () => {
|
||||
const hook = spreadsheetImportGetUnicityRowHook(mockObjectMetadataItem);
|
||||
const testData: ImportedStructuredRow[] = [
|
||||
const testData: ImportedStructuredRow<string>[] = [
|
||||
{ 'Link URL (domainName)': 'https://duplicaTe.com' },
|
||||
{ 'Link URL (domainName)': 'https://duplicate.com' },
|
||||
{ 'Link URL (domainName)': 'https://other.com' },
|
||||
@ -100,7 +100,7 @@ describe('spreadsheetImportGetUnicityRowHook', () => {
|
||||
it('should return row with error if row is not unique - index on id', () => {
|
||||
const hook = spreadsheetImportGetUnicityRowHook(mockObjectMetadataItem);
|
||||
|
||||
const testData: ImportedStructuredRow[] = [
|
||||
const testData: ImportedStructuredRow<string>[] = [
|
||||
{ 'Link URL (domainName)': 'test.com', id: '1' },
|
||||
{ 'Link URL (domainName)': 'test2.com', id: '1' },
|
||||
{ 'Link URL (domainName)': 'test3.com', id: '3' },
|
||||
@ -120,7 +120,7 @@ describe('spreadsheetImportGetUnicityRowHook', () => {
|
||||
it('should return row with error if row is not unique - multi fields index', () => {
|
||||
const hook = spreadsheetImportGetUnicityRowHook(mockObjectMetadataItem);
|
||||
|
||||
const testData: ImportedStructuredRow[] = [
|
||||
const testData: ImportedStructuredRow<string>[] = [
|
||||
{ name: 'test', employees: '100', id: '1' },
|
||||
{ name: 'test', employees: '100', id: '2' },
|
||||
{ name: 'test', employees: '101', id: '3' },
|
||||
@ -143,7 +143,7 @@ describe('spreadsheetImportGetUnicityRowHook', () => {
|
||||
it('should not add error if row values are unique', () => {
|
||||
const hook = spreadsheetImportGetUnicityRowHook(mockObjectMetadataItem);
|
||||
|
||||
const testData: ImportedStructuredRow[] = [
|
||||
const testData: ImportedStructuredRow<string>[] = [
|
||||
{
|
||||
name: 'test',
|
||||
'Link URL (domainName)': 'test.com',
|
||||
|
||||
@ -1,38 +1,31 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getCompositeSubFieldKey } from '@/object-record/spreadsheet-import/utils/spreadsheetImportGetCompositeSubFieldKey';
|
||||
import {
|
||||
ImportedStructuredRow,
|
||||
SpreadsheetImportFields,
|
||||
} from '@/spreadsheet-import/types';
|
||||
import { getSubFieldOptionKey } from '@/object-record/spreadsheet-import/utils/getSubFieldOptionKey';
|
||||
import { ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { CountryCode, parsePhoneNumberWithError } from 'libphonenumber-js';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { z } from 'zod';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { castToString } from '~/utils/castToString';
|
||||
import { convertCurrencyAmountToCurrencyMicros } from '~/utils/convertCurrencyToCurrencyMicros';
|
||||
import { isEmptyObject } from '~/utils/isEmptyObject';
|
||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||
|
||||
type BuildRecordFromImportedStructuredRowArgs = {
|
||||
importedStructuredRow: ImportedStructuredRow;
|
||||
fieldMetadataItems: FieldMetadataItem[];
|
||||
spreadsheetImportFields: SpreadsheetImportFields;
|
||||
importedStructuredRow: ImportedStructuredRow<any>;
|
||||
fields: FieldMetadataItem[];
|
||||
};
|
||||
|
||||
const buildCompositeFieldRecord = (
|
||||
field: FieldMetadataItem,
|
||||
importedStructuredRow: ImportedStructuredRow,
|
||||
importedStructuredRow: ImportedStructuredRow<any>,
|
||||
compositeFieldConfig: Record<string, ((value: any) => any) | undefined>,
|
||||
): Record<string, any> | undefined => {
|
||||
const compositeFieldRecord = Object.entries(compositeFieldConfig).reduce(
|
||||
(acc, [compositeFieldKey, transform]) => {
|
||||
const value =
|
||||
importedStructuredRow[
|
||||
getCompositeSubFieldKey(field, compositeFieldKey)
|
||||
];
|
||||
importedStructuredRow[getSubFieldOptionKey(field, compositeFieldKey)];
|
||||
|
||||
return isDefined(value)
|
||||
? { ...acc, [compositeFieldKey]: transform?.(value) || value }
|
||||
@ -44,59 +37,9 @@ const buildCompositeFieldRecord = (
|
||||
return isEmptyObject(compositeFieldRecord) ? undefined : compositeFieldRecord;
|
||||
};
|
||||
|
||||
const buildRelationConnectFieldRecord = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
importedStructuredRow: ImportedStructuredRow,
|
||||
spreadsheetImportFields: SpreadsheetImportFields,
|
||||
) => {
|
||||
if (fieldMetadataItem.relation?.type !== RelationType.MANY_TO_ONE)
|
||||
return undefined;
|
||||
|
||||
const relationConnectFields = spreadsheetImportFields.filter(
|
||||
(field) =>
|
||||
field.fieldMetadataItemId === fieldMetadataItem.id &&
|
||||
isDefined(importedStructuredRow[field.key]) &&
|
||||
isNonEmptyString(importedStructuredRow[field.key]),
|
||||
);
|
||||
|
||||
if (relationConnectFields.length === 0) return undefined;
|
||||
|
||||
const relationConnectFieldValue = relationConnectFields.reduce(
|
||||
(acc, field) => {
|
||||
const uniqueFieldMetadataItem = field.uniqueFieldMetadataItem;
|
||||
if (!isDefined(uniqueFieldMetadataItem)) return acc;
|
||||
|
||||
if (
|
||||
isCompositeFieldType(uniqueFieldMetadataItem.type) &&
|
||||
isDefined(field.compositeSubFieldKey)
|
||||
) {
|
||||
return {
|
||||
...acc,
|
||||
[uniqueFieldMetadataItem.name]: {
|
||||
...(isDefined(acc?.[uniqueFieldMetadataItem.name])
|
||||
? acc[uniqueFieldMetadataItem.name]
|
||||
: {}),
|
||||
[field.compositeSubFieldKey]: importedStructuredRow[field.key],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[uniqueFieldMetadataItem.name]: importedStructuredRow[field.key],
|
||||
};
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
);
|
||||
|
||||
return isEmptyObject(relationConnectFieldValue)
|
||||
? undefined
|
||||
: { connect: { where: relationConnectFieldValue } };
|
||||
};
|
||||
|
||||
export const buildRecordFromImportedStructuredRow = ({
|
||||
fieldMetadataItems,
|
||||
fields,
|
||||
importedStructuredRow,
|
||||
spreadsheetImportFields,
|
||||
}: BuildRecordFromImportedStructuredRowArgs) => {
|
||||
const stringArrayJSONSchema = z
|
||||
.preprocess((value) => {
|
||||
@ -202,7 +145,7 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
},
|
||||
};
|
||||
|
||||
for (const field of fieldMetadataItems) {
|
||||
for (const field of fields) {
|
||||
const importedFieldValue = importedStructuredRow[field.name];
|
||||
|
||||
switch (field.type) {
|
||||
@ -235,12 +178,12 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
|
||||
const primaryPhoneNumber =
|
||||
importedStructuredRow[
|
||||
getCompositeSubFieldKey(field, 'primaryPhoneNumber')
|
||||
getSubFieldOptionKey(field, 'primaryPhoneNumber')
|
||||
];
|
||||
|
||||
const primaryPhoneCallingCode =
|
||||
importedStructuredRow[
|
||||
getCompositeSubFieldKey(field, 'primaryPhoneCallingCode')
|
||||
getSubFieldOptionKey(field, 'primaryPhoneCallingCode')
|
||||
];
|
||||
|
||||
const hasUserProvidedPrimaryPhoneNumberWithoutCallingCode =
|
||||
@ -252,7 +195,7 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
if (hasUserProvidedPrimaryPhoneNumberWithoutCallingCode) {
|
||||
const primaryPhoneCountryCode =
|
||||
importedStructuredRow[
|
||||
getCompositeSubFieldKey(field, 'primaryPhoneCountryCode')
|
||||
getSubFieldOptionKey(field, 'primaryPhoneCountryCode')
|
||||
];
|
||||
|
||||
const hasUserProvidedPrimaryPhoneCountryCode =
|
||||
@ -294,14 +237,22 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
case FieldMetadataType.NUMERIC:
|
||||
recordToBuild[field.name] = Number(importedFieldValue);
|
||||
break;
|
||||
case FieldMetadataType.RELATION: {
|
||||
recordToBuild[field.name] = buildRelationConnectFieldRecord(
|
||||
field,
|
||||
importedStructuredRow,
|
||||
spreadsheetImportFields,
|
||||
);
|
||||
case FieldMetadataType.UUID:
|
||||
if (
|
||||
isDefined(importedFieldValue) &&
|
||||
isNonEmptyString(importedFieldValue)
|
||||
) {
|
||||
recordToBuild[field.name] = importedFieldValue;
|
||||
}
|
||||
break;
|
||||
case FieldMetadataType.RELATION:
|
||||
if (
|
||||
isDefined(importedFieldValue) &&
|
||||
isNonEmptyString(importedFieldValue)
|
||||
)
|
||||
recordToBuild[field.name + 'Id'] = importedFieldValue;
|
||||
|
||||
break;
|
||||
}
|
||||
case FieldMetadataType.ACTOR:
|
||||
recordToBuild[field.name] = {
|
||||
source: 'IMPORT',
|
||||
@ -324,30 +275,11 @@ export const buildRecordFromImportedStructuredRow = ({
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FieldMetadataType.UUID:
|
||||
case FieldMetadataType.DATE:
|
||||
case FieldMetadataType.DATE_TIME:
|
||||
if (
|
||||
isDefined(importedFieldValue) &&
|
||||
isNonEmptyString(importedFieldValue)
|
||||
) {
|
||||
recordToBuild[field.name] = importedFieldValue;
|
||||
}
|
||||
break;
|
||||
case FieldMetadataType.SELECT:
|
||||
case FieldMetadataType.RATING:
|
||||
case FieldMetadataType.TEXT:
|
||||
default:
|
||||
if (isDefined(importedFieldValue)) {
|
||||
recordToBuild[field.name] = importedFieldValue;
|
||||
}
|
||||
break;
|
||||
case FieldMetadataType.MORPH_RELATION:
|
||||
case FieldMetadataType.POSITION:
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
case FieldMetadataType.TS_VECTOR:
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(field.type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,18 +2,20 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { COMPOSITE_FIELD_SUB_FIELD_LABELS } from '@/settings/data-model/constants/CompositeFieldSubFieldLabel';
|
||||
|
||||
export const getCompositeSubFieldKey = (
|
||||
export const getSubFieldOptionKey = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
subFieldName: string,
|
||||
) => {
|
||||
if (!isCompositeFieldType(fieldMetadataItem.type)) {
|
||||
throw new Error(
|
||||
`getCompositeSubFieldKey can only be called for composite field types. Received: ${fieldMetadataItem.type}`,
|
||||
`getSubFieldOptionKey can only be called for composite field types. Received: ${fieldMetadataItem.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
const subFieldLabel =
|
||||
COMPOSITE_FIELD_SUB_FIELD_LABELS[fieldMetadataItem.type][subFieldName];
|
||||
|
||||
return `${subFieldLabel} (${fieldMetadataItem.name})`;
|
||||
const subFieldKey = `${subFieldLabel} (${fieldMetadataItem.name})`;
|
||||
|
||||
return subFieldKey;
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getRelationConnectSubFieldKey = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
uniqueConstraintField: FieldMetadataItem,
|
||||
compositeSubFieldKey?: string,
|
||||
) => {
|
||||
return `${isDefined(compositeSubFieldKey) ? `${compositeSubFieldKey}-${uniqueConstraintField.name}` : uniqueConstraintField.name} (${fieldMetadataItem.name})`;
|
||||
};
|
||||
@ -1,8 +0,0 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
|
||||
export const getCompositeSubFieldLabelWithFieldLabel = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
subFieldLabel: string,
|
||||
) => {
|
||||
return `${fieldMetadataItem.label} / ${subFieldLabel}`;
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { COMPOSITE_FIELD_SUB_FIELD_LABELS } from '@/settings/data-model/constants/CompositeFieldSubFieldLabel';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getRelationConnectSubFieldLabel = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
uniqueFieldMetadataItem: FieldMetadataItem,
|
||||
compositeSubFieldKey?: string,
|
||||
) => {
|
||||
const compositeSubFieldLabel =
|
||||
isCompositeFieldType(fieldMetadataItem.type) &&
|
||||
isDefined(compositeSubFieldKey)
|
||||
? COMPOSITE_FIELD_SUB_FIELD_LABELS[fieldMetadataItem.type][
|
||||
compositeSubFieldKey
|
||||
]
|
||||
: undefined;
|
||||
|
||||
return `${fieldMetadataItem.label} / ${uniqueFieldMetadataItem.label}${compositeSubFieldLabel ? ` / ${compositeSubFieldLabel}` : ''}`;
|
||||
};
|
||||
@ -1,7 +1,6 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { getCompositeSubFieldKey } from '@/object-record/spreadsheet-import/utils/spreadsheetImportGetCompositeSubFieldKey';
|
||||
import { getSubFieldOptionKey } from '@/object-record/spreadsheet-import/utils/getSubFieldOptionKey';
|
||||
import { COMPOSITE_FIELD_SUB_FIELD_LABELS } from '@/settings/data-model/constants/CompositeFieldSubFieldLabel';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import {
|
||||
@ -12,7 +11,6 @@ import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
getUniqueConstraintsFields,
|
||||
isDefined,
|
||||
lowercaseUrlOriginAndRemoveTrailingSlash,
|
||||
} from 'twenty-shared/utils';
|
||||
@ -25,14 +23,22 @@ type Column = {
|
||||
export const spreadsheetImportGetUnicityRowHook = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
) => {
|
||||
const uniqueConstraintsFields = getUniqueConstraintsFields<
|
||||
FieldMetadataItem,
|
||||
ObjectMetadataItem
|
||||
>(objectMetadataItem);
|
||||
const uniqueConstraints = objectMetadataItem.indexMetadatas.filter(
|
||||
(indexMetadata) => indexMetadata.isUnique,
|
||||
);
|
||||
|
||||
const uniqueConstraintsWithColumnNames: Column[][] = [
|
||||
[{ columnName: 'id', fieldType: FieldMetadataType.UUID }],
|
||||
...uniqueConstraints.map((indexMetadata) =>
|
||||
indexMetadata.indexFieldMetadatas.flatMap((indexField) => {
|
||||
const field = objectMetadataItem.fields.find(
|
||||
(objectField) => objectField.id === indexField.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!field) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const uniqueConstraintsWithColumnNames: Column[][] =
|
||||
uniqueConstraintsFields.map((uniqueConstraintFields) =>
|
||||
uniqueConstraintFields.flatMap((field) => {
|
||||
if (isCompositeFieldType(field.type)) {
|
||||
const compositeTypeFieldConfig =
|
||||
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[field.type];
|
||||
@ -42,16 +48,18 @@ export const spreadsheetImportGetUnicityRowHook = (
|
||||
);
|
||||
|
||||
return uniqueSubFields.map((subField) => ({
|
||||
columnName: getCompositeSubFieldKey(field, subField.subFieldName),
|
||||
columnName: getSubFieldOptionKey(field, subField.subFieldName),
|
||||
fieldType: field.type,
|
||||
}));
|
||||
}
|
||||
|
||||
return [{ columnName: field.name, fieldType: field.type }];
|
||||
}),
|
||||
);
|
||||
const rowHook: SpreadsheetImportRowHook = (row, addError, table) => {
|
||||
if (uniqueConstraintsFields.length === 0) {
|
||||
),
|
||||
];
|
||||
|
||||
const rowHook: SpreadsheetImportRowHook<string> = (row, addError, table) => {
|
||||
if (uniqueConstraints.length === 0) {
|
||||
return row;
|
||||
}
|
||||
|
||||
@ -87,7 +95,7 @@ export const spreadsheetImportGetUnicityRowHook = (
|
||||
};
|
||||
|
||||
const getUniqueValues = (
|
||||
row: ImportedStructuredRow,
|
||||
row: ImportedStructuredRow<string>,
|
||||
uniqueConstraint: Column[],
|
||||
) => {
|
||||
return uniqueConstraint
|
||||
|
||||
@ -42,10 +42,18 @@ export const sanitizeRecordInput = ({
|
||||
if (
|
||||
isDefined(fieldMetadataItem) &&
|
||||
fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
||||
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE &&
|
||||
!isDefined(recordInput[fieldMetadataItem.name]?.connect?.where)
|
||||
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
return undefined;
|
||||
const relationIdFieldName = `${fieldMetadataItem.name}Id`;
|
||||
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
|
||||
(field) => field.name === relationIdFieldName,
|
||||
);
|
||||
|
||||
const relationIdFieldValue = recordInput[relationIdFieldName];
|
||||
|
||||
return relationIdFieldMetadataItem
|
||||
? [relationIdFieldName, relationIdFieldValue ?? null]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user