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:
+280
-143
@@ -3,115 +3,185 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
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';
|
||||
|
||||
describe('buildColumnsToSelect', () => {
|
||||
const mockObjectMetadataItemWithFieldMaps: any = {
|
||||
id: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605',
|
||||
standardId: '20202020-e674-48e5-a542-72570eee7213',
|
||||
dataSourceId: 'd161a12f-1daa-4a0b-887c-96a48a3d9ecf',
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
labelSingular: 'Person',
|
||||
labelPlural: 'People',
|
||||
description: 'A person',
|
||||
icon: 'IconUser',
|
||||
standardOverrides: null,
|
||||
targetTableName: 'DEPRECATED',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isAuditLogged: true,
|
||||
isSearchable: true,
|
||||
duplicateCriteria: [
|
||||
['nameFirstName', 'nameLastName'],
|
||||
['linkedinLinkPrimaryLinkUrl'],
|
||||
['emailsPrimaryEmail'],
|
||||
],
|
||||
shortcut: 'P',
|
||||
labelIdentifierFieldMetadataId: '92414583-c6a6-4c98-bae7-6ce318bd3423',
|
||||
imageIdentifierFieldMetadataId: '6bfcecc4-1866-4254-ba6b-6f22246819bb',
|
||||
isLabelSyncedWithName: false,
|
||||
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
|
||||
createdAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
indexMetadatas: [],
|
||||
fieldsById: {
|
||||
'92414583-c6a6-4c98-bae7-6ce318bd3423': {
|
||||
id: '92414583-c6a6-4c98-bae7-6ce318bd3423',
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: {
|
||||
lastName: "''",
|
||||
firstName: "''",
|
||||
},
|
||||
description: "Contact's name",
|
||||
icon: 'IconUser',
|
||||
standardOverrides: null,
|
||||
options: undefined,
|
||||
settings: undefined,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
|
||||
isLabelSyncedWithName: true,
|
||||
relationTargetFieldMetadataId: undefined,
|
||||
relationTargetObjectMetadataId: undefined,
|
||||
objectMetadataId: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605',
|
||||
createdAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
},
|
||||
'30dc1370-bb1a-42e3-abbc-a40edf1c6796': {
|
||||
id: '30dc1370-bb1a-42e3-abbc-a40edf1c6796',
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
label: 'Company',
|
||||
defaultValue: null,
|
||||
description: "Contact's company",
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
standardOverrides: null,
|
||||
options: undefined,
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: 'companyId',
|
||||
},
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
|
||||
isLabelSyncedWithName: true,
|
||||
relationTargetFieldMetadataId: '83cd9e7f-dfbc-4b93-a0a7-4c04b76d2009',
|
||||
relationTargetObjectMetadataId: '9af20778-2f2c-4e22-ae83-2e77e479b57c',
|
||||
objectMetadataId: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605',
|
||||
createdAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
},
|
||||
},
|
||||
fieldIdByName: {
|
||||
name: '92414583-c6a6-4c98-bae7-6ce318bd3423',
|
||||
company: '30dc1370-bb1a-42e3-abbc-a40edf1c6796',
|
||||
},
|
||||
fieldIdByJoinColumnName: {
|
||||
companyId: '30dc1370-bb1a-42e3-abbc-a40edf1c6796',
|
||||
},
|
||||
};
|
||||
const workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
const personObjectId = 'a55d8cad-4c3d-4c8a-82b5-539a36de5605';
|
||||
const companyObjectId = '9af20778-2f2c-4e22-ae83-2e77e479b57c';
|
||||
|
||||
const mockObjectMetadataMaps = {
|
||||
byId: {
|
||||
'9af20778-2f2c-4e22-ae83-2e77e479b57c': {
|
||||
id: '9af20778-2f2c-4e22-ae83-2e77e479b57c',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
},
|
||||
const nameFieldId = '92414583-c6a6-4c98-bae7-6ce318bd3423';
|
||||
const companyFieldId = '30dc1370-bb1a-42e3-abbc-a40edf1c6796';
|
||||
|
||||
const createMockField = (
|
||||
overrides: Partial<FlatFieldMetadata> & {
|
||||
id: string;
|
||||
name: string;
|
||||
type: FieldMetadataType;
|
||||
objectMetadataId: string;
|
||||
},
|
||||
};
|
||||
): FlatFieldMetadata =>
|
||||
({
|
||||
workspaceId,
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
universalIdentifier: overrides.id,
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
label: overrides.name,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isUnique: false,
|
||||
...overrides,
|
||||
}) as FlatFieldMetadata;
|
||||
|
||||
const nameField = createMockField({
|
||||
id: nameFieldId,
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
objectMetadataId: personObjectId,
|
||||
defaultValue: {
|
||||
lastName: "''",
|
||||
firstName: "''",
|
||||
},
|
||||
description: "Contact's name",
|
||||
icon: 'IconUser',
|
||||
});
|
||||
|
||||
const companyRelationField = createMockField({
|
||||
id: companyFieldId,
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
label: 'Company',
|
||||
objectMetadataId: personObjectId,
|
||||
defaultValue: null,
|
||||
description: "Contact's company",
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: 'companyId',
|
||||
},
|
||||
relationTargetFieldMetadataId: '83cd9e7f-dfbc-4b93-a0a7-4c04b76d2009',
|
||||
relationTargetObjectMetadataId: companyObjectId,
|
||||
});
|
||||
|
||||
const buildMockFlatObjectMetadata = (
|
||||
fieldIds: string[],
|
||||
): FlatObjectMetadata =>
|
||||
({
|
||||
id: personObjectId,
|
||||
standardId: '20202020-e674-48e5-a542-72570eee7213',
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
labelSingular: 'Person',
|
||||
labelPlural: 'People',
|
||||
description: 'A person',
|
||||
icon: 'IconUser',
|
||||
targetTableName: 'DEPRECATED',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isAuditLogged: true,
|
||||
isSearchable: true,
|
||||
duplicateCriteria: [
|
||||
['nameFirstName', 'nameLastName'],
|
||||
['linkedinLinkPrimaryLinkUrl'],
|
||||
['emailsPrimaryEmail'],
|
||||
],
|
||||
shortcut: 'P',
|
||||
labelIdentifierFieldMetadataId: nameFieldId,
|
||||
imageIdentifierFieldMetadataId: '6bfcecc4-1866-4254-ba6b-6f22246819bb',
|
||||
isLabelSyncedWithName: false,
|
||||
workspaceId,
|
||||
createdAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
|
||||
universalIdentifier: personObjectId,
|
||||
fieldMetadataIds: fieldIds,
|
||||
indexMetadataIds: [],
|
||||
viewIds: [],
|
||||
applicationId: null,
|
||||
}) as unknown as FlatObjectMetadata;
|
||||
|
||||
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: {},
|
||||
});
|
||||
|
||||
const buildFlatObjectMetadataMaps = (
|
||||
objects: FlatObjectMetadata[],
|
||||
): FlatEntityMaps<FlatObjectMetadata> => ({
|
||||
byId: objects.reduce(
|
||||
(acc, obj) => {
|
||||
acc[obj.id] = obj;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FlatObjectMetadata>,
|
||||
),
|
||||
idByUniversalIdentifier: objects.reduce(
|
||||
(acc, obj) => {
|
||||
if (obj.universalIdentifier) {
|
||||
acc[obj.universalIdentifier] = obj.id;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
universalIdentifiersByApplicationId: {},
|
||||
});
|
||||
|
||||
const companyObjectMetadata: FlatObjectMetadata = {
|
||||
id: companyObjectId,
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
fieldMetadataIds: [],
|
||||
indexMetadataIds: [],
|
||||
viewIds: [],
|
||||
} as unknown as FlatObjectMetadata;
|
||||
|
||||
it('should build columns to select with relation fields', () => {
|
||||
const flatObjectMetadata = buildMockFlatObjectMetadata([
|
||||
nameFieldId,
|
||||
companyFieldId,
|
||||
]);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
|
||||
nameField,
|
||||
companyRelationField,
|
||||
]);
|
||||
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
|
||||
flatObjectMetadata,
|
||||
companyObjectMetadata,
|
||||
]);
|
||||
|
||||
const select = {
|
||||
nameFirstName: true,
|
||||
company: {
|
||||
@@ -127,8 +197,9 @@ describe('buildColumnsToSelect', () => {
|
||||
const result = buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -139,6 +210,19 @@ describe('buildColumnsToSelect', () => {
|
||||
});
|
||||
|
||||
it('should build columns to select without relation fields', () => {
|
||||
const flatObjectMetadata = buildMockFlatObjectMetadata([
|
||||
nameFieldId,
|
||||
companyFieldId,
|
||||
]);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
|
||||
nameField,
|
||||
companyRelationField,
|
||||
]);
|
||||
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
|
||||
flatObjectMetadata,
|
||||
companyObjectMetadata,
|
||||
]);
|
||||
|
||||
const select = {
|
||||
nameFirstName: true,
|
||||
nameLastName: true,
|
||||
@@ -149,8 +233,9 @@ describe('buildColumnsToSelect', () => {
|
||||
const result = buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -161,6 +246,19 @@ describe('buildColumnsToSelect', () => {
|
||||
});
|
||||
|
||||
it('should filter out non-boolean values from select', () => {
|
||||
const flatObjectMetadata = buildMockFlatObjectMetadata([
|
||||
nameFieldId,
|
||||
companyFieldId,
|
||||
]);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
|
||||
nameField,
|
||||
companyRelationField,
|
||||
]);
|
||||
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
|
||||
flatObjectMetadata,
|
||||
companyObjectMetadata,
|
||||
]);
|
||||
|
||||
const select = {
|
||||
nameFirstName: true,
|
||||
nameLastName: false,
|
||||
@@ -172,8 +270,9 @@ describe('buildColumnsToSelect', () => {
|
||||
const result = buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -183,6 +282,19 @@ describe('buildColumnsToSelect', () => {
|
||||
});
|
||||
|
||||
it('should handle relation field that is not a relation type', () => {
|
||||
const flatObjectMetadata = buildMockFlatObjectMetadata([
|
||||
nameFieldId,
|
||||
companyFieldId,
|
||||
]);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
|
||||
nameField,
|
||||
companyRelationField,
|
||||
]);
|
||||
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
|
||||
flatObjectMetadata,
|
||||
companyObjectMetadata,
|
||||
]);
|
||||
|
||||
const select = {
|
||||
nameFirstName: true,
|
||||
};
|
||||
@@ -194,8 +306,9 @@ describe('buildColumnsToSelect', () => {
|
||||
const result = buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -205,21 +318,32 @@ describe('buildColumnsToSelect', () => {
|
||||
});
|
||||
|
||||
it('should handle relation field that is not MANY_TO_ONE', () => {
|
||||
const mockObjectMetadataWithOneToMany: any = {
|
||||
...mockObjectMetadataItemWithFieldMaps,
|
||||
fieldsById: {
|
||||
...mockObjectMetadataItemWithFieldMaps.fieldsById,
|
||||
'30dc1370-bb1a-42e3-abbc-a40edf1c6796': {
|
||||
...mockObjectMetadataItemWithFieldMaps.fieldsById[
|
||||
'30dc1370-bb1a-42e3-abbc-a40edf1c6796'
|
||||
],
|
||||
settings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
joinColumnName: 'companyId',
|
||||
},
|
||||
},
|
||||
const oneToManyCompanyField = createMockField({
|
||||
id: companyFieldId,
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
label: 'Company',
|
||||
objectMetadataId: personObjectId,
|
||||
defaultValue: null,
|
||||
settings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
joinColumnName: 'companyId',
|
||||
},
|
||||
};
|
||||
relationTargetObjectMetadataId: companyObjectId,
|
||||
});
|
||||
|
||||
const flatObjectMetadata = buildMockFlatObjectMetadata([
|
||||
nameFieldId,
|
||||
companyFieldId,
|
||||
]);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
|
||||
nameField,
|
||||
oneToManyCompanyField,
|
||||
]);
|
||||
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
|
||||
flatObjectMetadata,
|
||||
companyObjectMetadata,
|
||||
]);
|
||||
|
||||
const select = {
|
||||
nameFirstName: true,
|
||||
@@ -232,8 +356,9 @@ describe('buildColumnsToSelect', () => {
|
||||
const result = buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps: mockObjectMetadataWithOneToMany,
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -243,21 +368,32 @@ describe('buildColumnsToSelect', () => {
|
||||
});
|
||||
|
||||
it('should handle relation field without joinColumnName', () => {
|
||||
const mockObjectMetadataWithoutJoinColumn: any = {
|
||||
...mockObjectMetadataItemWithFieldMaps,
|
||||
fieldsById: {
|
||||
...mockObjectMetadataItemWithFieldMaps.fieldsById,
|
||||
'30dc1370-bb1a-42e3-abbc-a40edf1c6796': {
|
||||
...mockObjectMetadataItemWithFieldMaps.fieldsById[
|
||||
'30dc1370-bb1a-42e3-abbc-a40edf1c6796'
|
||||
],
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: null,
|
||||
},
|
||||
},
|
||||
const noJoinColumnCompanyField = createMockField({
|
||||
id: companyFieldId,
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
label: 'Company',
|
||||
objectMetadataId: personObjectId,
|
||||
defaultValue: null,
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: null,
|
||||
},
|
||||
};
|
||||
relationTargetObjectMetadataId: companyObjectId,
|
||||
});
|
||||
|
||||
const flatObjectMetadata = buildMockFlatObjectMetadata([
|
||||
nameFieldId,
|
||||
companyFieldId,
|
||||
]);
|
||||
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
|
||||
nameField,
|
||||
noJoinColumnCompanyField,
|
||||
]);
|
||||
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
|
||||
flatObjectMetadata,
|
||||
companyObjectMetadata,
|
||||
]);
|
||||
|
||||
const select = {
|
||||
nameFirstName: true,
|
||||
@@ -270,8 +406,9 @@ describe('buildColumnsToSelect', () => {
|
||||
const result = buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps: mockObjectMetadataWithoutJoinColumn,
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
|
||||
+12
-8
@@ -1,24 +1,28 @@
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
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 FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
export const buildColumnsToReturn = ({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}: {
|
||||
select: Record<string, unknown>;
|
||||
relations: Record<string, unknown>;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}): string[] => {
|
||||
return Object.entries(
|
||||
buildColumnsToSelect({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}),
|
||||
)
|
||||
.filter(([_columnName, value]) => value === true)
|
||||
|
||||
+34
-30
@@ -1,32 +1,32 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.util';
|
||||
import { isFieldMetadataTypeRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-relation.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 { 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 { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
export const buildColumnsToSelect = ({
|
||||
select,
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}: {
|
||||
select: Record<string, unknown>;
|
||||
relations: Record<string, unknown>;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: {
|
||||
byId: Partial<
|
||||
Record<
|
||||
string,
|
||||
Pick<ObjectMetadataItemWithFieldMaps, 'nameSingular' | 'namePlural'>
|
||||
>
|
||||
>;
|
||||
};
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}) => {
|
||||
const requiredRelationColumns = getRequiredRelationColumns(
|
||||
relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const fieldsToSelect: Record<string, boolean> = Object.entries(select)
|
||||
@@ -44,20 +44,19 @@ export const buildColumnsToSelect = ({
|
||||
|
||||
const getRequiredRelationColumns = (
|
||||
relations: Record<string, unknown>,
|
||||
objectMetadataItem: Pick<ObjectMetadataItemWithFieldMaps, 'fieldsById'>,
|
||||
objectMetadataMaps: {
|
||||
byId: Partial<
|
||||
Record<
|
||||
string,
|
||||
Pick<ObjectMetadataItemWithFieldMaps, 'nameSingular' | 'namePlural'>
|
||||
>
|
||||
>;
|
||||
},
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
): string[] => {
|
||||
const requiredColumns: string[] = [];
|
||||
|
||||
for (const fieldMetadata of Object.values(objectMetadataItem.fieldsById)) {
|
||||
if (isFieldMetadataTypeRelation(fieldMetadata)) {
|
||||
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
|
||||
const fieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: fieldId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (isFlatFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)) {
|
||||
const relationValue = relations[fieldMetadata.name];
|
||||
|
||||
if (
|
||||
@@ -71,9 +70,14 @@ const getRequiredRelationColumns = (
|
||||
requiredColumns.push(fieldMetadata.settings.joinColumnName);
|
||||
}
|
||||
|
||||
if (isFieldMetadataTypeMorphRelation(fieldMetadata)) {
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
if (
|
||||
isFlatFieldMetadataOfType(fieldMetadata, FieldMetadataType.MORPH_RELATION)
|
||||
) {
|
||||
const targetObjectMetadata = fieldMetadata.relationTargetObjectMetadataId
|
||||
? flatObjectMetadataMaps.byId[
|
||||
fieldMetadata.relationTargetObjectMetadataId
|
||||
]
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
!fieldMetadata.settings?.relationType ||
|
||||
|
||||
+46
-31
@@ -2,56 +2,71 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.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 { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
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 { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
type GetFieldMetadataFromGraphQLFieldArgs = {
|
||||
objectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
graphQLField: string;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
};
|
||||
|
||||
export function getFieldMetadataFromGraphQLField({
|
||||
objectMetadataItem,
|
||||
flatObjectMetadata,
|
||||
graphQLField,
|
||||
objectMetadataMaps,
|
||||
}: GetFieldMetadataFromGraphQLFieldArgs) {
|
||||
const sourceFieldMetadataId = objectMetadataItem.fieldIdByName[graphQLField];
|
||||
let sourceFieldMetadata =
|
||||
objectMetadataItem.fieldsById[sourceFieldMetadataId];
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}: GetFieldMetadataFromGraphQLFieldArgs): FlatFieldMetadata | undefined {
|
||||
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
const sourceFieldMetadataId = fieldIdByName[graphQLField];
|
||||
let sourceFieldMetadata = sourceFieldMetadataId
|
||||
? flatFieldMetadataMaps.byId[sourceFieldMetadataId]
|
||||
: undefined;
|
||||
|
||||
// If empty, it could be a morph relation
|
||||
if (!isDefined(sourceFieldMetadata)) {
|
||||
const morphRelationsWithTargetObjectMetadata = Object.values(
|
||||
objectMetadataItem.fieldsById,
|
||||
)
|
||||
.filter(
|
||||
(fieldMetadata) =>
|
||||
fieldMetadata.type === FieldMetadataType.MORPH_RELATION,
|
||||
const morphRelationsWithTargetObjectMetadata =
|
||||
getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
)
|
||||
.map((fieldMetadata) => {
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
.filter(
|
||||
(fieldMetadata) =>
|
||||
fieldMetadata.type === FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
.map((fieldMetadata) => {
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldMetadata,
|
||||
targetObjectMetadata,
|
||||
};
|
||||
});
|
||||
return {
|
||||
fieldMetadata,
|
||||
targetObjectMetadata,
|
||||
};
|
||||
});
|
||||
|
||||
const possibleGraphQLFieldNames: {
|
||||
graphQLField: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
targetObjectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
targetObjectMetadata: FlatObjectMetadata;
|
||||
}[] = [];
|
||||
|
||||
morphRelationsWithTargetObjectMetadata.map((morphRelation) => {
|
||||
if (
|
||||
!isFieldMetadataTypeMorphRelation(morphRelation.fieldMetadata) ||
|
||||
!isFlatFieldMetadataOfType(
|
||||
morphRelation.fieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
) ||
|
||||
!morphRelation.fieldMetadata.settings?.relationType
|
||||
) {
|
||||
return;
|
||||
|
||||
+7
-6
@@ -2,13 +2,14 @@ import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
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';
|
||||
|
||||
export const getTargetObjectMetadataOrThrow = (
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
) => {
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
): FlatObjectMetadata => {
|
||||
if (!fieldMetadata.relationTargetObjectMetadataId) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Relation target object metadata id not found for field ${fieldMetadata.name}`,
|
||||
@@ -17,7 +18,7 @@ export const getTargetObjectMetadataOrThrow = (
|
||||
}
|
||||
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
flatObjectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
|
||||
if (!targetObjectMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
|
||||
Reference in New Issue
Block a user