Connect - Relation on FE Importer (#13213)
Done : - Relation connect on FE Importer - Remove templating on SpreadsheetMatchedColumn type - Remove useless files on import - Remove AvailableFieldsForImport type + Update SpreadsheetImportField type and SpreadsheetImportFieldOption To test : - Try import opportunities on Apple wk [using this file](https://github.com/user-attachments/files/21233720/Test.import.-.opportunities-sample.csv) closes : https://github.com/twentyhq/core-team-issues/issues/1090
This commit is contained in:
@ -6,6 +6,7 @@ import {
|
||||
GraphQLInputType,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
import { getUniqueConstraintsFields } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
@ -17,7 +18,6 @@ import {
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { getUniqueConstraintsFields } from 'src/engine/metadata-modules/index-metadata/utils/getUniqueConstraintsFields.util';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
export const formatRelationConnectInputTarget = (objectMetadataId: string) =>
|
||||
@ -62,7 +62,10 @@ export class RelationConnectInputTypeDefinitionFactory {
|
||||
private generateRelationWhereInputType(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
): Record<string, GraphQLInputFieldConfig> {
|
||||
const uniqueConstraints = getUniqueConstraintsFields(objectMetadata);
|
||||
const uniqueConstraints = getUniqueConstraintsFields<
|
||||
FieldMetadataInterface,
|
||||
ObjectMetadataInterface
|
||||
>(objectMetadata);
|
||||
|
||||
const fields: Record<
|
||||
string,
|
||||
|
||||
@ -10,14 +10,14 @@ export const fullNameCompositeType: CompositeType = {
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
isIncludedInUniqueConstraint: true,
|
||||
isIncludedInUniqueConstraint: false,
|
||||
},
|
||||
{
|
||||
name: 'lastName',
|
||||
type: FieldMetadataType.TEXT,
|
||||
hidden: false,
|
||||
isRequired: false,
|
||||
isIncludedInUniqueConstraint: true,
|
||||
isIncludedInUniqueConstraint: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { IndexFieldMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface';
|
||||
import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface';
|
||||
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { getUniqueConstraintsFields } from 'src/engine/metadata-modules/index-metadata/utils/getUniqueConstraintsFields.util';
|
||||
|
||||
describe('getUniqueConstraintsFields', () => {
|
||||
const mockIdField: FieldMetadataInterface = {
|
||||
id: 'field-id-1',
|
||||
name: 'id',
|
||||
label: 'ID',
|
||||
type: FieldMetadataType.UUID,
|
||||
objectMetadataId: 'object-id-1',
|
||||
isNullable: false,
|
||||
isUnique: false,
|
||||
isCustom: false,
|
||||
isSystem: true,
|
||||
isActive: true,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-01'),
|
||||
};
|
||||
|
||||
const mockEmailField: FieldMetadataInterface = {
|
||||
id: 'field-id-2',
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
objectMetadataId: 'object-id-1',
|
||||
isNullable: true,
|
||||
isUnique: true,
|
||||
isCustom: false,
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-01'),
|
||||
};
|
||||
|
||||
const mockNameField: FieldMetadataInterface = {
|
||||
id: 'field-id-3',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: 'object-id-1',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
isCustom: false,
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-01'),
|
||||
};
|
||||
|
||||
const createMockIndexFieldMetadata = (
|
||||
fieldMetadataId: string,
|
||||
indexMetadataId: string,
|
||||
order = 0,
|
||||
): IndexFieldMetadataInterface =>
|
||||
({
|
||||
id: `index-field-${fieldMetadataId}-${indexMetadataId}`,
|
||||
indexMetadataId,
|
||||
fieldMetadataId,
|
||||
order,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-01'),
|
||||
}) as IndexFieldMetadataInterface;
|
||||
|
||||
const createMockIndexMetadata = (
|
||||
id: string,
|
||||
name: string,
|
||||
isUnique: boolean,
|
||||
indexFieldMetadatas: IndexFieldMetadataInterface[],
|
||||
): IndexMetadataInterface => ({
|
||||
id,
|
||||
name,
|
||||
isUnique,
|
||||
indexFieldMetadatas,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-01'),
|
||||
indexWhereClause: null,
|
||||
indexType: IndexType.BTREE,
|
||||
});
|
||||
|
||||
const createMockObjectMetadata = (
|
||||
fields: FieldMetadataInterface[],
|
||||
indexMetadatas: IndexMetadataInterface[] = [],
|
||||
): ObjectMetadataInterface => ({
|
||||
id: 'object-id-1',
|
||||
workspaceId: 'workspace-id-1',
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
labelSingular: 'Person',
|
||||
labelPlural: 'People',
|
||||
description: 'A person object',
|
||||
icon: 'IconUser',
|
||||
targetTableName: 'person',
|
||||
fields,
|
||||
indexMetadatas,
|
||||
isSystem: false,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isRemote: false,
|
||||
isAuditLogged: true,
|
||||
isSearchable: true,
|
||||
});
|
||||
|
||||
it('should return the primary key constraint field if no unique indexes are present', () => {
|
||||
const objectMetadata = createMockObjectMetadata([
|
||||
mockIdField,
|
||||
mockNameField,
|
||||
]);
|
||||
|
||||
const result = getUniqueConstraintsFields(objectMetadata);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
expect(result[0][0]).toEqual(mockIdField);
|
||||
});
|
||||
|
||||
it('should return the primary key constraint field and the unique indexes fields if unique indexes are present', () => {
|
||||
const emailIndexFieldMetadata = createMockIndexFieldMetadata(
|
||||
'field-id-2',
|
||||
'index-id-1',
|
||||
);
|
||||
const emailIndex = createMockIndexMetadata(
|
||||
'index-id-1',
|
||||
'unique_email_index',
|
||||
true,
|
||||
[emailIndexFieldMetadata],
|
||||
);
|
||||
|
||||
const objectMetadata = createMockObjectMetadata(
|
||||
[mockIdField, mockEmailField, mockNameField],
|
||||
[emailIndex],
|
||||
);
|
||||
|
||||
const result = getUniqueConstraintsFields(objectMetadata);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
expect(result[0][0]).toEqual(mockIdField);
|
||||
expect(result[1]).toHaveLength(1);
|
||||
expect(result[1][0]).toEqual(mockEmailField);
|
||||
});
|
||||
});
|
||||
@ -1,42 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
export const getUniqueConstraintsFields = (
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
): FieldMetadataInterface[][] => {
|
||||
const uniqueIndexes = objectMetadata.indexMetadatas.filter(
|
||||
(index) => index.isUnique,
|
||||
);
|
||||
|
||||
const fieldsMapById = new Map(
|
||||
objectMetadata.fields.map((field) => [field.id, field]),
|
||||
);
|
||||
|
||||
const primaryKeyConstraintField = objectMetadata.fields.find(
|
||||
(field) => field.name === 'id',
|
||||
);
|
||||
|
||||
if (!isDefined(primaryKeyConstraintField)) {
|
||||
throw new Error(
|
||||
`Primary key constraint field not found for object metadata ${objectMetadata.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
const otherUniqueConstraintsFields = uniqueIndexes.map((index) =>
|
||||
index.indexFieldMetadatas.map((field) => {
|
||||
const indexField = fieldsMapById.get(field.fieldMetadataId);
|
||||
|
||||
if (!isDefined(indexField)) {
|
||||
throw new Error(
|
||||
`Index field not found for field id ${field.fieldMetadataId} in index metadata ${index.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
return indexField;
|
||||
}),
|
||||
);
|
||||
|
||||
return [[primaryKeyConstraintField], ...otherUniqueConstraintsFields];
|
||||
};
|
||||
@ -1558,7 +1558,9 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
const connectFieldName = connectQueryConfig.connectFieldName;
|
||||
|
||||
throw new TwentyORMException(
|
||||
`Expected 1 record to connect to ${connectFieldName}, but found ${recordToConnectTotal}.`,
|
||||
`Expected 1 record to connect to ${connectFieldName}, but found ${recordToConnectTotal} with conditions: ${JSON.stringify(
|
||||
connectQueryConfig.recordToConnectConditionByEntityIndex[index],
|
||||
)}.`,
|
||||
TwentyORMExceptionCode.CONNECT_RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getUniqueConstraintsFields, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { getUniqueConstraintsFields } from 'src/engine/metadata-modules/index-metadata/utils/getUniqueConstraintsFields.util';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { ConnectObject } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-relation-connect.type';
|
||||
@ -218,14 +218,14 @@ const hasRelationConnect = (value: unknown): value is ConnectObject => {
|
||||
return whereKeys.every((key) => {
|
||||
const whereValue = where[key];
|
||||
|
||||
if (typeof whereValue === 'string') {
|
||||
if (typeof whereValue === 'string' || whereValue === null) {
|
||||
return true;
|
||||
}
|
||||
if (whereValue && typeof whereValue === 'object') {
|
||||
const subObj = whereValue as Record<string, unknown>;
|
||||
|
||||
return Object.values(subObj).every(
|
||||
(subValue) => typeof subValue === 'string',
|
||||
(subValue) => typeof subValue === 'string' || subValue === null,
|
||||
);
|
||||
}
|
||||
|
||||
@ -238,7 +238,10 @@ const checkUniqueConstraintFullyPopulated = (
|
||||
connectObject: ConnectObject,
|
||||
connectFieldName: string,
|
||||
) => {
|
||||
const uniqueConstraintsFields = getUniqueConstraintsFields({
|
||||
const uniqueConstraintsFields = getUniqueConstraintsFields<
|
||||
FieldMetadataInterface,
|
||||
ObjectMetadataInterface
|
||||
>({
|
||||
...objectMetadata,
|
||||
fields: Object.values(objectMetadata.fieldsById),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user