Deprecate object metadata maps in favor of flat entities (#16080)
## Context Deprecating the old objectMetadataMap type in favour of split flat entities to match with our new caching. In the long run, trying to achieve: - Better performance through caching - Consistent data access patterns across the codebase - Reduced database queries Now that everything is based on flat entities, which are cached, we can finish the refactoring of workspace context cache which should already improve performances. Then the last step will be to consume that new cache in the new global datasource to get rid of the many workspace datasources stored in the server
This commit is contained in:
+360
-138
@@ -2,147 +2,359 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { computeRelationConnectQueryConfigs } from 'src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util';
|
||||
|
||||
describe('computeRelationConnectQueryConfigs', () => {
|
||||
const personMetadata = {
|
||||
const personFields: FlatFieldMetadata[] = [
|
||||
{
|
||||
id: 'person-id-field-id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
label: 'id',
|
||||
objectMetadataId: 'person-object-metadata-id',
|
||||
isNullable: false,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'person-id-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'person-name-field-id',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
label: 'name',
|
||||
objectMetadataId: 'person-object-metadata-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'person-name-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'person-company-1-field-id',
|
||||
name: 'company-related-to-1',
|
||||
type: FieldMetadataType.RELATION,
|
||||
label: 'company-related-to-1',
|
||||
objectMetadataId: 'person-object-metadata-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'person-company-1-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
relationTargetObjectMetadataId: 'company-object-metadata-id',
|
||||
relationTargetFieldMetadataId: 'company-id-field-id',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: 'company-related-to-1Id',
|
||||
},
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'person-company-2-field-id',
|
||||
name: 'company-related-to-2',
|
||||
type: FieldMetadataType.RELATION,
|
||||
label: 'company-related-to-2',
|
||||
objectMetadataId: 'person-object-metadata-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'person-company-2-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
relationTargetObjectMetadataId: 'company-object-metadata-id',
|
||||
relationTargetFieldMetadataId: 'company-id-field-id',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: 'company-related-to-2Id',
|
||||
},
|
||||
} as unknown as FlatFieldMetadata,
|
||||
];
|
||||
|
||||
const companyFields: FlatFieldMetadata[] = [
|
||||
{
|
||||
id: 'company-id-field-id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
label: 'id',
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
isNullable: false,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'company-id-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'company-name-field-id',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'name',
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'company-name-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'company-description-field-id',
|
||||
name: 'description',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'description',
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'company-description-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'company-domain-name-field-id',
|
||||
name: 'domainName',
|
||||
type: FieldMetadataType.LINKS,
|
||||
label: 'domainName',
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'company-domain-name-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'company-address-field-id',
|
||||
name: 'address',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'address',
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'company-address-field-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
];
|
||||
|
||||
const allFields = [...personFields, ...companyFields];
|
||||
|
||||
const flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata> = {
|
||||
byId: allFields.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.id] = field;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FlatFieldMetadata>,
|
||||
),
|
||||
idByUniversalIdentifier: allFields.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.universalIdentifier] = field.id;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
universalIdentifiersByApplicationId: {},
|
||||
};
|
||||
|
||||
const createFlatObjectMetadata = (
|
||||
partial: Partial<FlatObjectMetadata> & {
|
||||
id: string;
|
||||
nameSingular: string;
|
||||
fieldMetadataIds: string[];
|
||||
indexMetadataIds: string[];
|
||||
},
|
||||
): FlatObjectMetadata =>
|
||||
({
|
||||
namePlural: `${partial.nameSingular}s`,
|
||||
labelSingular: partial.nameSingular,
|
||||
labelPlural: `${partial.nameSingular}s`,
|
||||
icon: 'Icon',
|
||||
targetTableName: partial.nameSingular,
|
||||
workspaceId: 'workspace-id',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isAuditLogged: true,
|
||||
isSearchable: true,
|
||||
universalIdentifier: partial.id,
|
||||
viewIds: [],
|
||||
applicationId: null,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
shortcut: null,
|
||||
description: null,
|
||||
standardOverrides: null,
|
||||
isUIReadOnly: false,
|
||||
standardId: null,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
duplicateCriteria: null,
|
||||
...partial,
|
||||
}) as FlatObjectMetadata;
|
||||
|
||||
const personMetadata = createFlatObjectMetadata({
|
||||
id: 'person-object-metadata-id',
|
||||
nameSingular: 'person',
|
||||
fieldsById: {
|
||||
'person-id-field-id': {
|
||||
id: 'person-id-field-id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
label: 'id',
|
||||
},
|
||||
'person-name-field-id': {
|
||||
id: 'person-name-field-id',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
label: 'name',
|
||||
},
|
||||
'person-company-1-field-id': {
|
||||
id: 'person-company-1-field-id',
|
||||
name: 'company-related-to-1',
|
||||
type: FieldMetadataType.RELATION,
|
||||
label: 'company-related-to-1',
|
||||
relationTargetObjectMetadataId: 'company-object-metadata-id',
|
||||
relationTargetFieldMetadataId: 'company-id-field-id',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
'person-company-2-field-id': {
|
||||
id: 'person-company-2-field-id',
|
||||
name: 'company-related-to-2',
|
||||
type: FieldMetadataType.RELATION,
|
||||
label: 'company-related-to-2',
|
||||
relationTargetObjectMetadataId: 'company-object-metadata-id',
|
||||
relationTargetFieldMetadataId: 'company-id-field-id',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
},
|
||||
fieldIdByName: {
|
||||
id: 'person-id-field-id',
|
||||
name: 'person-name-field-id',
|
||||
'company-related-to-1': 'person-company-1-field-id',
|
||||
'company-related-to-2': 'person-company-2-field-id',
|
||||
},
|
||||
} as unknown as ObjectMetadataItemWithFieldMaps;
|
||||
indexMetadataIds: [],
|
||||
fieldMetadataIds: personFields.map((f) => f.id),
|
||||
});
|
||||
|
||||
const companyMetadata = {
|
||||
const companyMetadata = createFlatObjectMetadata({
|
||||
id: 'company-object-metadata-id',
|
||||
nameSingular: 'company',
|
||||
indexMetadatas: [
|
||||
{
|
||||
id: 'company-id-index-metadata-id',
|
||||
name: 'company-id-index-metadata-name',
|
||||
indexFieldMetadatas: [
|
||||
{
|
||||
fieldMetadataId: 'company-id-field-id',
|
||||
},
|
||||
],
|
||||
isUnique: true,
|
||||
},
|
||||
{
|
||||
id: 'company-domain-index-metadata-id',
|
||||
name: 'company-domain-index-metadata-name',
|
||||
indexFieldMetadatas: [
|
||||
{
|
||||
fieldMetadataId: 'company-domain-name-field-id',
|
||||
},
|
||||
],
|
||||
isUnique: true,
|
||||
},
|
||||
{
|
||||
id: 'company-composite-index-metadata-id',
|
||||
name: 'company-composite-index-metadata-name',
|
||||
indexFieldMetadatas: [
|
||||
{
|
||||
fieldMetadataId: 'company-name-field-id',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 'company-description-field-id',
|
||||
},
|
||||
],
|
||||
isUnique: true,
|
||||
},
|
||||
indexMetadataIds: [
|
||||
'company-id-index-metadata-id',
|
||||
'company-domain-index-metadata-id',
|
||||
'company-composite-index-metadata-id',
|
||||
],
|
||||
fieldsById: {
|
||||
'company-id-field-id': {
|
||||
id: 'company-id-field-id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
label: 'id',
|
||||
},
|
||||
'company-name-field-id': {
|
||||
id: 'company-name-field-id',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'name',
|
||||
},
|
||||
'company-description-field-id': {
|
||||
id: 'company-description-field-id',
|
||||
name: 'description',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'description',
|
||||
},
|
||||
'company-domain-name-field-id': {
|
||||
id: 'company-domain-name-field-id',
|
||||
name: 'domainName',
|
||||
type: FieldMetadataType.LINKS,
|
||||
label: 'domainName',
|
||||
},
|
||||
'company-address-field-id': {
|
||||
id: 'company-address-field-id',
|
||||
name: 'address',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'address',
|
||||
},
|
||||
},
|
||||
fieldIdByName: {
|
||||
id: 'company-id-field-id',
|
||||
name: 'company-name-field-id',
|
||||
description: 'company-description-field-id',
|
||||
domainName: 'company-domain-name-field-id',
|
||||
address: 'company-address-field-id',
|
||||
},
|
||||
} as unknown as ObjectMetadataItemWithFieldMaps;
|
||||
fieldMetadataIds: companyFields.map((f) => f.id),
|
||||
});
|
||||
|
||||
const objectMetadataMaps = {
|
||||
const flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata> = {
|
||||
byId: {
|
||||
'person-object-metadata-id': personMetadata,
|
||||
'company-object-metadata-id': companyMetadata,
|
||||
},
|
||||
idByNameSingular: {
|
||||
person: 'person-object-metadata-id',
|
||||
company: 'company-object-metadata-id',
|
||||
idByUniversalIdentifier: {
|
||||
'person-object-metadata-id': 'person-object-metadata-id',
|
||||
'company-object-metadata-id': 'company-object-metadata-id',
|
||||
},
|
||||
} as ObjectMetadataMaps;
|
||||
universalIdentifiersByApplicationId: {},
|
||||
};
|
||||
|
||||
const createFlatIndexFieldMetadata = (
|
||||
id: string,
|
||||
fieldMetadataId: string,
|
||||
indexMetadataId: string,
|
||||
order: number,
|
||||
) => ({
|
||||
id,
|
||||
fieldMetadataId,
|
||||
indexMetadataId,
|
||||
order,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
const flatIndexMaps: FlatEntityMaps<FlatIndexMetadata> = {
|
||||
byId: {
|
||||
'company-id-index-metadata-id': {
|
||||
id: 'company-id-index-metadata-id',
|
||||
name: 'company-id-index-metadata-name',
|
||||
isUnique: true,
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
universalIdentifier: 'company-id-index-metadata-id',
|
||||
flatIndexFieldMetadatas: [
|
||||
createFlatIndexFieldMetadata(
|
||||
'company-id-index-field-metadata-id',
|
||||
'company-id-field-id',
|
||||
'company-id-index-metadata-id',
|
||||
0,
|
||||
),
|
||||
],
|
||||
} as unknown as FlatIndexMetadata,
|
||||
'company-domain-index-metadata-id': {
|
||||
id: 'company-domain-index-metadata-id',
|
||||
name: 'company-domain-index-metadata-name',
|
||||
isUnique: true,
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
universalIdentifier: 'company-domain-index-metadata-id',
|
||||
flatIndexFieldMetadatas: [
|
||||
createFlatIndexFieldMetadata(
|
||||
'company-domain-index-field-metadata-id',
|
||||
'company-domain-name-field-id',
|
||||
'company-domain-index-metadata-id',
|
||||
0,
|
||||
),
|
||||
],
|
||||
} as unknown as FlatIndexMetadata,
|
||||
'company-composite-index-metadata-id': {
|
||||
id: 'company-composite-index-metadata-id',
|
||||
name: 'company-composite-index-metadata-name',
|
||||
isUnique: true,
|
||||
objectMetadataId: 'company-object-metadata-id',
|
||||
universalIdentifier: 'company-composite-index-metadata-id',
|
||||
flatIndexFieldMetadatas: [
|
||||
createFlatIndexFieldMetadata(
|
||||
'company-name-index-field-metadata-id',
|
||||
'company-name-field-id',
|
||||
'company-composite-index-metadata-id',
|
||||
0,
|
||||
),
|
||||
createFlatIndexFieldMetadata(
|
||||
'company-description-index-field-metadata-id',
|
||||
'company-description-field-id',
|
||||
'company-composite-index-metadata-id',
|
||||
1,
|
||||
),
|
||||
],
|
||||
} as unknown as FlatIndexMetadata,
|
||||
},
|
||||
idByUniversalIdentifier: {
|
||||
'company-id-index-metadata-id': 'company-id-index-metadata-id',
|
||||
'company-domain-index-metadata-id': 'company-domain-index-metadata-id',
|
||||
'company-composite-index-metadata-id':
|
||||
'company-composite-index-metadata-id',
|
||||
},
|
||||
universalIdentifiersByApplicationId: {},
|
||||
};
|
||||
|
||||
it('should return an empty object if no connect fields are found', () => {
|
||||
const peopleEntityInputs = [
|
||||
@@ -159,7 +371,9 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
const result = computeRelationConnectQueryConfigs(
|
||||
peopleEntityInputs,
|
||||
personMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
{},
|
||||
);
|
||||
|
||||
@@ -187,7 +401,9 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
computeRelationConnectQueryConfigs(
|
||||
peopleEntityInputs,
|
||||
personMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
relationConnectQueryFieldsByEntityIndex,
|
||||
);
|
||||
}).toThrow('Connect is not allowed for name on person');
|
||||
@@ -213,7 +429,9 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
computeRelationConnectQueryConfigs(
|
||||
peopleEntityInputs,
|
||||
personMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
relationConnectQueryFieldsByEntityIndex,
|
||||
);
|
||||
}).toThrow(
|
||||
@@ -255,7 +473,9 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
computeRelationConnectQueryConfigs(
|
||||
peopleEntityInputs,
|
||||
personMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
relationConnectQueryFieldsByEntityIndex,
|
||||
);
|
||||
}).toThrow(
|
||||
@@ -304,7 +524,9 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
computeRelationConnectQueryConfigs(
|
||||
peopleEntityInputs,
|
||||
personMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
relationConnectQueryFieldsByEntityIndex,
|
||||
);
|
||||
}).toThrow(
|
||||
@@ -374,7 +596,9 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
const result = computeRelationConnectQueryConfigs(
|
||||
peopleEntityInputs,
|
||||
personMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
relationConnectQueryFieldsByEntityIndex,
|
||||
);
|
||||
|
||||
@@ -392,12 +616,11 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
relationFieldName: 'company-related-to-1Id',
|
||||
targetObjectName: 'company',
|
||||
uniqueConstraintFields: [
|
||||
{
|
||||
expect.objectContaining({
|
||||
id: 'company-domain-name-field-id',
|
||||
label: 'domainName',
|
||||
name: 'domainName',
|
||||
type: FieldMetadataType.LINKS,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -410,12 +633,11 @@ describe('computeRelationConnectQueryConfigs', () => {
|
||||
relationFieldName: 'company-related-to-2Id',
|
||||
targetObjectName: 'company',
|
||||
uniqueConstraintFields: [
|
||||
{
|
||||
expect.objectContaining({
|
||||
id: 'company-id-field-id',
|
||||
label: 'id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
+73
-33
@@ -2,43 +2,80 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { type ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { formatTwentyOrmEventToDatabaseBatchEvent } from 'src/engine/twenty-orm/utils/format-twenty-orm-event-to-database-batch-event.util';
|
||||
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
|
||||
import { getMockObjectMetadataItemWithFieldsMaps } from 'src/utils/__test__/get-object-metadata-item-with-fields-maps.mock';
|
||||
|
||||
describe('formatTwentyOrmEventToDatabaseBatchEvent', () => {
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
getMockObjectMetadataItemWithFieldsMaps({
|
||||
id: 'object-id',
|
||||
workspaceId: 'workspace-id',
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
indexMetadatas: [],
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldIdByName: {
|
||||
name: 'name-id',
|
||||
age: 'age-id',
|
||||
fullName: 'fullname-id',
|
||||
},
|
||||
fieldsById: {
|
||||
'name-id': getMockFieldMetadataEntity({
|
||||
workspaceId: 'workspace-id',
|
||||
objectMetadataId: 'object-id',
|
||||
id: 'name-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
isLabelSyncedWithName: true,
|
||||
isNullable: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
},
|
||||
});
|
||||
const workspaceId = 'workspace-id';
|
||||
const objectMetadataId = 'object-id';
|
||||
|
||||
const createMockField = (
|
||||
overrides: Partial<FlatFieldMetadata> & {
|
||||
id: string;
|
||||
name: string;
|
||||
type: FieldMetadataType;
|
||||
},
|
||||
): FlatFieldMetadata =>
|
||||
({
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: overrides.id,
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
label: overrides.name,
|
||||
...overrides,
|
||||
}) as FlatFieldMetadata;
|
||||
|
||||
const nameField = createMockField({
|
||||
id: 'name-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
});
|
||||
|
||||
const flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata> = {
|
||||
byId: {
|
||||
'name-id': nameField,
|
||||
},
|
||||
idByUniversalIdentifier: {
|
||||
'name-id': 'name-id',
|
||||
},
|
||||
universalIdentifiersByApplicationId: {},
|
||||
};
|
||||
|
||||
const flatObjectMetadata: FlatObjectMetadata = {
|
||||
id: objectMetadataId,
|
||||
workspaceId,
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
labelSingular: 'Person',
|
||||
labelPlural: 'People',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: objectMetadataId,
|
||||
fieldMetadataIds: ['name-id'],
|
||||
indexMetadataIds: [],
|
||||
viewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatObjectMetadata;
|
||||
|
||||
const mockWorkspaceId = 'workspace-id';
|
||||
const mockAuthContext = {
|
||||
@@ -73,7 +110,8 @@ describe('formatTwentyOrmEventToDatabaseBatchEvent', () => {
|
||||
try {
|
||||
formatTwentyOrmEventToDatabaseBatchEvent({
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataItem: flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
workspaceId: mockWorkspaceId,
|
||||
authContext: mockAuthContext,
|
||||
entities: afterEntities,
|
||||
@@ -115,7 +153,8 @@ describe('formatTwentyOrmEventToDatabaseBatchEvent', () => {
|
||||
|
||||
const result = formatTwentyOrmEventToDatabaseBatchEvent({
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataItem: flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
workspaceId: mockWorkspaceId,
|
||||
authContext: mockAuthContext,
|
||||
entities: afterEntities,
|
||||
@@ -155,7 +194,8 @@ describe('formatTwentyOrmEventToDatabaseBatchEvent', () => {
|
||||
|
||||
const result = formatTwentyOrmEventToDatabaseBatchEvent({
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataItem: flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
workspaceId: mockWorkspaceId,
|
||||
authContext: mockAuthContext,
|
||||
entities: afterEntity,
|
||||
|
||||
+145
-122
@@ -1,19 +1,20 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { getColumnNameToFieldMetadataIdMap } from 'src/engine/twenty-orm/utils/get-column-name-to-field-metadata-id.util';
|
||||
|
||||
describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
const createMockObjectMetadataItemWithFieldMaps = (
|
||||
fieldsById: Record<string, any>,
|
||||
): ObjectMetadataItemWithFieldMaps =>
|
||||
const createMockFlatObjectMetadata = (
|
||||
fieldMetadataIds: string[],
|
||||
): FlatObjectMetadata =>
|
||||
({
|
||||
id: 'test-object-id',
|
||||
nameSingular: 'test',
|
||||
namePlural: 'tests',
|
||||
labelSingular: 'Test',
|
||||
labelPlural: 'Tests',
|
||||
description: 'Test object',
|
||||
icon: 'IconTest',
|
||||
targetTableName: 'test',
|
||||
isCustom: false,
|
||||
@@ -22,53 +23,87 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
isSystem: false,
|
||||
isAuditLogged: false,
|
||||
isSearchable: false,
|
||||
labelIdentifierFieldMetadataId: '',
|
||||
imageIdentifierFieldMetadataId: '',
|
||||
workspaceId: 'test-workspace-id',
|
||||
indexMetadatas: [],
|
||||
fieldsById,
|
||||
fieldIdByName: {},
|
||||
fieldIdByJoinColumnName: {},
|
||||
}) as unknown as ObjectMetadataItemWithFieldMaps;
|
||||
universalIdentifier: 'test-object-id',
|
||||
indexMetadataIds: [],
|
||||
fieldMetadataIds,
|
||||
viewIds: [],
|
||||
applicationId: null,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
shortcut: null,
|
||||
description: null,
|
||||
standardOverrides: null,
|
||||
isUIReadOnly: false,
|
||||
standardId: null,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
duplicateCriteria: null,
|
||||
}) as FlatObjectMetadata;
|
||||
|
||||
const createMockFieldMetadata = (
|
||||
const createMockFlatFieldMetadata = (
|
||||
id: string,
|
||||
name: string,
|
||||
type: FieldMetadataType,
|
||||
) => ({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
label: name,
|
||||
objectMetadataId: 'test-object-id',
|
||||
isLabelSyncedWithName: true,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
workspaceId: 'test-workspace-id',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
settings?: Record<string, unknown>,
|
||||
): FlatFieldMetadata =>
|
||||
({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
label: name,
|
||||
objectMetadataId: 'test-object-id',
|
||||
isLabelSyncedWithName: true,
|
||||
isNullable: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: id,
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
...(settings ? { settings } : {}),
|
||||
}) as unknown as FlatFieldMetadata;
|
||||
|
||||
const buildFlatFieldMetadataMaps = (
|
||||
fields: FlatFieldMetadata[],
|
||||
): FlatEntityMaps<FlatFieldMetadata> => ({
|
||||
byId: fields.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.id] = field;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FlatFieldMetadata>,
|
||||
),
|
||||
idByUniversalIdentifier: fields.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.universalIdentifier] = field.id;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
universalIdentifiersByApplicationId: {},
|
||||
});
|
||||
|
||||
describe('with simple field types', () => {
|
||||
it('should return a map with column name to field metadata id for simple field types', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
'field-1',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
'field-2': createMockFieldMetadata(
|
||||
'field-2',
|
||||
'age',
|
||||
FieldMetadataType.NUMBER,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata('field-1', 'name', FieldMetadataType.TEXT),
|
||||
createMockFlatFieldMetadata('field-2', 'age', FieldMetadataType.NUMBER),
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result['name']).toBe('field-1');
|
||||
@@ -79,19 +114,21 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
|
||||
describe('with composite field types', () => {
|
||||
it('should return column names to field metadata id for FULL_NAME composite type', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'fullName',
|
||||
FieldMetadataType.FULL_NAME,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result['fullNameFirstName']).toBe('field-1');
|
||||
@@ -100,19 +137,21 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
});
|
||||
|
||||
it('should return column names to field metadata id for CURRENCY composite type', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'price',
|
||||
FieldMetadataType.CURRENCY,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result['priceAmountMicros']).toBe('field-1');
|
||||
@@ -121,29 +160,27 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
});
|
||||
|
||||
it('should handle multiple composite fields', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'fullName',
|
||||
FieldMetadataType.FULL_NAME,
|
||||
),
|
||||
'field-2': createMockFieldMetadata(
|
||||
createMockFlatFieldMetadata(
|
||||
'field-2',
|
||||
'price',
|
||||
FieldMetadataType.CURRENCY,
|
||||
),
|
||||
'field-3': createMockFieldMetadata(
|
||||
'field-3',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
createMockFlatFieldMetadata('field-3', 'name', FieldMetadataType.TEXT),
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result['fullNameFirstName']).toBe('field-1');
|
||||
@@ -157,34 +194,28 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
|
||||
describe('with mixed field types', () => {
|
||||
it('should handle both simple and composite field types', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
'field-1',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
'field-2': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata('field-1', 'name', FieldMetadataType.TEXT),
|
||||
createMockFlatFieldMetadata(
|
||||
'field-2',
|
||||
'fullName',
|
||||
FieldMetadataType.FULL_NAME,
|
||||
),
|
||||
'field-3': createMockFieldMetadata(
|
||||
'field-3',
|
||||
'age',
|
||||
FieldMetadataType.NUMBER,
|
||||
),
|
||||
'field-4': createMockFieldMetadata(
|
||||
createMockFlatFieldMetadata('field-3', 'age', FieldMetadataType.NUMBER),
|
||||
createMockFlatFieldMetadata(
|
||||
'field-4',
|
||||
'price',
|
||||
FieldMetadataType.CURRENCY,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result['name']).toBe('field-1');
|
||||
@@ -199,30 +230,26 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
|
||||
describe('with relation field types', () => {
|
||||
it('should handle relation field types with join column name', () => {
|
||||
const fieldsById = {
|
||||
'field-1': {
|
||||
...createMockFieldMetadata(
|
||||
'field-1',
|
||||
'company',
|
||||
FieldMetadataType.RELATION,
|
||||
),
|
||||
settings: {
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'company',
|
||||
FieldMetadataType.RELATION,
|
||||
{
|
||||
relationType: 'ONE_TO_ONE',
|
||||
joinColumnName: 'companyId',
|
||||
},
|
||||
},
|
||||
'field-2': createMockFieldMetadata(
|
||||
'field-2',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
createMockFlatFieldMetadata('field-2', 'name', FieldMetadataType.TEXT),
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result['companyId']).toBe('field-1');
|
||||
@@ -232,30 +259,26 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
});
|
||||
|
||||
it('should skip ONE_TO_MANY relation field types', () => {
|
||||
const fieldsById = {
|
||||
'field-1': {
|
||||
...createMockFieldMetadata(
|
||||
'field-1',
|
||||
'employees',
|
||||
FieldMetadataType.RELATION,
|
||||
),
|
||||
settings: {
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'employees',
|
||||
FieldMetadataType.RELATION,
|
||||
{
|
||||
relationType: 'ONE_TO_MANY',
|
||||
joinColumnName: 'companyId',
|
||||
},
|
||||
},
|
||||
'field-2': createMockFieldMetadata(
|
||||
'field-2',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
createMockFlatFieldMetadata('field-2', 'name', FieldMetadataType.TEXT),
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result['name']).toBe('field-2');
|
||||
|
||||
+115
-86
@@ -1,19 +1,20 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { getFieldMetadataIdToColumnNamesMap } from 'src/engine/twenty-orm/utils/get-field-metadata-id-to-column-names-map.util';
|
||||
|
||||
describe('getFieldMetadataIdToColumnNamesMap', () => {
|
||||
const createMockObjectMetadataItemWithFieldMaps = (
|
||||
fieldsById: Record<string, any>,
|
||||
): ObjectMetadataItemWithFieldMaps =>
|
||||
const createMockFlatObjectMetadata = (
|
||||
fieldMetadataIds: string[],
|
||||
): FlatObjectMetadata =>
|
||||
({
|
||||
id: 'test-object-id',
|
||||
nameSingular: 'test',
|
||||
namePlural: 'tests',
|
||||
labelSingular: 'Test',
|
||||
labelPlural: 'Tests',
|
||||
description: 'Test object',
|
||||
icon: 'IconTest',
|
||||
targetTableName: 'test',
|
||||
isCustom: false,
|
||||
@@ -22,53 +23,85 @@ describe('getFieldMetadataIdToColumnNamesMap', () => {
|
||||
isSystem: false,
|
||||
isAuditLogged: false,
|
||||
isSearchable: false,
|
||||
labelIdentifierFieldMetadataId: '',
|
||||
imageIdentifierFieldMetadataId: '',
|
||||
workspaceId: 'test-workspace-id',
|
||||
indexMetadatas: [],
|
||||
fieldsById,
|
||||
fieldIdByName: {},
|
||||
fieldIdByJoinColumnName: {},
|
||||
}) as unknown as ObjectMetadataItemWithFieldMaps;
|
||||
universalIdentifier: 'test-object-id',
|
||||
indexMetadataIds: [],
|
||||
fieldMetadataIds,
|
||||
viewIds: [],
|
||||
applicationId: null,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
shortcut: null,
|
||||
description: null,
|
||||
standardOverrides: null,
|
||||
isUIReadOnly: false,
|
||||
standardId: null,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
duplicateCriteria: null,
|
||||
}) as FlatObjectMetadata;
|
||||
|
||||
const createMockFieldMetadata = (
|
||||
const createMockFlatFieldMetadata = (
|
||||
id: string,
|
||||
name: string,
|
||||
type: FieldMetadataType,
|
||||
) => ({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
label: name,
|
||||
objectMetadataId: 'test-object-id',
|
||||
isLabelSyncedWithName: true,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
workspaceId: 'test-workspace-id',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
): FlatFieldMetadata =>
|
||||
({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
label: name,
|
||||
objectMetadataId: 'test-object-id',
|
||||
isLabelSyncedWithName: true,
|
||||
isNullable: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: id,
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
}) as unknown as FlatFieldMetadata;
|
||||
|
||||
const buildFlatFieldMetadataMaps = (
|
||||
fields: FlatFieldMetadata[],
|
||||
): FlatEntityMaps<FlatFieldMetadata> => ({
|
||||
byId: fields.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.id] = field;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FlatFieldMetadata>,
|
||||
),
|
||||
idByUniversalIdentifier: fields.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.universalIdentifier] = field.id;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
universalIdentifiersByApplicationId: {},
|
||||
});
|
||||
|
||||
describe('with simple field types', () => {
|
||||
it('should return a map with single column name for simple field types', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
'field-1',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
'field-2': createMockFieldMetadata(
|
||||
'field-2',
|
||||
'age',
|
||||
FieldMetadataType.NUMBER,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata('field-1', 'name', FieldMetadataType.TEXT),
|
||||
createMockFlatFieldMetadata('field-2', 'age', FieldMetadataType.NUMBER),
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getFieldMetadataIdToColumnNamesMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result.get('field-1')).toEqual(['name']);
|
||||
@@ -79,19 +112,21 @@ describe('getFieldMetadataIdToColumnNamesMap', () => {
|
||||
|
||||
describe('with composite field types', () => {
|
||||
it('should return multiple column names for FULL_NAME composite type', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'fullName',
|
||||
FieldMetadataType.FULL_NAME,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getFieldMetadataIdToColumnNamesMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result.get('field-1')).toEqual([
|
||||
@@ -102,19 +137,21 @@ describe('getFieldMetadataIdToColumnNamesMap', () => {
|
||||
});
|
||||
|
||||
it('should return multiple column names for CURRENCY composite type', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'price',
|
||||
FieldMetadataType.CURRENCY,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getFieldMetadataIdToColumnNamesMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result.get('field-1')).toEqual([
|
||||
@@ -125,29 +162,27 @@ describe('getFieldMetadataIdToColumnNamesMap', () => {
|
||||
});
|
||||
|
||||
it('should handle multiple composite fields', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata(
|
||||
'field-1',
|
||||
'fullName',
|
||||
FieldMetadataType.FULL_NAME,
|
||||
),
|
||||
'field-2': createMockFieldMetadata(
|
||||
createMockFlatFieldMetadata(
|
||||
'field-2',
|
||||
'price',
|
||||
FieldMetadataType.CURRENCY,
|
||||
),
|
||||
'field-3': createMockFieldMetadata(
|
||||
'field-3',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
createMockFlatFieldMetadata('field-3', 'name', FieldMetadataType.TEXT),
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getFieldMetadataIdToColumnNamesMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result.get('field-1')).toEqual([
|
||||
@@ -165,34 +200,28 @@ describe('getFieldMetadataIdToColumnNamesMap', () => {
|
||||
|
||||
describe('with mixed field types', () => {
|
||||
it('should handle both simple and composite field types', () => {
|
||||
const fieldsById = {
|
||||
'field-1': createMockFieldMetadata(
|
||||
'field-1',
|
||||
'name',
|
||||
FieldMetadataType.TEXT,
|
||||
),
|
||||
'field-2': createMockFieldMetadata(
|
||||
const fields = [
|
||||
createMockFlatFieldMetadata('field-1', 'name', FieldMetadataType.TEXT),
|
||||
createMockFlatFieldMetadata(
|
||||
'field-2',
|
||||
'fullName',
|
||||
FieldMetadataType.FULL_NAME,
|
||||
),
|
||||
'field-3': createMockFieldMetadata(
|
||||
'field-3',
|
||||
'age',
|
||||
FieldMetadataType.NUMBER,
|
||||
),
|
||||
'field-4': createMockFieldMetadata(
|
||||
createMockFlatFieldMetadata('field-3', 'age', FieldMetadataType.NUMBER),
|
||||
createMockFlatFieldMetadata(
|
||||
'field-4',
|
||||
'price',
|
||||
FieldMetadataType.CURRENCY,
|
||||
),
|
||||
};
|
||||
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
createMockObjectMetadataItemWithFieldMaps(fieldsById);
|
||||
];
|
||||
const flatObjectMetadata = createMockFlatObjectMetadata(
|
||||
fields.map((f) => f.id),
|
||||
);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps(fields);
|
||||
|
||||
const result = getFieldMetadataIdToColumnNamesMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
expect(result.get('field-1')).toEqual(['name']);
|
||||
|
||||
+64
-24
@@ -5,11 +5,13 @@ import { getUniqueConstraintsFields, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
|
||||
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type ConnectObject } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-nested-relation-fields.type';
|
||||
import {
|
||||
type RelationConnectQueryConfig,
|
||||
@@ -26,8 +28,10 @@ import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-
|
||||
|
||||
export const computeRelationConnectQueryConfigs = (
|
||||
entities: Record<string, unknown>[],
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMap: ObjectMetadataMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
flatIndexMaps: FlatEntityMaps<FlatIndexMetadata>,
|
||||
relationConnectQueryFieldsByEntityIndex: RelationConnectQueryFieldsByEntityIndex,
|
||||
) => {
|
||||
const allConnectQueryConfigs: Record<string, RelationConnectQueryConfig> = {};
|
||||
@@ -48,8 +52,10 @@ export const computeRelationConnectQueryConfigs = (
|
||||
} = computeRecordToConnectCondition(
|
||||
connectFieldName,
|
||||
connectObject,
|
||||
objectMetadata,
|
||||
objectMetadataMap,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
entity,
|
||||
);
|
||||
|
||||
@@ -102,7 +108,7 @@ const updateConnectQueryConfigs = (
|
||||
const createConnectQueryConfig = (
|
||||
connectFieldName: string,
|
||||
recordToConnectCondition: UniqueConstraintCondition,
|
||||
uniqueConstraintFields: FieldMetadataEntity<FieldMetadataType>[],
|
||||
uniqueConstraintFields: FlatFieldMetadata<FieldMetadataType>[],
|
||||
targetObjectNameSingular: string,
|
||||
entityIndex: number,
|
||||
) => {
|
||||
@@ -121,26 +127,33 @@ const createConnectQueryConfig = (
|
||||
const computeRecordToConnectCondition = (
|
||||
connectFieldName: string,
|
||||
connectObject: ConnectObject,
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMap: ObjectMetadataMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
flatIndexMaps: FlatEntityMaps<FlatIndexMetadata>,
|
||||
entity: Record<string, unknown>,
|
||||
): {
|
||||
recordToConnectCondition: UniqueConstraintCondition;
|
||||
uniqueConstraintFields: FieldMetadataEntity<FieldMetadataType>[];
|
||||
uniqueConstraintFields: FlatFieldMetadata<FieldMetadataType>[];
|
||||
targetObjectNameSingular: string;
|
||||
} => {
|
||||
const field =
|
||||
objectMetadata.fieldsById[objectMetadata.fieldIdByName[connectFieldName]];
|
||||
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
const field = flatFieldMetadataMaps.byId[fieldIdByName[connectFieldName]];
|
||||
|
||||
if (
|
||||
!isDefined(field) ||
|
||||
(!isFieldMetadataEntityOfType(field, FieldMetadataType.RELATION) &&
|
||||
!isFieldMetadataEntityOfType(field, FieldMetadataType.MORPH_RELATION)) ||
|
||||
field.settings?.relationType !== RelationType.MANY_TO_ONE
|
||||
) {
|
||||
const objectMetadataNameSingular = objectMetadata.nameSingular;
|
||||
const objectMetadataNameSingular = flatObjectMetadata.nameSingular;
|
||||
|
||||
throw new TwentyORMException(
|
||||
`Connect is not allowed for ${connectFieldName} on ${objectMetadata.nameSingular}`,
|
||||
`Connect is not allowed for ${connectFieldName} on ${flatObjectMetadata.nameSingular}`,
|
||||
TwentyORMExceptionCode.CONNECT_NOT_ALLOWED,
|
||||
{
|
||||
userFriendlyMessage: msg`Connect is not allowed for ${connectFieldName} on ${objectMetadataNameSingular}`,
|
||||
@@ -150,7 +163,7 @@ const computeRecordToConnectCondition = (
|
||||
checkNoRelationFieldConflictOrThrow(entity, connectFieldName);
|
||||
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMap.byId[field.relationTargetObjectMetadataId || ''];
|
||||
flatObjectMetadataMaps.byId[field.relationTargetObjectMetadataId || ''];
|
||||
|
||||
if (!isDefined(targetObjectMetadata)) {
|
||||
throw new TwentyORMException(
|
||||
@@ -164,6 +177,8 @@ const computeRecordToConnectCondition = (
|
||||
|
||||
const uniqueConstraintFields = checkUniqueConstraintFullyPopulated(
|
||||
targetObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
connectObject,
|
||||
connectFieldName,
|
||||
);
|
||||
@@ -179,16 +194,41 @@ const computeRecordToConnectCondition = (
|
||||
};
|
||||
|
||||
const checkUniqueConstraintFullyPopulated = (
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
flatIndexMaps: FlatEntityMaps<FlatIndexMetadata>,
|
||||
connectObject: ConnectObject,
|
||||
connectFieldName: string,
|
||||
) => {
|
||||
const fields = getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const indexMetadatas = flatObjectMetadata.indexMetadataIds
|
||||
.map((indexId) => flatIndexMaps.byId[indexId])
|
||||
.filter(isDefined)
|
||||
.map((index) => ({
|
||||
id: index.id,
|
||||
isUnique: index.isUnique,
|
||||
indexFieldMetadatas: index.flatIndexFieldMetadatas.map(
|
||||
(fieldMetadata) => ({
|
||||
fieldMetadataId: fieldMetadata.fieldMetadataId,
|
||||
}),
|
||||
),
|
||||
}));
|
||||
|
||||
const uniqueConstraintsFields = getUniqueConstraintsFields<
|
||||
FieldMetadataEntity,
|
||||
ObjectMetadataEntity
|
||||
FlatFieldMetadata,
|
||||
{
|
||||
id: string;
|
||||
indexMetadatas: typeof indexMetadatas;
|
||||
fields: FlatFieldMetadata[];
|
||||
}
|
||||
>({
|
||||
...objectMetadata,
|
||||
fields: Object.values(objectMetadata.fieldsById),
|
||||
id: flatObjectMetadata.id,
|
||||
indexMetadatas,
|
||||
fields,
|
||||
});
|
||||
|
||||
const hasUniqueConstraintFieldFullyPopulated = uniqueConstraintsFields.some(
|
||||
@@ -243,7 +283,7 @@ const checkNoRelationFieldConflictOrThrow = (
|
||||
};
|
||||
|
||||
const computeUniqueConstraintCondition = (
|
||||
uniqueConstraintFields: FieldMetadataEntity<FieldMetadataType>[],
|
||||
uniqueConstraintFields: FlatFieldMetadata<FieldMetadataType>[],
|
||||
connectObject: ConnectObject,
|
||||
): UniqueConstraintCondition => {
|
||||
return uniqueConstraintFields.reduce((acc, uniqueConstraintField) => {
|
||||
@@ -271,7 +311,7 @@ const computeUniqueConstraintCondition = (
|
||||
|
||||
const checkUniqueConstraintsAreSameOrThrow = (
|
||||
relationConnectQueryConfig: RelationConnectQueryConfig,
|
||||
uniqueConstraintFields: FieldMetadataEntity<FieldMetadataType>[],
|
||||
uniqueConstraintFields: FlatFieldMetadata<FieldMetadataType>[],
|
||||
) => {
|
||||
if (
|
||||
!deepEqual(
|
||||
|
||||
+11
-11
@@ -1,8 +1,9 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { type RelationType } from 'typeorm/metadata/types/RelationTypes';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
RelationException,
|
||||
RelationExceptionCode,
|
||||
@@ -16,12 +17,13 @@ interface RelationDetails {
|
||||
joinColumn: { name: string } | undefined;
|
||||
}
|
||||
|
||||
export async function determineSchemaRelationDetails(
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
export function determineSchemaRelationDetails(
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
): Promise<RelationDetails> {
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
): RelationDetails {
|
||||
if (!fieldMetadata.settings) {
|
||||
throw new Error('Field metadata settings are missing');
|
||||
}
|
||||
@@ -38,9 +40,9 @@ export async function determineSchemaRelationDetails(
|
||||
}
|
||||
|
||||
const sourceObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.objectMetadataId];
|
||||
flatObjectMetadataMaps.byId[fieldMetadata.objectMetadataId];
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
flatObjectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
|
||||
if (!sourceObjectMetadata || !targetObjectMetadata) {
|
||||
throw new RelationException(
|
||||
@@ -57,9 +59,7 @@ export async function determineSchemaRelationDetails(
|
||||
}
|
||||
|
||||
const targetFieldMetadata =
|
||||
targetObjectMetadata.fieldsById[
|
||||
fieldMetadata.relationTargetFieldMetadataId
|
||||
];
|
||||
flatFieldMetadataMaps.byId[fieldMetadata.relationTargetFieldMetadataId];
|
||||
|
||||
if (!targetFieldMetadata) {
|
||||
throw new Error('Target field metadata not found');
|
||||
|
||||
@@ -4,14 +4,17 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
|
||||
export function formatData<T>(
|
||||
data: T,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
): T {
|
||||
if (!data) {
|
||||
return data;
|
||||
@@ -19,24 +22,27 @@ export function formatData<T>(
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) =>
|
||||
formatData(item, objectMetadataItemWithFieldMaps),
|
||||
formatData(item, flatObjectMetadata, flatFieldMetadataMaps),
|
||||
) as T;
|
||||
}
|
||||
|
||||
const { fieldIdByName, fieldIdByJoinColumnName } =
|
||||
buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const newData: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const fieldMetadataId =
|
||||
objectMetadataItemWithFieldMaps.fieldIdByName[key] ||
|
||||
objectMetadataItemWithFieldMaps.fieldIdByJoinColumnName[key];
|
||||
const fieldMetadataId = fieldIdByName[key] || fieldIdByJoinColumnName[key];
|
||||
|
||||
const fieldMetadata =
|
||||
objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||
const fieldMetadata = flatFieldMetadataMaps.byId[fieldMetadataId];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new Error(
|
||||
`Field metadata for field "${key}" is missing in object metadata ${objectMetadataItemWithFieldMaps.nameSingular}`,
|
||||
`Field metadata for field "${key}" is missing in object metadata ${flatObjectMetadata.nameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +64,7 @@ export function formatData<T>(
|
||||
export function formatCompositeField(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Record<string, any> {
|
||||
const compositeType = compositeTypeDefinitions.get(
|
||||
@@ -81,7 +87,7 @@ export function formatCompositeField(
|
||||
if (value && value[subFieldKey] !== undefined) {
|
||||
formattedCompositeField[fullFieldName] = formatFieldMetadataValue(
|
||||
value[subFieldKey],
|
||||
property as unknown as FieldMetadataEntity,
|
||||
property as unknown as FlatFieldMetadata,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +98,7 @@ export function formatCompositeField(
|
||||
function formatFieldMetadataValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
) {
|
||||
if (
|
||||
fieldMetadata.type === FieldMetadataType.RAW_JSON &&
|
||||
|
||||
@@ -12,18 +12,21 @@ import {
|
||||
DEFAULT_COMPOSITE_FIELDS_NULL_EQUIVALENT_VALUE,
|
||||
DEFAULT_TEXT_FIELD_NULL_EQUIVALENT_VALUE,
|
||||
} from 'src/engine/api/common/common-args-processors/data-arg-processor/constants/null-equivalent-values.constant';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
|
||||
export function formatResult<T>(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: any,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps | undefined,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata | undefined,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
): T {
|
||||
if (!isDefined(data)) {
|
||||
return data;
|
||||
@@ -31,7 +34,12 @@ export function formatResult<T>(
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) =>
|
||||
formatResult(item, objectMetadataItemWithFieldMaps, objectMetadataMaps),
|
||||
formatResult(
|
||||
item,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
),
|
||||
) as T;
|
||||
}
|
||||
|
||||
@@ -39,12 +47,18 @@ export function formatResult<T>(
|
||||
return data;
|
||||
}
|
||||
|
||||
if (!objectMetadataItemWithFieldMaps) {
|
||||
if (!flatObjectMetadata) {
|
||||
throw new Error('Object metadata is missing');
|
||||
}
|
||||
|
||||
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
const compositeFieldMetadataMap = getCompositeFieldMetadataMap(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const newData: object = {};
|
||||
@@ -53,14 +67,12 @@ export function formatResult<T>(
|
||||
const compositePropertyArgs = compositeFieldMetadataMap.get(key);
|
||||
|
||||
const fieldMetadataId =
|
||||
objectMetadataItemWithFieldMaps.fieldIdByName[key] ||
|
||||
objectMetadataItemWithFieldMaps.fieldIdByName[
|
||||
compositePropertyArgs?.parentField ?? ''
|
||||
];
|
||||
fieldIdByName[key] ||
|
||||
fieldIdByName[compositePropertyArgs?.parentField ?? ''];
|
||||
|
||||
const fieldMetadata = objectMetadataItemWithFieldMaps.fieldsById[
|
||||
fieldMetadataId
|
||||
] as FieldMetadataEntity<FieldMetadataType> | undefined;
|
||||
const fieldMetadata = flatFieldMetadataMaps.byId[fieldMetadataId] as
|
||||
| FlatFieldMetadata<FieldMetadataType>
|
||||
| undefined;
|
||||
|
||||
const isRelation = fieldMetadata
|
||||
? isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.RELATION)
|
||||
@@ -71,8 +83,9 @@ export function formatResult<T>(
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
newData[key] = formatResult(
|
||||
value,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
} else if (fieldMetadata) {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
@@ -93,7 +106,9 @@ export function formatResult<T>(
|
||||
}
|
||||
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
flatObjectMetadataMaps.byId[
|
||||
fieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!targetObjectMetadata) {
|
||||
throw new Error(
|
||||
@@ -105,7 +120,8 @@ export function formatResult<T>(
|
||||
newData[key] = formatResult(
|
||||
value,
|
||||
targetObjectMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,8 +147,9 @@ export function formatResult<T>(
|
||||
: value;
|
||||
}
|
||||
|
||||
const fieldMetadataItemsOfTypeDateOnly = Object.values(
|
||||
objectMetadataItemWithFieldMaps.fieldsById,
|
||||
const fieldMetadataItemsOfTypeDateOnly = getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
).filter((field) => field.type === FieldMetadataType.DATE);
|
||||
|
||||
for (const dateField of fieldMetadataItemsOfTypeDateOnly) {
|
||||
@@ -151,10 +168,12 @@ export function formatResult<T>(
|
||||
}
|
||||
|
||||
export function getCompositeFieldMetadataMap(
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
const compositeFieldMetadataCollection = getCompositeFieldMetadataCollection(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
return new Map(
|
||||
@@ -211,7 +230,7 @@ function formatFieldMetadataValue(
|
||||
function transformCompositeFieldNullValue(
|
||||
value: unknown,
|
||||
compositePropertyName: string,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
) {
|
||||
if (!isNull(value)) return value;
|
||||
|
||||
|
||||
+9
-4
@@ -12,7 +12,9 @@ import type { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/typ
|
||||
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
|
||||
import { ObjectRecordUpsertEvent } from 'src/engine/core-modules/event-emitter/types/object-record-upsert.event';
|
||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
||||
import type { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import type { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
@@ -24,13 +26,15 @@ export const formatTwentyOrmEventToDatabaseBatchEvent = <
|
||||
>({
|
||||
action,
|
||||
objectMetadataItem,
|
||||
flatFieldMetadataMaps,
|
||||
workspaceId,
|
||||
authContext,
|
||||
entities,
|
||||
beforeEntities,
|
||||
}: {
|
||||
action: DatabaseEventAction;
|
||||
objectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataItem: FlatObjectMetadata;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
workspaceId: string;
|
||||
authContext?: AuthContext;
|
||||
entities: T | T[];
|
||||
@@ -41,7 +45,6 @@ export const formatTwentyOrmEventToDatabaseBatchEvent = <
|
||||
}
|
||||
|
||||
const objectMetadataNameSingular = objectMetadataItem.nameSingular;
|
||||
const fields = Object.values(objectMetadataItem.fieldsById ?? {});
|
||||
const entityArray = isDefined(entities)
|
||||
? Array.isArray(entities)
|
||||
? entities
|
||||
@@ -89,6 +92,7 @@ export const formatTwentyOrmEventToDatabaseBatchEvent = <
|
||||
before,
|
||||
after,
|
||||
objectMetadataItem,
|
||||
flatFieldMetadataMaps,
|
||||
) as Partial<ObjectRecordDiff<T>>;
|
||||
|
||||
const updatedFields = Object.keys(diff);
|
||||
@@ -158,6 +162,7 @@ export const formatTwentyOrmEventToDatabaseBatchEvent = <
|
||||
before ?? {},
|
||||
after,
|
||||
objectMetadataItem,
|
||||
flatFieldMetadataMaps,
|
||||
) as Partial<ObjectRecordDiff<T>>;
|
||||
updatedFields = Object.keys(diff);
|
||||
|
||||
@@ -183,7 +188,7 @@ export const formatTwentyOrmEventToDatabaseBatchEvent = <
|
||||
objectMetadataNameSingular,
|
||||
action,
|
||||
events,
|
||||
objectMetadata: { ...objectMetadataItem, fields },
|
||||
objectMetadata: objectMetadataItem,
|
||||
workspaceId,
|
||||
};
|
||||
};
|
||||
|
||||
+9
-6
@@ -1,16 +1,18 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type CompositeType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
type ColumnNameProcessor,
|
||||
processFieldMetadataForColumnNameMapping,
|
||||
} from 'src/engine/twenty-orm/utils/process-field-metadata-for-column-name-mapping.util';
|
||||
|
||||
export function getColumnNameToFieldMetadataIdMap(
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
const columnNameToFieldMetadataIdMap: Record<string, string> = {};
|
||||
|
||||
@@ -21,7 +23,7 @@ export function getColumnNameToFieldMetadataIdMap(
|
||||
compositeType,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
compositeType: CompositeType;
|
||||
}) => {
|
||||
compositeType.properties.forEach((compositeProperty) => {
|
||||
@@ -59,7 +61,8 @@ export function getColumnNameToFieldMetadataIdMap(
|
||||
};
|
||||
|
||||
processFieldMetadataForColumnNameMapping(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
processor,
|
||||
);
|
||||
|
||||
|
||||
+9
-4
@@ -1,11 +1,16 @@
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
export function getCompositeFieldMetadataCollection(
|
||||
ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
const compositeFieldMetadataCollection = Object.values(
|
||||
ObjectMetadataItemWithFieldMaps.fieldsById,
|
||||
const compositeFieldMetadataCollection = getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
).filter((fieldMetadataItem) =>
|
||||
isCompositeFieldMetadataType(fieldMetadataItem.type),
|
||||
);
|
||||
|
||||
+9
-6
@@ -1,15 +1,17 @@
|
||||
import { type CompositeType } from 'twenty-shared/types';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import {
|
||||
type ColumnNameProcessor,
|
||||
processFieldMetadataForColumnNameMapping,
|
||||
} from 'src/engine/twenty-orm/utils/process-field-metadata-for-column-name-mapping.util';
|
||||
|
||||
export function getFieldMetadataIdToColumnNamesMap(
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
const fieldMetadataToColumnNamesMap = new Map<string, string[]>();
|
||||
|
||||
@@ -20,7 +22,7 @@ export function getFieldMetadataIdToColumnNamesMap(
|
||||
compositeType,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
compositeType: CompositeType;
|
||||
}) => {
|
||||
compositeType.properties.forEach((compositeProperty) => {
|
||||
@@ -52,7 +54,7 @@ export function getFieldMetadataIdToColumnNamesMap(
|
||||
columnName,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
columnName: string;
|
||||
}) => {
|
||||
fieldMetadataToColumnNamesMap.set(fieldMetadataId, [columnName]);
|
||||
@@ -60,7 +62,8 @@ export function getFieldMetadataIdToColumnNamesMap(
|
||||
};
|
||||
|
||||
processFieldMetadataForColumnNameMapping(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
processor,
|
||||
);
|
||||
|
||||
|
||||
+16
-10
@@ -6,8 +6,7 @@ import {
|
||||
|
||||
import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
@@ -17,7 +16,7 @@ import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspac
|
||||
export const getObjectMetadataFromEntityTarget = <T extends ObjectLiteral>(
|
||||
entityTarget: EntityTarget<T>,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
): ObjectMetadataItemWithFieldMaps => {
|
||||
): FlatObjectMetadata => {
|
||||
const objectMetadataName =
|
||||
typeof entityTarget === 'string'
|
||||
? entityTarget
|
||||
@@ -33,22 +32,29 @@ export const getObjectMetadataFromEntityTarget = <T extends ObjectLiteral>(
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata = getObjectMetadataMapItemByNameSingular(
|
||||
internalContext.objectMetadataMaps,
|
||||
objectMetadataName,
|
||||
);
|
||||
const objectMetadataId =
|
||||
internalContext.objectIdByNameSingular[objectMetadataName];
|
||||
|
||||
if (!objectMetadata) {
|
||||
if (!objectMetadataId) {
|
||||
throw new TwentyORMException(
|
||||
`Object metadata for object "${objectMetadataName}" is missing ` +
|
||||
`in workspace "${internalContext.workspaceId}" ` +
|
||||
`with object metadata collection length: ${
|
||||
Object.keys(internalContext.objectMetadataMaps.idByNameSingular)
|
||||
.length
|
||||
Object.keys(internalContext.objectIdByNameSingular).length
|
||||
}`,
|
||||
TwentyORMExceptionCode.MALFORMED_METADATA,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata =
|
||||
internalContext.flatObjectMetadataMaps.byId[objectMetadataId];
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new TwentyORMException(
|
||||
`Object metadata for object "${objectMetadataName}" (id: ${objectMetadataId}) is missing`,
|
||||
TwentyORMExceptionCode.MALFORMED_METADATA,
|
||||
);
|
||||
}
|
||||
|
||||
return objectMetadata;
|
||||
};
|
||||
|
||||
+18
-14
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
type FieldMetadataRelationSettings,
|
||||
type CompositeType,
|
||||
compositeTypeDefinitions,
|
||||
} from 'twenty-shared/types';
|
||||
@@ -7,15 +6,17 @@ import {
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
export type ColumnNameProcessor = {
|
||||
processCompositeField: ({
|
||||
@@ -24,7 +25,7 @@ export type ColumnNameProcessor = {
|
||||
compositeType,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
compositeType: CompositeType;
|
||||
}) => void;
|
||||
processRelationField: ({
|
||||
@@ -34,7 +35,7 @@ export type ColumnNameProcessor = {
|
||||
connectFieldName,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
joinColumnName: string;
|
||||
connectFieldName?: string;
|
||||
}) => void;
|
||||
@@ -44,18 +45,22 @@ export type ColumnNameProcessor = {
|
||||
columnName,
|
||||
}: {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
columnName: string;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export function processFieldMetadataForColumnNameMapping(
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
processor: ColumnNameProcessor,
|
||||
) {
|
||||
for (const [fieldMetadataId, fieldMetadata] of Object.entries(
|
||||
objectMetadataItemWithFieldMaps.fieldsById,
|
||||
)) {
|
||||
for (const fieldMetadataId of flatObjectMetadata.fieldMetadataIds) {
|
||||
const fieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityId: fieldMetadataId,
|
||||
});
|
||||
|
||||
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
|
||||
|
||||
@@ -72,9 +77,8 @@ export function processFieldMetadataForColumnNameMapping(
|
||||
compositeType,
|
||||
});
|
||||
} else {
|
||||
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
const fieldMetadataSettings =
|
||||
fieldMetadata.settings as FieldMetadataRelationSettings;
|
||||
if (isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
const fieldMetadataSettings = fieldMetadata.settings;
|
||||
|
||||
if (fieldMetadataSettings?.relationType === RelationType.ONE_TO_MANY) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user