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:
+148
-92
@@ -1,24 +1,151 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
|
||||
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';
|
||||
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';
|
||||
|
||||
const workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
const objectMetadataId = 'person-object-id';
|
||||
|
||||
export const mockPersonObjectMetadataWithFieldMaps = (
|
||||
const mockFieldMetadatas: FlatFieldMetadata[] = [
|
||||
{
|
||||
id: 'name-id',
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: {
|
||||
lastName: "''",
|
||||
firstName: "''",
|
||||
},
|
||||
objectMetadataId,
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'name-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'emails-id',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
name: 'emails',
|
||||
label: 'Emails',
|
||||
defaultValue: {
|
||||
primaryEmail: "''",
|
||||
additionalEmails: null,
|
||||
},
|
||||
objectMetadataId,
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'emails-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'linkedinLink-id',
|
||||
type: FieldMetadataType.LINKS,
|
||||
name: 'linkedinLink',
|
||||
label: 'Linkedin',
|
||||
defaultValue: {
|
||||
primaryLinkUrl: "''",
|
||||
secondaryLinks: [],
|
||||
primaryLinkLabel: "''",
|
||||
},
|
||||
objectMetadataId,
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'linkedinLink-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
{
|
||||
id: 'jobTitle-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'jobTitle',
|
||||
label: 'Job Title',
|
||||
defaultValue: "''",
|
||||
objectMetadataId,
|
||||
isNullable: false,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'jobTitle-id',
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
calendarViewIds: [],
|
||||
applicationId: null,
|
||||
} as unknown as FlatFieldMetadata,
|
||||
];
|
||||
|
||||
export const mockPersonFlatFieldMetadataMaps =
|
||||
(): FlatEntityMaps<FlatFieldMetadata> => ({
|
||||
byId: mockFieldMetadatas.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.id] = field;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FlatFieldMetadata>,
|
||||
),
|
||||
idByUniversalIdentifier: mockFieldMetadatas.reduce(
|
||||
(acc, field) => {
|
||||
acc[field.universalIdentifier] = field.id;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
universalIdentifiersByApplicationId: {},
|
||||
});
|
||||
|
||||
export const mockPersonFlatObjectMetadataMaps = (
|
||||
duplicateCriteria: WorkspaceEntityDuplicateCriteria[],
|
||||
) =>
|
||||
getMockObjectMetadataItemWithFieldsMaps({
|
||||
id: '',
|
||||
): FlatEntityMaps<FlatObjectMetadata> => {
|
||||
const flatObjectMetadata = mockPersonFlatObjectMetadata(duplicateCriteria);
|
||||
|
||||
return {
|
||||
byId: {
|
||||
[flatObjectMetadata.id]: flatObjectMetadata,
|
||||
},
|
||||
idByUniversalIdentifier: {
|
||||
[flatObjectMetadata.universalIdentifier as string]: flatObjectMetadata.id,
|
||||
},
|
||||
universalIdentifiersByApplicationId: {},
|
||||
};
|
||||
};
|
||||
|
||||
export const mockPersonFlatObjectMetadata = (
|
||||
duplicateCriteria: WorkspaceEntityDuplicateCriteria[],
|
||||
): FlatObjectMetadata =>
|
||||
({
|
||||
id: objectMetadataId,
|
||||
icon: 'Icon123',
|
||||
standardId: '',
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
labelSingular: 'Person',
|
||||
labelPlural: 'People',
|
||||
description: 'A person',
|
||||
targetTableName: 'DEPRECATED',
|
||||
targetTableName: 'person',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
@@ -29,87 +156,16 @@ export const mockPersonObjectMetadataWithFieldMaps = (
|
||||
labelIdentifierFieldMetadataId: '',
|
||||
imageIdentifierFieldMetadataId: '',
|
||||
workspaceId,
|
||||
indexMetadatas: [],
|
||||
fieldIdByName: {
|
||||
name: 'name-id',
|
||||
emails: 'emails-id',
|
||||
linkedinLink: 'linkedinLink-id',
|
||||
jobTitle: 'jobTitle-id',
|
||||
},
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldsById: {
|
||||
'name-id': getMockFieldMetadataEntity({
|
||||
workspaceId,
|
||||
objectMetadataId: '',
|
||||
id: 'name-id',
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: {
|
||||
lastName: "''",
|
||||
firstName: "''",
|
||||
},
|
||||
description: "Contact's name",
|
||||
isCustom: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
'emails-id': getMockFieldMetadataEntity({
|
||||
workspaceId,
|
||||
objectMetadataId: '',
|
||||
id: 'emails-id',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
name: 'emails',
|
||||
label: 'Emails',
|
||||
defaultValue: {
|
||||
primaryEmail: "''",
|
||||
additionalEmails: null,
|
||||
},
|
||||
description: "Contact's Emails",
|
||||
isCustom: false,
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
'linkedinLink-id': getMockFieldMetadataEntity({
|
||||
workspaceId,
|
||||
objectMetadataId: '',
|
||||
id: 'linkedinLink-id',
|
||||
type: FieldMetadataType.LINKS,
|
||||
name: 'linkedinLink',
|
||||
label: 'Linkedin',
|
||||
defaultValue: {
|
||||
primaryLinkUrl: "''",
|
||||
secondaryLinks: [],
|
||||
primaryLinkLabel: "''",
|
||||
},
|
||||
description: "Contact's Linkedin account",
|
||||
isCustom: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
'jobTitle-id': getMockFieldMetadataEntity({
|
||||
workspaceId,
|
||||
objectMetadataId: '',
|
||||
id: 'jobTitle-id',
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'jobTitle',
|
||||
label: 'Job Title',
|
||||
defaultValue: "''",
|
||||
description: "Contact's job title",
|
||||
isCustom: false,
|
||||
isNullable: false,
|
||||
isUnique: false,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
},
|
||||
});
|
||||
universalIdentifier: objectMetadataId,
|
||||
indexMetadataIds: [],
|
||||
fieldMetadataIds: mockFieldMetadatas.map((field) => field.id),
|
||||
viewIds: [],
|
||||
applicationId: null,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
shortcut: null,
|
||||
description: null,
|
||||
standardOverrides: null,
|
||||
isUIReadOnly: false,
|
||||
}) as FlatObjectMetadata;
|
||||
|
||||
+11
-5
@@ -2,19 +2,25 @@ import { Brackets, NotBrackets, type WhereExpressionBuilder } from 'typeorm';
|
||||
|
||||
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
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 WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
|
||||
import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser';
|
||||
|
||||
export class GraphqlQueryFilterConditionParser {
|
||||
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
private flatObjectMetadata: FlatObjectMetadata;
|
||||
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
|
||||
|
||||
constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
|
||||
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||
constructor(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
this.flatObjectMetadata = flatObjectMetadata;
|
||||
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
|
||||
this.objectMetadataMapItem,
|
||||
this.flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+27
-12
@@ -1,5 +1,5 @@
|
||||
import { compositeTypeDefinitions } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { type WhereExpressionBuilder } from 'typeorm';
|
||||
|
||||
import {
|
||||
@@ -7,18 +7,35 @@ import {
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { computeWhereConditionParts } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts';
|
||||
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';
|
||||
|
||||
const ARRAY_OPERATORS = ['in', 'contains', 'notContains'];
|
||||
|
||||
export class GraphqlQueryFilterFieldParser {
|
||||
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
private flatObjectMetadata: FlatObjectMetadata;
|
||||
private flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
private fieldIdByName: Record<string, string>;
|
||||
private fieldIdByJoinColumnName: Record<string, string>;
|
||||
|
||||
constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
|
||||
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||
constructor(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
this.flatObjectMetadata = flatObjectMetadata;
|
||||
this.flatFieldMetadataMaps = flatFieldMetadataMaps;
|
||||
|
||||
const fieldMaps = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
this.fieldIdByName = fieldMaps.fieldIdByName;
|
||||
this.fieldIdByJoinColumnName = fieldMaps.fieldIdByJoinColumnName;
|
||||
}
|
||||
|
||||
public parse(
|
||||
@@ -30,13 +47,11 @@ export class GraphqlQueryFilterFieldParser {
|
||||
isFirst = false,
|
||||
): void {
|
||||
const fieldMetadataId =
|
||||
this.objectMetadataMapItem.fieldIdByName[`${key}`] ||
|
||||
this.objectMetadataMapItem.fieldIdByJoinColumnName[`${key}`];
|
||||
this.fieldIdByName[`${key}`] || this.fieldIdByJoinColumnName[`${key}`];
|
||||
|
||||
const fieldMetadata =
|
||||
this.objectMetadataMapItem.fieldsById[fieldMetadataId];
|
||||
const fieldMetadata = this.flatFieldMetadataMaps.byId[fieldMetadataId];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
if (!isDefined(fieldMetadata)) {
|
||||
throw new Error(`Field metadata not found for field: ${key}`);
|
||||
}
|
||||
|
||||
@@ -77,7 +92,7 @@ export class GraphqlQueryFilterFieldParser {
|
||||
|
||||
private parseCompositeFieldForFilter(
|
||||
queryBuilder: WhereExpressionBuilder,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
objectNameSingular: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fieldValue: any,
|
||||
|
||||
+54
-41
@@ -32,12 +32,13 @@ import {
|
||||
type AggregationField,
|
||||
getAvailableAggregationsFromObjectFields,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
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 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 { 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 { formatColumnNameForRelationField } from 'src/engine/twenty-orm/utils/format-column-name-for-relation-field.util';
|
||||
import { formatColumnNamesFromCompositeFieldAndSubfields } from 'src/engine/twenty-orm/utils/format-column-names-from-composite-field-and-subfield.util';
|
||||
|
||||
@@ -47,15 +48,28 @@ export type OrderByCondition = {
|
||||
};
|
||||
|
||||
export class GraphqlQueryOrderFieldParser {
|
||||
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
private objectMetadataMaps?: ObjectMetadataMaps;
|
||||
private flatObjectMetadata: FlatObjectMetadata;
|
||||
private flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
private flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
private fieldIdByName: Record<string, string>;
|
||||
private fieldIdByJoinColumnName: Record<string, string>;
|
||||
|
||||
constructor(
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps?: ObjectMetadataMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||
this.objectMetadataMaps = objectMetadataMaps;
|
||||
this.flatObjectMetadata = flatObjectMetadata;
|
||||
this.flatObjectMetadataMaps = flatObjectMetadataMaps;
|
||||
this.flatFieldMetadataMaps = flatFieldMetadataMaps;
|
||||
|
||||
const fieldMaps = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
this.fieldIdByName = fieldMaps.fieldIdByName;
|
||||
this.fieldIdByJoinColumnName = fieldMaps.fieldIdByJoinColumnName;
|
||||
}
|
||||
|
||||
parse(
|
||||
@@ -67,10 +81,10 @@ export class GraphqlQueryOrderFieldParser {
|
||||
(acc, item) => {
|
||||
Object.entries(item).forEach(([fieldName, orderByDirection]) => {
|
||||
const fieldMetadataId =
|
||||
this.objectMetadataMapItem.fieldIdByName[fieldName] ||
|
||||
this.objectMetadataMapItem.fieldIdByJoinColumnName[fieldName];
|
||||
this.fieldIdByName[fieldName] ||
|
||||
this.fieldIdByJoinColumnName[fieldName];
|
||||
const fieldMetadata =
|
||||
this.objectMetadataMapItem.fieldsById[fieldMetadataId];
|
||||
this.flatFieldMetadataMaps.byId[fieldMetadataId];
|
||||
|
||||
if (!fieldMetadata || orderByDirection === undefined) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
@@ -92,9 +106,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
const orderByCasting =
|
||||
this.getOptionalOrderByCasting(fieldMetadata);
|
||||
|
||||
const columnName = isFieldMetadataRelationOrMorphRelation(
|
||||
fieldMetadata,
|
||||
)
|
||||
const columnName = isMorphOrRelationFlatFieldMetadata(fieldMetadata)
|
||||
? formatColumnNameForRelationField(
|
||||
fieldMetadata.name,
|
||||
fieldMetadata.settings,
|
||||
@@ -124,17 +136,19 @@ export class GraphqlQueryOrderFieldParser {
|
||||
}): Record<string, OrderByCondition>[] {
|
||||
let parsedOrderBy: Record<string, OrderByCondition>[] = [];
|
||||
|
||||
const fields = this.flatObjectMetadata.fieldMetadataIds
|
||||
.map((id) => this.flatFieldMetadataMaps.byId[id])
|
||||
.filter(isDefined);
|
||||
|
||||
const availableAggregations: Record<string, AggregationField> =
|
||||
getAvailableAggregationsFromObjectFields(
|
||||
Object.values(this.objectMetadataMapItem.fieldsById),
|
||||
);
|
||||
getAvailableAggregationsFromObjectFields(fields);
|
||||
|
||||
for (const orderByArg of orderBy) {
|
||||
if (this.isAggregateOrderByArg(orderByArg)) {
|
||||
const parsedAggregateOrderBy = this.parseAggregateOrderByArg(
|
||||
availableAggregations,
|
||||
orderByArg,
|
||||
this.objectMetadataMapItem,
|
||||
this.flatObjectMetadata,
|
||||
);
|
||||
|
||||
parsedOrderBy.push(parsedAggregateOrderBy);
|
||||
@@ -148,10 +162,8 @@ export class GraphqlQueryOrderFieldParser {
|
||||
}
|
||||
|
||||
const fieldName = Object.keys(orderByArg)[0];
|
||||
const fieldMetadataId =
|
||||
this.objectMetadataMapItem.fieldIdByName[fieldName];
|
||||
const fieldMetadata =
|
||||
this.objectMetadataMapItem.fieldsById[fieldMetadataId];
|
||||
const fieldMetadataId = this.fieldIdByName[fieldName];
|
||||
const fieldMetadata = this.flatFieldMetadataMaps.byId[fieldMetadataId];
|
||||
|
||||
if (!isDefined(fieldMetadata)) {
|
||||
throw new UserInputError(`Cannot orderBy unknown field: ${fieldName}.`);
|
||||
@@ -162,7 +174,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
this.parseObjectRecordOrderByForScalarField({
|
||||
groupByFields,
|
||||
orderByArg,
|
||||
objectMetadataItemWithFieldMaps: this.objectMetadataMapItem,
|
||||
flatObjectMetadata: this.flatObjectMetadata,
|
||||
fieldMetadata,
|
||||
});
|
||||
|
||||
@@ -218,7 +230,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
this.parseObjectRecordOrderByForCompositeField({
|
||||
groupByFields,
|
||||
orderByArg,
|
||||
objectMetadataItemWithFieldMaps: this.objectMetadataMapItem,
|
||||
flatObjectMetadata: this.flatObjectMetadata,
|
||||
fieldMetadata,
|
||||
});
|
||||
|
||||
@@ -237,7 +249,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
}
|
||||
|
||||
getOptionalOrderByCasting(
|
||||
fieldMetadata: Pick<FieldMetadataEntity, 'type'>,
|
||||
fieldMetadata: Pick<FlatFieldMetadata, 'type'>,
|
||||
): string {
|
||||
if (
|
||||
fieldMetadata.type === FieldMetadataType.SELECT ||
|
||||
@@ -379,7 +391,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
parseAggregateOrderByArg = (
|
||||
availableAggregations: Record<string, AggregationField>,
|
||||
orderByArg: AggregateOrderByWithGroupByField,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
): Record<string, OrderByCondition> => {
|
||||
const aggregate = orderByArg.aggregate;
|
||||
|
||||
@@ -399,7 +411,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
|
||||
const aggregateExpression = ProcessAggregateHelper.getAggregateExpression(
|
||||
aggregateField,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
if (!isDefined(aggregateExpression)) {
|
||||
@@ -419,13 +431,13 @@ export class GraphqlQueryOrderFieldParser {
|
||||
parseObjectRecordOrderByForScalarField = ({
|
||||
groupByFields,
|
||||
orderByArg,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
fieldMetadata,
|
||||
}: {
|
||||
groupByFields: GroupByField[];
|
||||
orderByArg: ObjectRecordOrderByForScalarField;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
}): Record<string, OrderByCondition> | null => {
|
||||
const fieldIsInGroupBy = groupByFields.some(
|
||||
(groupByField) => groupByField.fieldMetadata.id === fieldMetadata.id,
|
||||
@@ -445,7 +457,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
}
|
||||
|
||||
return {
|
||||
[`"${objectMetadataItemWithFieldMaps.nameSingular}"."${fieldMetadata.name}"${orderByCasting}`]:
|
||||
[`"${flatObjectMetadata.nameSingular}"."${fieldMetadata.name}"${orderByCasting}`]:
|
||||
convertOrderByToFindOptionsOrder(orderByDirection),
|
||||
};
|
||||
};
|
||||
@@ -453,13 +465,13 @@ export class GraphqlQueryOrderFieldParser {
|
||||
parseObjectRecordOrderByForCompositeField = ({
|
||||
groupByFields,
|
||||
orderByArg,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
fieldMetadata,
|
||||
}: {
|
||||
groupByFields: GroupByField[];
|
||||
orderByArg: ObjectRecordOrderByForCompositeField;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
}): Record<string, OrderByCondition> | null => {
|
||||
const fieldName = Object.keys(orderByArg)[0];
|
||||
const orderBySubField = orderByArg[fieldName];
|
||||
@@ -491,7 +503,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
return parseCompositeFieldForOrder(
|
||||
fieldMetadata,
|
||||
orderBySubField,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -556,7 +568,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
}: {
|
||||
groupByFields: GroupByField[];
|
||||
orderByArg: ObjectRecordOrderByForRelationField;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
}): Record<string, OrderByCondition> | null => {
|
||||
const {
|
||||
associatedGroupByField,
|
||||
@@ -565,7 +577,8 @@ export class GraphqlQueryOrderFieldParser {
|
||||
} = prepareForOrderByRelationFieldParsing({
|
||||
orderByArg,
|
||||
fieldMetadata,
|
||||
objectMetadataMaps: this.objectMetadataMaps,
|
||||
flatObjectMetadataMaps: this.flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps: this.flatFieldMetadataMaps,
|
||||
groupByFields,
|
||||
});
|
||||
|
||||
@@ -709,9 +722,9 @@ export class GraphqlQueryOrderFieldParser {
|
||||
| ObjectRecordOrderByForCompositeField
|
||||
| ObjectRecordOrderByWithGroupByDateField
|
||||
| ObjectRecordOrderByForRelationField,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
): orderByArg is ObjectRecordOrderByForRelationField => {
|
||||
if (!isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
if (!isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -6,11 +6,11 @@ import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { type OrderByCondition } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
||||
import { convertOrderByToFindOptionsOrder } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/utils/convert-order-by-to-find-options-order';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
|
||||
export const parseCompositeFieldForOrder = (
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any,
|
||||
objectNameSingular: string,
|
||||
|
||||
+21
-19
@@ -8,26 +8,24 @@ import {
|
||||
type GroupByRelationField,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { isGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-relation-field.util';
|
||||
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 { 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';
|
||||
|
||||
export const prepareForOrderByRelationFieldParsing = ({
|
||||
orderByArg,
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
groupByFields,
|
||||
}: {
|
||||
orderByArg: ObjectRecordOrderByForRelationField;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
objectMetadataMaps?: ObjectMetadataMaps;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
groupByFields: GroupByField[];
|
||||
}) => {
|
||||
if (!objectMetadataMaps) {
|
||||
throw new UserInputError(
|
||||
`Object metadata maps not found for field ${fieldMetadata.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const relationFieldName = Object.keys(orderByArg)[0];
|
||||
const nestedFieldOrderByObject = orderByArg[relationFieldName];
|
||||
|
||||
@@ -57,17 +55,22 @@ export const prepareForOrderByRelationFieldParsing = ({
|
||||
);
|
||||
}
|
||||
|
||||
const targetObjectMetadataItem =
|
||||
objectMetadataMaps?.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
const targetObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
|
||||
if (!isDefined(targetObjectMetadataItem)) {
|
||||
if (!isDefined(targetObjectMetadata)) {
|
||||
throw new UserInputError(
|
||||
`Target object metadata item not found for field ${fieldMetadata.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const nestedFieldMetadataId =
|
||||
targetObjectMetadataItem?.fieldIdByName[nestedFieldName];
|
||||
const { fieldIdByName: targetFieldIdByName } =
|
||||
buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
targetObjectMetadata,
|
||||
);
|
||||
|
||||
const nestedFieldMetadataId = targetFieldIdByName[nestedFieldName];
|
||||
|
||||
if (!isDefined(nestedFieldMetadataId)) {
|
||||
throw new UserInputError(
|
||||
@@ -75,12 +78,11 @@ export const prepareForOrderByRelationFieldParsing = ({
|
||||
);
|
||||
}
|
||||
|
||||
const nestedFieldMetadata =
|
||||
targetObjectMetadataItem?.fieldsById[nestedFieldMetadataId];
|
||||
const nestedFieldMetadata = flatFieldMetadataMaps.byId[nestedFieldMetadataId];
|
||||
|
||||
if (!isDefined(nestedFieldMetadata) || !isDefined(nestedFieldMetadataId)) {
|
||||
throw new UserInputError(
|
||||
`Nested field "${nestedFieldName}" not found in target object "${targetObjectMetadataItem.nameSingular}"`,
|
||||
`Nested field "${nestedFieldName}" not found in target object "${targetObjectMetadata.nameSingular}"`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+12
-5
@@ -1,21 +1,28 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import {
|
||||
type AggregationField,
|
||||
getAvailableAggregationsFromObjectFields,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.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 class GraphqlQuerySelectedFieldsAggregateParser {
|
||||
parse(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||
): void {
|
||||
const fields = flatObjectMetadata.fieldMetadataIds
|
||||
.map((id) => flatFieldMetadataMaps.byId[id])
|
||||
.filter(isDefined);
|
||||
|
||||
const availableAggregations: Record<string, AggregationField> =
|
||||
getAvailableAggregationsFromObjectFields(
|
||||
Object.values(objectMetadataMapItem.fieldsById),
|
||||
);
|
||||
getAvailableAggregationsFromObjectFields(fields);
|
||||
|
||||
for (const selectedField of Object.keys(graphqlSelectedFields)) {
|
||||
const selectedAggregation = availableAggregations[selectedField];
|
||||
|
||||
+27
-13
@@ -1,20 +1,28 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
GraphqlQuerySelectedFieldsParser,
|
||||
type GraphqlQuerySelectedFieldsResult,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
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 { 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 { 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 { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
export class GraphqlQuerySelectedFieldsRelationParser {
|
||||
private objectMetadataMaps: ObjectMetadataMaps;
|
||||
private flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
private flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
|
||||
constructor(objectMetadataMaps: ObjectMetadataMaps) {
|
||||
this.objectMetadataMaps = objectMetadataMaps;
|
||||
constructor(
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
this.flatObjectMetadataMaps = flatObjectMetadataMaps;
|
||||
this.flatFieldMetadataMaps = flatFieldMetadataMaps;
|
||||
}
|
||||
|
||||
parseRelationField(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
fieldKey: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fieldValue: any,
|
||||
@@ -26,18 +34,24 @@ export class GraphqlQuerySelectedFieldsRelationParser {
|
||||
|
||||
accumulator.relations[fieldKey] = true;
|
||||
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMaps,
|
||||
);
|
||||
if (!isDefined(fieldMetadata.relationTargetObjectMetadataId)) {
|
||||
throw new Error(
|
||||
`Relation target object metadata id not found for field ${fieldMetadata.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: fieldMetadata.relationTargetObjectMetadataId,
|
||||
flatEntityMaps: this.flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const fieldParser = new GraphqlQuerySelectedFieldsParser(
|
||||
this.objectMetadataMaps,
|
||||
this.flatObjectMetadataMaps,
|
||||
this.flatFieldMetadataMaps,
|
||||
);
|
||||
const relationAccumulator = fieldParser.parse(
|
||||
fieldValue,
|
||||
targetObjectMetadata,
|
||||
this.objectMetadataMaps,
|
||||
);
|
||||
|
||||
accumulator.select[fieldKey] = {
|
||||
|
||||
+39
-31
@@ -6,12 +6,13 @@ import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { GraphqlQuerySelectedFieldsAggregateParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser';
|
||||
import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser';
|
||||
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 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 { 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';
|
||||
import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
|
||||
export type GraphqlQuerySelectedFieldsResult = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -25,18 +26,27 @@ export type GraphqlQuerySelectedFieldsResult = {
|
||||
export class GraphqlQuerySelectedFieldsParser {
|
||||
private graphqlQuerySelectedFieldsRelationParser: GraphqlQuerySelectedFieldsRelationParser;
|
||||
private aggregateParser: GraphqlQuerySelectedFieldsAggregateParser;
|
||||
private flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
private flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
|
||||
constructor(objectMetadataMaps: ObjectMetadataMaps) {
|
||||
constructor(
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
this.flatObjectMetadataMaps = flatObjectMetadataMaps;
|
||||
this.flatFieldMetadataMaps = flatFieldMetadataMaps;
|
||||
this.graphqlQuerySelectedFieldsRelationParser =
|
||||
new GraphqlQuerySelectedFieldsRelationParser(objectMetadataMaps);
|
||||
new GraphqlQuerySelectedFieldsRelationParser(
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
this.aggregateParser = new GraphqlQuerySelectedFieldsAggregateParser();
|
||||
}
|
||||
|
||||
parse(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
): GraphqlQuerySelectedFieldsResult {
|
||||
const accumulator: GraphqlQuerySelectedFieldsResult = {
|
||||
select: {},
|
||||
@@ -47,9 +57,8 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
if (this.isRootConnection(graphqlSelectedFields)) {
|
||||
this.parseConnectionField(
|
||||
graphqlSelectedFields,
|
||||
objectMetadataMapItem,
|
||||
flatObjectMetadata,
|
||||
accumulator,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
return accumulator;
|
||||
@@ -57,15 +66,15 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
|
||||
this.aggregateParser.parse(
|
||||
graphqlSelectedFields,
|
||||
objectMetadataMapItem,
|
||||
flatObjectMetadata,
|
||||
this.flatFieldMetadataMaps,
|
||||
accumulator,
|
||||
);
|
||||
|
||||
this.parseRecordFields(
|
||||
graphqlSelectedFields,
|
||||
objectMetadataMapItem,
|
||||
flatObjectMetadata,
|
||||
accumulator,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
return accumulator;
|
||||
@@ -74,15 +83,17 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
private parseRecordFields(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
): void {
|
||||
for (const fieldMetadata of Object.values(
|
||||
objectMetadataMapItem.fieldsById,
|
||||
)) {
|
||||
for (const fieldMetadataId of flatObjectMetadata.fieldMetadataIds) {
|
||||
const fieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: fieldMetadataId,
|
||||
flatEntityMaps: this.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (
|
||||
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.RELATION)
|
||||
isFlatFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)
|
||||
) {
|
||||
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||
|
||||
@@ -111,13 +122,15 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
}
|
||||
|
||||
if (
|
||||
isFieldMetadataEntityOfType(
|
||||
isFlatFieldMetadataOfType(
|
||||
fieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
this.flatObjectMetadataMaps.byId[
|
||||
fieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (
|
||||
!fieldMetadata.settings?.relationType ||
|
||||
@@ -182,24 +195,19 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
private parseConnectionField(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
): void {
|
||||
this.aggregateParser.parse(
|
||||
graphqlSelectedFields,
|
||||
objectMetadataMapItem,
|
||||
flatObjectMetadata,
|
||||
this.flatFieldMetadataMaps,
|
||||
accumulator,
|
||||
);
|
||||
|
||||
const node = graphqlSelectedFields.edges.node;
|
||||
|
||||
this.parseRecordFields(
|
||||
node,
|
||||
objectMetadataMapItem,
|
||||
accumulator,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
this.parseRecordFields(node, flatObjectMetadata, accumulator);
|
||||
}
|
||||
|
||||
private isRootConnection(
|
||||
@@ -210,7 +218,7 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
}
|
||||
|
||||
private parseCompositeField(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fieldValue: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
+20
-31
@@ -6,10 +6,6 @@ import {
|
||||
type ObjectRecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser';
|
||||
import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
||||
import {
|
||||
@@ -17,30 +13,35 @@ import {
|
||||
type GraphqlQuerySelectedFieldsResult,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
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 { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.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 { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
|
||||
export class GraphqlQueryParser {
|
||||
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
private objectMetadataMaps: ObjectMetadataMaps;
|
||||
private flatObjectMetadata: FlatObjectMetadata;
|
||||
private flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
private flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
private filterConditionParser: GraphqlQueryFilterConditionParser;
|
||||
private orderFieldParser: GraphqlQueryOrderFieldParser;
|
||||
|
||||
constructor(
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||
this.objectMetadataMaps = objectMetadataMaps;
|
||||
this.flatObjectMetadata = flatObjectMetadata;
|
||||
this.flatObjectMetadataMaps = flatObjectMetadataMaps;
|
||||
this.flatFieldMetadataMaps = flatFieldMetadataMaps;
|
||||
|
||||
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
|
||||
this.objectMetadataMapItem,
|
||||
this.flatObjectMetadata,
|
||||
this.flatFieldMetadataMaps,
|
||||
);
|
||||
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
||||
this.objectMetadataMapItem,
|
||||
this.objectMetadataMaps,
|
||||
this.flatObjectMetadata,
|
||||
this.flatObjectMetadataMaps,
|
||||
this.flatFieldMetadataMaps,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,26 +144,14 @@ export class GraphqlQueryParser {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
): GraphqlQuerySelectedFieldsResult {
|
||||
const objectMetadataMapItem = getObjectMetadataMapItemByNameSingular(
|
||||
this.objectMetadataMaps,
|
||||
this.objectMetadataMapItem.nameSingular,
|
||||
);
|
||||
|
||||
if (!objectMetadataMapItem) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Could not find object metadata for ${this.objectMetadataMapItem.nameSingular}`,
|
||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const selectedFieldsParser = new GraphqlQuerySelectedFieldsParser(
|
||||
this.objectMetadataMaps,
|
||||
this.flatObjectMetadataMaps,
|
||||
this.flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
return selectedFieldsParser.parse(
|
||||
graphqlSelectedFields,
|
||||
this.objectMetadataMapItem,
|
||||
this.objectMetadataMaps,
|
||||
this.flatObjectMetadata,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,21 +1,21 @@
|
||||
import { type ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
|
||||
import { type FirstDayOfTheWeek } from 'twenty-shared/utils';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export type GroupByRegularField = {
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
subFieldName?: string;
|
||||
};
|
||||
export type GroupByDateField = {
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
subFieldName?: string;
|
||||
dateGranularity: ObjectRecordGroupByDateGranularity;
|
||||
weekStartDay?: FirstDayOfTheWeek;
|
||||
};
|
||||
export type GroupByRelationField = {
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
nestedFieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
nestedFieldMetadata: FlatFieldMetadata;
|
||||
nestedSubFieldName?: string;
|
||||
dateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
weekStartDay?: FirstDayOfTheWeek;
|
||||
|
||||
+15
-10
@@ -7,8 +7,9 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type CommonGroupByOutputItem } from 'src/engine/api/common/types/common-group-by-output-item.type';
|
||||
import { type GroupByDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-definition.type';
|
||||
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';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
export const formatResultWithGroupByDimensionValues = async ({
|
||||
@@ -16,8 +17,9 @@ export const formatResultWithGroupByDimensionValues = async ({
|
||||
groupByDefinitions,
|
||||
aggregateFieldNames,
|
||||
recordsResult,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
processRecord,
|
||||
}: {
|
||||
groupsResult: Record<string, unknown>[];
|
||||
@@ -25,8 +27,9 @@ export const formatResultWithGroupByDimensionValues = async ({
|
||||
aggregateFieldNames: string[];
|
||||
processRecord?: (record: ObjectRecord) => Promise<ObjectRecord>;
|
||||
recordsResult?: Array<Record<string, unknown>>;
|
||||
objectMetadataItemWithFieldMaps?: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps?: ObjectMetadataMaps;
|
||||
flatObjectMetadata?: FlatObjectMetadata;
|
||||
flatObjectMetadataMaps?: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps?: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}): Promise<CommonGroupByOutputItem[]> => {
|
||||
const formattedResult: CommonGroupByOutputItem[] = [];
|
||||
|
||||
@@ -34,8 +37,9 @@ export const formatResultWithGroupByDimensionValues = async ({
|
||||
|
||||
if (isDefined(recordsResult)) {
|
||||
if (
|
||||
!isDefined(objectMetadataItemWithFieldMaps) ||
|
||||
!isDefined(objectMetadataMaps)
|
||||
!isDefined(flatObjectMetadata) ||
|
||||
!isDefined(flatObjectMetadataMaps) ||
|
||||
!isDefined(flatFieldMetadataMaps)
|
||||
) {
|
||||
throw new Error('Metadata are required to format result');
|
||||
}
|
||||
@@ -55,8 +59,9 @@ export const formatResultWithGroupByDimensionValues = async ({
|
||||
await processRecord(
|
||||
formatResult(
|
||||
record,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+22
-12
@@ -1,5 +1,5 @@
|
||||
import { isDefined } from 'class-validator';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type GroupByResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@@ -7,19 +7,28 @@ import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/g
|
||||
import { isGroupByDateFieldDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field-definition.util';
|
||||
import { parseGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/parse-group-by-relation-field.util';
|
||||
import { validateSingleKeyForGroupByOrThrow } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/validate-single-key-for-group-by-or-throw.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
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 { 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';
|
||||
|
||||
export const parseGroupByArgs = (
|
||||
args: GroupByResolverArgs,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
): GroupByField[] => {
|
||||
const groupByFieldNames = args.groupBy;
|
||||
|
||||
const groupByFields: GroupByField[] = [];
|
||||
|
||||
const { fieldIdByName, fieldIdByJoinColumnName } =
|
||||
buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
for (const fieldNames of groupByFieldNames) {
|
||||
validateSingleKeyForGroupByOrThrow({
|
||||
groupByKeys: Object.keys(fieldNames),
|
||||
@@ -29,17 +38,17 @@ export const parseGroupByArgs = (
|
||||
|
||||
for (const fieldName of Object.keys(fieldNames)) {
|
||||
const fieldMetadataId =
|
||||
objectMetadataItemWithFieldMaps.fieldIdByName[fieldName] ||
|
||||
objectMetadataItemWithFieldMaps.fieldIdByJoinColumnName[fieldName];
|
||||
const fieldMetadata =
|
||||
objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||
fieldIdByName[fieldName] || fieldIdByJoinColumnName[fieldName];
|
||||
const fieldMetadata = fieldMetadataId
|
||||
? flatFieldMetadataMaps.byId[fieldMetadataId]
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(fieldMetadata) || !isDefined(fieldMetadataId)) {
|
||||
throw new Error(`Unidentified field in groupBy: ${fieldName}`);
|
||||
}
|
||||
|
||||
const isGroupByRelationField =
|
||||
isFieldMetadataRelationOrMorphRelation(fieldMetadata) &&
|
||||
isMorphOrRelationFlatFieldMetadata(fieldMetadata) &&
|
||||
typeof fieldNames[fieldName] === 'object' &&
|
||||
fieldNames[fieldName] !== null &&
|
||||
!isGroupByDateFieldDefinition(fieldNames[fieldName]);
|
||||
@@ -50,7 +59,8 @@ export const parseGroupByArgs = (
|
||||
fieldNames,
|
||||
fieldName,
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
groupByFields,
|
||||
});
|
||||
|
||||
|
||||
+38
-23
@@ -1,6 +1,6 @@
|
||||
import { UserInputError } from 'apollo-server-core';
|
||||
import { isDefined } from 'class-validator';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
@@ -11,31 +11,41 @@ import { type DateFieldGroupByDefinition } from 'src/engine/api/graphql/graphql-
|
||||
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
|
||||
import { isGroupByDateFieldDefinition } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field-definition.util';
|
||||
import { validateSingleKeyForGroupByOrThrow } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/validate-single-key-for-group-by-or-throw.util';
|
||||
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 { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
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 { 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 { 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';
|
||||
|
||||
const getNestedFieldMetadataDetails = ({
|
||||
fieldNames,
|
||||
fieldName,
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}: {
|
||||
fieldNames: Record<string, unknown>;
|
||||
fieldName: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}) => {
|
||||
const nestedFieldGroupByDefinitions = fieldNames[fieldName] as
|
||||
| Record<string, boolean>
|
||||
| Record<string, CompositeFieldGroupByDefinition>
|
||||
| Record<string, DateFieldGroupByDefinition>;
|
||||
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
if (!isDefined(fieldMetadata.relationTargetObjectMetadataId)) {
|
||||
throw new UserInputError(
|
||||
`Relation target object metadata id not found for field ${fieldMetadata.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: fieldMetadata.relationTargetObjectMetadataId,
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const nestedFieldNames = Object.keys(nestedFieldGroupByDefinitions);
|
||||
|
||||
@@ -46,10 +56,13 @@ const getNestedFieldMetadataDetails = ({
|
||||
});
|
||||
|
||||
const nestedFieldName = nestedFieldNames[0];
|
||||
const nestedFieldMetadataId =
|
||||
targetObjectMetadata.fieldIdByName[nestedFieldName];
|
||||
const nestedFieldMetadata =
|
||||
targetObjectMetadata.fieldsById[nestedFieldMetadataId];
|
||||
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
targetObjectMetadata,
|
||||
);
|
||||
|
||||
const nestedFieldMetadataId = fieldIdByName[nestedFieldName];
|
||||
const nestedFieldMetadata = flatFieldMetadataMaps.byId[nestedFieldMetadataId];
|
||||
|
||||
if (!isDefined(nestedFieldMetadata) || !isDefined(nestedFieldMetadataId)) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
@@ -83,8 +96,8 @@ const handleNestedCompositeField = ({
|
||||
}: {
|
||||
nestedFieldGroupByDefinition: CompositeFieldGroupByDefinition;
|
||||
nestedFieldName: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
nestedFieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
nestedFieldMetadata: FlatFieldMetadata;
|
||||
groupByFields: GroupByField[];
|
||||
}) => {
|
||||
if (
|
||||
@@ -127,13 +140,15 @@ export const parseGroupByRelationField = ({
|
||||
fieldNames,
|
||||
fieldName,
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
groupByFields,
|
||||
}: {
|
||||
fieldNames: Record<string, unknown>;
|
||||
fieldName: string;
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
groupByFields: GroupByField[];
|
||||
}) => {
|
||||
const { nestedFieldGroupByDefinition, nestedFieldMetadata, nestedFieldName } =
|
||||
@@ -141,7 +156,8 @@ export const parseGroupByRelationField = ({
|
||||
fieldNames,
|
||||
fieldName,
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
// Handle date fields in nested relations
|
||||
@@ -150,8 +166,7 @@ export const parseGroupByRelationField = ({
|
||||
nestedFieldMetadata.type === FieldMetadataType.DATE_TIME) &&
|
||||
isGroupByDateFieldDefinition(nestedFieldGroupByDefinition)
|
||||
) {
|
||||
const dateFieldDefinition =
|
||||
nestedFieldGroupByDefinition as DateFieldGroupByDefinition;
|
||||
const dateFieldDefinition = nestedFieldGroupByDefinition;
|
||||
|
||||
groupByFields.push({
|
||||
fieldMetadata,
|
||||
|
||||
+32
-22
@@ -18,8 +18,9 @@ import { formatResultWithGroupByDimensionValues } from 'src/engine/api/graphql/g
|
||||
import { getGroupLimit } from 'src/engine/api/graphql/graphql-query-runner/group-by/utils/get-group-limit.util';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { 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 WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
@@ -66,16 +67,18 @@ export class GroupByWithRecordsService {
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
rolePermissionConfig,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
repository,
|
||||
} = queryRunnerContext;
|
||||
|
||||
const columnsToSelect = buildColumnsToSelect({
|
||||
select: selectedFieldsResult.select,
|
||||
relations: selectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const queryBuilderWithPartitionBy = this.addPartitionByToQueryBuilder({
|
||||
@@ -85,16 +88,18 @@ export class GroupByWithRecordsService {
|
||||
groupByDefinitions,
|
||||
repository,
|
||||
orderByForRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const recordsResult = await queryBuilderWithPartitionBy.getRawMany();
|
||||
|
||||
if (!isEmpty(selectedFieldsResult.relations)) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem: flatObjectMetadata,
|
||||
parentObjectRecords: recordsResult.flatMap((group) => group.records),
|
||||
parentObjectRecordsAggregatedValues: {},
|
||||
relations: selectedFieldsResult.relations,
|
||||
@@ -112,13 +117,15 @@ export class GroupByWithRecordsService {
|
||||
recordsResult,
|
||||
groupByDefinitions,
|
||||
aggregateFieldNames: Object.keys(selectedFieldsResult.aggregate),
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
processRecord: (record: ObjectRecord) =>
|
||||
this.commonResultGettersService.processRecord(
|
||||
record,
|
||||
objectMetadataItemWithFieldMaps.id,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
authContext.workspace.id,
|
||||
),
|
||||
});
|
||||
@@ -131,8 +138,9 @@ export class GroupByWithRecordsService {
|
||||
groupByDefinitions,
|
||||
repository,
|
||||
orderByForRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}: {
|
||||
queryBuilderForSubQuery: WorkspaceSelectQueryBuilder<ObjectLiteral>;
|
||||
columnsToSelect: Record<string, boolean>;
|
||||
@@ -140,8 +148,9 @@ export class GroupByWithRecordsService {
|
||||
groupByDefinitions: GroupByDefinition[];
|
||||
repository: WorkspaceRepository<ObjectLiteral>;
|
||||
orderByForRecords: ObjectRecordOrderBy;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}): WorkspaceSelectQueryBuilder<ObjectLiteral> {
|
||||
const groupByExpressions = groupByDefinitions
|
||||
.map((def) => def.expression)
|
||||
@@ -157,7 +166,7 @@ export class GroupByWithRecordsService {
|
||||
queryBuilderForSubQuery,
|
||||
);
|
||||
|
||||
const objectAlias = getObjectAlias(objectMetadataItemWithFieldMaps);
|
||||
const objectAlias = getObjectAlias(flatObjectMetadata);
|
||||
|
||||
const recordSelectWithAlias = Object.keys(columnsToSelect)
|
||||
.map((col) => `"${objectAlias}"."${col}" as "${SUB_QUERY_PREFIX}${col}"`)
|
||||
@@ -175,14 +184,15 @@ export class GroupByWithRecordsService {
|
||||
|
||||
if (!isEmpty(orderByForRecords)) {
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
graphqlQueryParser.applyOrderToBuilder(
|
||||
subQuery,
|
||||
orderByForRecords,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+34
-88
@@ -16,21 +16,29 @@ import {
|
||||
import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util';
|
||||
import { type AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
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 { 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 ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.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 { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { isPlainObject } from 'src/utils/is-plain-object';
|
||||
|
||||
// TODO: Refacto-common - Rename CommonRecordsToGraphqlConnectionHelper
|
||||
export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
private objectMetadataMaps: ObjectMetadataMaps;
|
||||
private flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
private flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
private objectIdByNameSingular: Record<string, string>;
|
||||
|
||||
constructor(objectMetadataMaps: ObjectMetadataMaps) {
|
||||
this.objectMetadataMaps = objectMetadataMaps;
|
||||
constructor(
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
objectIdByNameSingular: Record<string, string>,
|
||||
) {
|
||||
this.flatObjectMetadataMaps = flatObjectMetadataMaps;
|
||||
this.flatFieldMetadataMaps = flatFieldMetadataMaps;
|
||||
this.objectIdByNameSingular = objectIdByNameSingular;
|
||||
}
|
||||
|
||||
public createConnection<T extends ObjectRecord = ObjectRecord>({
|
||||
@@ -154,22 +162,22 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata = getObjectMetadataMapItemByNameSingular(
|
||||
this.objectMetadataMaps,
|
||||
objectName,
|
||||
);
|
||||
const objectMetadataId = this.objectIdByNameSingular[objectName];
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Object metadata not found for ${objectName}`,
|
||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: objectMetadataId,
|
||||
flatEntityMaps: this.flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const processedObjectRecord: Record<string, any> = {};
|
||||
|
||||
for (const fieldMetadata of Object.values(objectMetadata.fieldsById)) {
|
||||
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
|
||||
const fieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: fieldId,
|
||||
flatEntityMaps: this.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
const objectValue = objectRecord[fieldMetadata.name];
|
||||
|
||||
@@ -183,75 +191,13 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFieldMetadataTypeRelation(fieldMetadata)) {
|
||||
const fieldMetadataNameWithId = `${fieldMetadata.name}Id`;
|
||||
|
||||
if (isDefined(objectRecord[fieldMetadataNameWithId])) {
|
||||
processedObjectRecord[fieldMetadataNameWithId] =
|
||||
objectRecord[fieldMetadataNameWithId];
|
||||
}
|
||||
|
||||
const objectValue = objectRecord[fieldMetadata.name];
|
||||
|
||||
if (!isDefined(objectValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(objectValue)) {
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMaps,
|
||||
);
|
||||
|
||||
processedObjectRecord[fieldMetadata.name] = this.createConnection({
|
||||
objectRecords: objectValue,
|
||||
parentObjectRecord: objectRecord,
|
||||
objectRecordsAggregatedValues:
|
||||
objectRecordsAggregatedValues[fieldMetadata.name],
|
||||
selectedAggregatedFields:
|
||||
selectedAggregatedFields[fieldMetadata.name],
|
||||
objectName: targetObjectMetadata.nameSingular,
|
||||
take,
|
||||
totalCount:
|
||||
objectRecordsAggregatedValues[fieldMetadata.name]?.totalCount ??
|
||||
objectValue.length,
|
||||
order,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
depth: depth + 1,
|
||||
});
|
||||
} else if (isPlainObject(objectValue)) {
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMaps,
|
||||
);
|
||||
|
||||
processedObjectRecord[fieldMetadata.name] = this.processRecord({
|
||||
objectRecord: objectValue,
|
||||
objectRecordsAggregatedValues:
|
||||
objectRecordsAggregatedValues[fieldMetadata.name],
|
||||
selectedAggregatedFields:
|
||||
selectedAggregatedFields[fieldMetadata.name],
|
||||
objectName: targetObjectMetadata.nameSingular,
|
||||
take,
|
||||
totalCount,
|
||||
order,
|
||||
depth: depth + 1,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFieldMetadataTypeMorphRelation(fieldMetadata)) {
|
||||
if (isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
const targetObjectMetadata =
|
||||
this.objectMetadataMaps.byId[
|
||||
this.flatObjectMetadataMaps.byId[
|
||||
fieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (
|
||||
!fieldMetadata.settings?.relationType ||
|
||||
!isDefined(targetObjectMetadata)
|
||||
) {
|
||||
if (!isDefined(targetObjectMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -287,9 +233,9 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
depth: depth + 1,
|
||||
});
|
||||
} else if (isPlainObject(objectValue)) {
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
const targetObjectMetadataOrThrow = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMaps,
|
||||
this.flatObjectMetadataMaps,
|
||||
);
|
||||
|
||||
processedObjectRecord[fieldMetadata.name] = this.processRecord({
|
||||
@@ -298,7 +244,7 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
objectRecordsAggregatedValues[fieldMetadata.name],
|
||||
selectedAggregatedFields:
|
||||
selectedAggregatedFields[fieldMetadata.name],
|
||||
objectName: targetObjectMetadata.nameSingular,
|
||||
objectName: targetObjectMetadataOrThrow.nameSingular,
|
||||
take,
|
||||
totalCount,
|
||||
order,
|
||||
@@ -324,7 +270,7 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
}
|
||||
|
||||
private processCompositeField(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fieldValue: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
+62
-48
@@ -11,13 +11,14 @@ import {
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
import { getFieldMetadataFromGraphQLField } from 'src/engine/api/graphql/graphql-query-runner/utils/get-field-metadata-from-graphql-field.util';
|
||||
import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util';
|
||||
import { type AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
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 { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { 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 { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
@@ -28,7 +29,8 @@ export class ProcessNestedRelationsV2Helper {
|
||||
constructor() {}
|
||||
|
||||
public async processNestedRelations<T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues = {},
|
||||
@@ -40,8 +42,9 @@ export class ProcessNestedRelationsV2Helper {
|
||||
rolePermissionConfig,
|
||||
selectedFields,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
parentObjectMetadataItem: FlatObjectMetadata;
|
||||
parentObjectRecords: T[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parentObjectRecordsAggregatedValues?: Record<string, any>;
|
||||
@@ -57,7 +60,8 @@ export class ProcessNestedRelationsV2Helper {
|
||||
const processRelationTasks = Object.entries(relations).map(
|
||||
([sourceFieldName, nestedRelations]) =>
|
||||
this.processRelation({
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
@@ -79,7 +83,8 @@ export class ProcessNestedRelationsV2Helper {
|
||||
}
|
||||
|
||||
private async processRelation<T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
@@ -92,8 +97,9 @@ export class ProcessNestedRelationsV2Helper {
|
||||
rolePermissionConfig,
|
||||
selectedFields,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
parentObjectMetadataItem: FlatObjectMetadata;
|
||||
parentObjectRecords: T[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parentObjectRecordsAggregatedValues: Record<string, any>;
|
||||
@@ -106,12 +112,20 @@ export class ProcessNestedRelationsV2Helper {
|
||||
rolePermissionConfig?: RolePermissionConfig;
|
||||
selectedFields: Record<string, unknown>;
|
||||
}): Promise<void> {
|
||||
const sourceFieldMetadata = getFieldMetadataFromGraphQLField({
|
||||
objectMetadataItem: parentObjectMetadataItem,
|
||||
graphQLField: sourceFieldName,
|
||||
objectMetadataMaps,
|
||||
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
);
|
||||
|
||||
const sourceFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: fieldIdByName[sourceFieldName],
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!sourceFieldMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!isFieldMetadataEntityOfType(
|
||||
sourceFieldMetadata,
|
||||
@@ -136,7 +150,8 @@ export class ProcessNestedRelationsV2Helper {
|
||||
const relationType = sourceFieldMetadata.settings?.relationType;
|
||||
const { targetRelationName, targetObjectMetadata, targetRelation } =
|
||||
this.getTargetObjectMetadata({
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
sourceFieldName,
|
||||
});
|
||||
@@ -155,8 +170,9 @@ export class ProcessNestedRelationsV2Helper {
|
||||
const columnsToSelect = buildColumnsToSelect({
|
||||
select: selectedFields,
|
||||
relations: nestedRelations,
|
||||
objectMetadataItemWithFieldMaps: targetObjectMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata: targetObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
targetObjectQueryBuilder = targetObjectQueryBuilder.setFindOptions({
|
||||
@@ -208,23 +224,11 @@ export class ProcessNestedRelationsV2Helper {
|
||||
relationType,
|
||||
});
|
||||
|
||||
const targetObjectMetadataItemWithFieldsMaps =
|
||||
getObjectMetadataMapItemByNameSingular(
|
||||
objectMetadataMaps,
|
||||
targetObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
if (!targetObjectMetadataItemWithFieldsMaps) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Object ${targetObjectMetadata.nameSingular} not found`,
|
||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(nestedRelations).length > 0) {
|
||||
await this.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: targetObjectMetadataItemWithFieldsMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem: targetObjectMetadata,
|
||||
parentObjectRecords: relationResults as ObjectRecord[],
|
||||
parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult,
|
||||
relations: nestedRelations as Record<
|
||||
@@ -242,23 +246,36 @@ export class ProcessNestedRelationsV2Helper {
|
||||
}
|
||||
|
||||
private getTargetObjectMetadata({
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
sourceFieldName,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
parentObjectMetadataItem: FlatObjectMetadata;
|
||||
sourceFieldName: string;
|
||||
}) {
|
||||
const targetFieldMetadata = getFieldMetadataFromGraphQLField({
|
||||
objectMetadataItem: parentObjectMetadataItem,
|
||||
graphQLField: sourceFieldName,
|
||||
objectMetadataMaps,
|
||||
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
);
|
||||
|
||||
const targetFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: fieldIdByName[sourceFieldName],
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!targetFieldMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Field ${sourceFieldName} not found on object ${parentObjectMetadataItem.nameSingular}`,
|
||||
GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
targetFieldMetadata,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -272,14 +289,11 @@ export class ProcessNestedRelationsV2Helper {
|
||||
}
|
||||
|
||||
const targetRelation =
|
||||
objectMetadataMaps.byId[
|
||||
targetFieldMetadata.relationTargetObjectMetadataId
|
||||
]?.fieldsById[targetFieldMetadata.relationTargetFieldMetadataId];
|
||||
flatFieldMetadataMaps.byId[
|
||||
targetFieldMetadata.relationTargetFieldMetadataId
|
||||
];
|
||||
|
||||
const targetRelationName =
|
||||
objectMetadataMaps.byId[
|
||||
targetFieldMetadata.relationTargetObjectMetadataId
|
||||
]?.fieldsById[targetFieldMetadata.relationTargetFieldMetadataId]?.name;
|
||||
const targetRelationName = targetRelation?.name;
|
||||
|
||||
return { targetRelationName, targetObjectMetadata, targetRelation };
|
||||
}
|
||||
|
||||
+10
-6
@@ -6,8 +6,9 @@ import { type FindOptionsRelations, type ObjectLiteral } from 'typeorm';
|
||||
import { ProcessNestedRelationsV2Helper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper';
|
||||
import { type AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
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 { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
|
||||
@@ -18,7 +19,8 @@ export class ProcessNestedRelationsHelper {
|
||||
) {}
|
||||
|
||||
public async processNestedRelations<T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues = {},
|
||||
@@ -30,8 +32,9 @@ export class ProcessNestedRelationsHelper {
|
||||
rolePermissionConfig,
|
||||
selectedFields,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
parentObjectMetadataItem: FlatObjectMetadata;
|
||||
parentObjectRecords: T[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parentObjectRecordsAggregatedValues?: Record<string, any>;
|
||||
@@ -45,7 +48,8 @@ export class ProcessNestedRelationsHelper {
|
||||
selectedFields: Record<string, any>;
|
||||
}): Promise<void> {
|
||||
return this.processNestedRelationsV2Helper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
|
||||
+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(
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { type GraphQLResolveInfo } from 'graphql';
|
||||
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
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';
|
||||
|
||||
export interface WorkspaceQueryRunnerOptions {
|
||||
authContext: AuthContext;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
info: GraphQLResolveInfo;
|
||||
}
|
||||
+12
-62
@@ -1,9 +1,7 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryFailedError } from 'typeorm';
|
||||
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
@@ -14,64 +12,16 @@ interface PostgreSQLError extends QueryFailedError {
|
||||
}
|
||||
|
||||
export const handleDuplicateKeyError = (
|
||||
error: PostgreSQLError,
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||
_error: PostgreSQLError,
|
||||
_objectMetadata: FlatObjectMetadata,
|
||||
) => {
|
||||
const indexNameMatch = error.message.match(/"([^"]+)"/);
|
||||
|
||||
const duplicatedValues = error?.detail?.match(/=\(([^)]+)\)/)?.[1];
|
||||
|
||||
if (indexNameMatch) {
|
||||
const indexName = indexNameMatch[1];
|
||||
|
||||
const deletedAtFieldMetadataId = objectMetadata.fieldIdByName['deletedAt'];
|
||||
|
||||
const affectedColumns = objectMetadata.indexMetadatas
|
||||
.find((index) => index.name === indexName)
|
||||
?.indexFieldMetadatas?.filter(
|
||||
(field) => field.fieldMetadataId !== deletedAtFieldMetadataId,
|
||||
)
|
||||
.map((indexField) => {
|
||||
const fieldMetadata =
|
||||
objectMetadata.fieldsById[indexField.fieldMetadataId];
|
||||
|
||||
return fieldMetadata?.label;
|
||||
});
|
||||
|
||||
if (!isDefined(affectedColumns)) {
|
||||
throw new TwentyORMException(
|
||||
`A duplicate entry was detected`,
|
||||
TwentyORMExceptionCode.DUPLICATE_ENTRY_DETECTED,
|
||||
{
|
||||
userFriendlyMessage: msg`This record already exists. Please check your data and try again.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const columnNames = affectedColumns.join(', ');
|
||||
|
||||
if (affectedColumns?.length === 1) {
|
||||
const fieldName = columnNames.toLowerCase();
|
||||
|
||||
throw new UserInputError(
|
||||
`Duplicate ${columnNames} ${duplicatedValues ? `with value ${duplicatedValues}` : ''}. Please set a unique one.`,
|
||||
{
|
||||
userFriendlyMessage: duplicatedValues
|
||||
? msg`This ${fieldName} with value ${duplicatedValues} is already taken. Please choose a different value.`
|
||||
: msg`This ${fieldName} is already taken. Please choose a different value.`,
|
||||
isExpected: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const fieldNames = columnNames.toLowerCase();
|
||||
|
||||
throw new TwentyORMException(
|
||||
`A duplicate entry was detected. The combination of ${columnNames} must be unique.`,
|
||||
TwentyORMExceptionCode.DUPLICATE_ENTRY_DETECTED,
|
||||
{
|
||||
userFriendlyMessage: msg`This combination of ${fieldNames} already exists. Please use different values.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
// Since we no longer have indexMetadatas in FlatObjectMetadata,
|
||||
// we provide a generic error message
|
||||
throw new TwentyORMException(
|
||||
`A duplicate entry was detected`,
|
||||
TwentyORMExceptionCode.DUPLICATE_ENTRY_DETECTED,
|
||||
{
|
||||
userFriendlyMessage: msg`This record already exists. Please check your data and try again.`,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
+4
-3
@@ -41,14 +41,15 @@ export class CreateManyResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return records.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
|
||||
+4
-3
@@ -40,13 +40,14 @@ export class CreateOneResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
+4
-3
@@ -41,14 +41,15 @@ export class DeleteManyResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return records.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
|
||||
+4
-3
@@ -40,13 +40,14 @@ export class DeleteOneResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
+4
-3
@@ -41,14 +41,15 @@ export class DestroyManyResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return records.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
|
||||
+4
-3
@@ -40,13 +40,14 @@ export class DestroyOneResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
+4
-3
@@ -42,14 +42,15 @@ export class FindDuplicatesResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return paginatedDuplicates.map((duplicate) =>
|
||||
typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: duplicate.records,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: duplicate.records.length,
|
||||
totalCount: duplicate.totalCount,
|
||||
order: [{ id: OrderByDirection.AscNullsFirst }],
|
||||
|
||||
+4
-3
@@ -47,15 +47,16 @@ export class FindManyResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: records,
|
||||
objectRecordsAggregatedValues: aggregatedValues,
|
||||
selectedAggregatedFields: selectedFieldsResult.aggregate,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: args.first ?? args.last ?? QUERY_MAX_RECORDS,
|
||||
totalCount,
|
||||
order: args.orderBy,
|
||||
|
||||
+4
-3
@@ -40,13 +40,14 @@ export class FindOneResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
+4
-3
@@ -40,7 +40,9 @@ export class GroupByResolverFactory
|
||||
try {
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
const results = await this.commonGroupByQueryRunnerService.execute(
|
||||
@@ -51,8 +53,7 @@ export class GroupByResolverFactory
|
||||
const formattedResults = results.map((group) => {
|
||||
const formattedRecords = typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: group.records ?? [],
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
objectRecordsAggregatedValues: {},
|
||||
selectedAggregatedFields: {},
|
||||
take: group.records?.length || 0,
|
||||
|
||||
+4
-3
@@ -40,13 +40,14 @@ export class MergeManyResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
+4
-3
@@ -41,14 +41,15 @@ export class RestoreManyResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return records.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
|
||||
+4
-3
@@ -40,13 +40,14 @@ export class RestoreOneResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
+4
-3
@@ -41,14 +41,15 @@ export class UpdateManyResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return records.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
|
||||
+4
-3
@@ -40,13 +40,14 @@ export class UpdateOneResolverFactory
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
internalContext.flatObjectMetadataMaps,
|
||||
internalContext.flatFieldMetadataMaps,
|
||||
internalContext.objectIdByNameSingular,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectName: internalContext.flatObjectMetadata.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
+23
-32
@@ -17,11 +17,10 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { 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 { getResolverName } from 'src/engine/utils/get-resolver-name.util';
|
||||
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
|
||||
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
|
||||
|
||||
import { CreateManyResolverFactory } from './factories/create-many-resolver.factory';
|
||||
import { CreateOneResolverFactory } from './factories/create-one-resolver.factory';
|
||||
@@ -57,12 +56,13 @@ export class WorkspaceResolverFactory {
|
||||
private readonly mergeManyResolverFactory: MergeManyResolverFactory,
|
||||
private readonly groupByResolverFactory: GroupByResolverFactory,
|
||||
private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async create(
|
||||
authContext: AuthContext,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
objectIdByNameSingular: Record<string, string>,
|
||||
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
|
||||
): Promise<IResolvers> {
|
||||
const factories = new Map<
|
||||
@@ -99,30 +99,17 @@ export class WorkspaceResolverFactory {
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceFeatureFlagsMap =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||
|
||||
for (const objectMetadata of Object.values(objectMetadataMaps.byId).filter(
|
||||
isDefined,
|
||||
)) {
|
||||
if (
|
||||
shouldExcludeFromWorkspaceApi(
|
||||
objectMetadata,
|
||||
standardObjectMetadataDefinitions,
|
||||
workspaceFeatureFlagsMap,
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const flatObjectMetadata of Object.values(
|
||||
flatObjectMetadataMaps.byId,
|
||||
).filter(isDefined)) {
|
||||
// Generate query resolvers
|
||||
for (const methodName of workspaceResolverBuilderMethods.queries) {
|
||||
const resolverName = getResolverName(objectMetadata, methodName);
|
||||
const resolverName = getResolverName(flatObjectMetadata, methodName);
|
||||
const resolverFactory = factories.get(methodName);
|
||||
|
||||
if (!resolverFactory) {
|
||||
this.logger.error(`Unknown query resolver type: ${methodName}`, {
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
methodName,
|
||||
resolverName,
|
||||
});
|
||||
@@ -132,27 +119,29 @@ export class WorkspaceResolverFactory {
|
||||
|
||||
if (
|
||||
this.workspaceResolverBuilderService.shouldBuildResolver(
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
methodName,
|
||||
)
|
||||
) {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
resolvers.Query[resolverName] = resolverFactory.create({
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps: objectMetadata,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectIdByNameSingular,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate mutation resolvers
|
||||
for (const methodName of workspaceResolverBuilderMethods.mutations) {
|
||||
const resolverName = getResolverName(objectMetadata, methodName);
|
||||
const resolverName = getResolverName(flatObjectMetadata, methodName);
|
||||
const resolverFactory = factories.get(methodName);
|
||||
|
||||
if (!resolverFactory) {
|
||||
this.logger.error(`Unknown mutation resolver type: ${methodName}`, {
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
methodName,
|
||||
resolverName,
|
||||
});
|
||||
@@ -162,15 +151,17 @@ export class WorkspaceResolverFactory {
|
||||
|
||||
if (
|
||||
this.workspaceResolverBuilderService.shouldBuildResolver(
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
methodName,
|
||||
)
|
||||
) {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
resolvers.Mutation[resolverName] = resolverFactory.create({
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps: objectMetadata,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectIdByNameSingular,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -9,9 +9,9 @@ import {
|
||||
type FieldMetadataComplexOption,
|
||||
type FieldMetadataDefaultOption,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { transformEnumValue } from 'src/engine/utils/transform-enum-value';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@@ -23,25 +23,28 @@ export class EnumFieldMetadataGqlEnumTypeGenerator {
|
||||
|
||||
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
public buildAndStore(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
) {
|
||||
for (const fieldMetadata of fields) {
|
||||
if (!isEnumFieldMetadataType(fieldMetadata.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.gqlTypesStorage.addGqlType(
|
||||
computeEnumFieldGqlTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
fieldMetadata.name,
|
||||
),
|
||||
this.generateEnum(objectMetadata.nameSingular, fieldMetadata),
|
||||
this.generateEnum(flatObjectMetadata.nameSingular, fieldMetadata),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private generateEnum(
|
||||
objectName: string,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
): GraphQLEnumType {
|
||||
// FixMe: It's a hack until Typescript get fixed on union types for reduce function
|
||||
// https://github.com/microsoft/TypeScript/issues/36390
|
||||
|
||||
+60
-27
@@ -4,6 +4,7 @@ import {
|
||||
type CompositeType,
|
||||
compositeTypeDefinitions,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CompositeFieldMetadataGqlEnumTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/composite-field-metadata-gql-enum-type.generator';
|
||||
import { EnumFieldMetadataGqlEnumTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/enum-field-metadata-gql-enum-type.generator';
|
||||
@@ -19,9 +20,10 @@ import { ObjectMetadataGqlObjectTypeGenerator } from 'src/engine/api/graphql/wor
|
||||
import { ObjectMetadataWithRelationsGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-with-relations-gql-object-type.generator';
|
||||
import { MutationTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator';
|
||||
import { QueryTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator';
|
||||
import { objectContainsMorphRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-morph-relation-field.util';
|
||||
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
|
||||
|
||||
@Injectable()
|
||||
export class GqlTypeGenerator {
|
||||
@@ -44,14 +46,14 @@ export class GqlTypeGenerator {
|
||||
private readonly mutationTypeGenerator: MutationTypeGenerator,
|
||||
) {}
|
||||
|
||||
async buildAndStore(objectMetadataCollection: ObjectMetadataEntity[]) {
|
||||
async buildAndStore(context: SchemaGenerationContext) {
|
||||
const compositeTypeCollection = [...compositeTypeDefinitions.values()];
|
||||
|
||||
this.buildAndStoreCompositeFieldMetadataGqlTypes(compositeTypeCollection);
|
||||
this.buildAndStoreDateFieldMetadataGroupByGqlTypes();
|
||||
this.buildAndStoreObjectMetadataGqlTypes(objectMetadataCollection);
|
||||
await this.queryTypeGenerator.buildAndStore(objectMetadataCollection);
|
||||
this.mutationTypeGenerator.buildAndStore(objectMetadataCollection);
|
||||
this.buildAndStoreObjectMetadataGqlTypes(context);
|
||||
await this.queryTypeGenerator.buildAndStore(context);
|
||||
this.mutationTypeGenerator.buildAndStore(context);
|
||||
}
|
||||
|
||||
private buildAndStoreCompositeFieldMetadataGqlTypes(
|
||||
@@ -75,36 +77,67 @@ export class GqlTypeGenerator {
|
||||
}
|
||||
|
||||
private buildAndStoreObjectMetadataGqlTypes(
|
||||
dynamicObjectMetadataCollection: ObjectMetadataEntity[],
|
||||
context: SchemaGenerationContext,
|
||||
) {
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps } = context;
|
||||
const objectMetadataCollection = Object.values(
|
||||
flatObjectMetadataMaps.byId,
|
||||
).filter(isDefined);
|
||||
|
||||
this.logger.log(
|
||||
`Generating metadata objects: [${dynamicObjectMetadataCollection
|
||||
`Generating metadata objects: [${objectMetadataCollection
|
||||
.map((object) => object.nameSingular)
|
||||
.join(', ')}]`,
|
||||
);
|
||||
|
||||
for (const objectMetadata of dynamicObjectMetadataCollection) {
|
||||
this.enumFieldMetadataGqlEnumTypeGenerator.buildAndStore(objectMetadata);
|
||||
this.objectMetadataGqlObjectTypeGenerator.buildAndStore(objectMetadata);
|
||||
this.edgeGqlObjectTypeGenerator.buildAndStore(objectMetadata);
|
||||
this.connectionGqlObjectTypeGenerator.buildAndStore(objectMetadata);
|
||||
this.groupByConnectionGqlObjectTypeGenerator.buildAndStore(
|
||||
objectMetadata,
|
||||
);
|
||||
this.relationConnectGqlInputTypeGenerator.buildAndStore(objectMetadata);
|
||||
this.objectMetadataGqlInputTypeGenerator.buildAndStore(
|
||||
objectMetadata,
|
||||
dynamicObjectMetadataCollection,
|
||||
for (const flatObjectMetadata of objectMetadataCollection) {
|
||||
const fields = getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
if (
|
||||
objectContainsRelationField(objectMetadata) ||
|
||||
objectContainsMorphRelationField(objectMetadata)
|
||||
)
|
||||
this.enumFieldMetadataGqlEnumTypeGenerator.buildAndStore(
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
);
|
||||
this.objectMetadataGqlObjectTypeGenerator.buildAndStore(
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
);
|
||||
this.edgeGqlObjectTypeGenerator.buildAndStore(flatObjectMetadata);
|
||||
this.connectionGqlObjectTypeGenerator.buildAndStore(
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
);
|
||||
this.groupByConnectionGqlObjectTypeGenerator.buildAndStore(
|
||||
flatObjectMetadata,
|
||||
);
|
||||
this.relationConnectGqlInputTypeGenerator.buildAndStore(
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
);
|
||||
this.objectMetadataGqlInputTypeGenerator.buildAndStore(
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
);
|
||||
|
||||
if (this.objectContainsRelationOrMorphField(fields)) {
|
||||
this.objectMetadataWithRelationsGqlObjectTypeGenerator.buildAndStore(
|
||||
objectMetadata,
|
||||
dynamicObjectMetadataCollection,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private objectContainsRelationOrMorphField(
|
||||
fields: FlatFieldMetadata[],
|
||||
): boolean {
|
||||
return fields.some((field) =>
|
||||
isMorphOrRelationFieldMetadataType(field.type),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-19
@@ -16,15 +16,16 @@ import {
|
||||
TypeOptions,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
|
||||
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
|
||||
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { 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 { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -38,15 +39,20 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
public buildAndStore(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
_context: SchemaGenerationContext,
|
||||
) {
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Create.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Create.toString()}Input`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () =>
|
||||
this.generateFields(flatObjectMetadata.nameSingular, fields),
|
||||
}) as GraphQLInputObjectType;
|
||||
|
||||
const key = computeObjectMetadataInputTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
GqlInputTypeDefinitionKind.Create,
|
||||
);
|
||||
|
||||
@@ -54,11 +60,12 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fields: FlatFieldMetadata[],
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
for (const fieldMetadata of fields) {
|
||||
const typeOptions = computeFieldInputTypeOptions(
|
||||
fieldMetadata,
|
||||
GqlInputTypeDefinitionKind.Create,
|
||||
@@ -66,24 +73,26 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
|
||||
|
||||
let generatedFields;
|
||||
|
||||
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
if (isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
const relationFieldMetadata = fieldMetadata;
|
||||
|
||||
generatedFields = {
|
||||
...this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldCreateOrUpdateInputType(
|
||||
{
|
||||
fieldMetadata,
|
||||
fieldMetadata: relationFieldMetadata,
|
||||
typeOptions,
|
||||
},
|
||||
),
|
||||
...this.relationFieldMetadataGqlInputTypeGenerator.generateConnectRelationFieldInputType(
|
||||
{
|
||||
fieldMetadata,
|
||||
fieldMetadata: relationFieldMetadata,
|
||||
typeOptions,
|
||||
},
|
||||
),
|
||||
};
|
||||
} else if (isEnumFieldMetadataType(fieldMetadata.type)) {
|
||||
generatedFields = this.generateEnumFieldCreateInputType(
|
||||
objectMetadata,
|
||||
objectNameSingular,
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
);
|
||||
@@ -106,12 +115,12 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateEnumFieldCreateInputType(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const key = computeEnumFieldGqlTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
objectNameSingular,
|
||||
fieldMetadata.name,
|
||||
);
|
||||
|
||||
@@ -141,7 +150,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateCompositeFieldCreateInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const key = computeCompositeFieldInputTypeKey(
|
||||
@@ -175,7 +184,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateAtomicFieldCreateInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const type = this.typeMapperService.mapToScalarType(
|
||||
|
||||
+27
-19
@@ -21,11 +21,11 @@ import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/worksp
|
||||
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { createGqlEnumFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/create-gql-enum-filter-type.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { 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 { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -39,15 +39,19 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
public buildAndStore(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
) {
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Filter.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata, inputType),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Filter.toString()}Input`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () =>
|
||||
this.generateFields(flatObjectMetadata.nameSingular, fields, inputType),
|
||||
}) as GraphQLInputObjectType;
|
||||
|
||||
const key = computeObjectMetadataInputTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
GqlInputTypeDefinitionKind.Filter,
|
||||
);
|
||||
|
||||
@@ -55,7 +59,8 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fields: FlatFieldMetadata[],
|
||||
inputType: GraphQLInputObjectType,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const andOrType = this.typeMapperService.applyTypeOptions(inputType, {
|
||||
@@ -65,17 +70,20 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
|
||||
});
|
||||
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
fieldMetadata.isNullable = true;
|
||||
for (const fieldMetadata of fields) {
|
||||
const modifiedFieldMetadata = {
|
||||
...fieldMetadata,
|
||||
isNullable: true,
|
||||
};
|
||||
|
||||
const typeOptions = computeFieldInputTypeOptions(
|
||||
fieldMetadata,
|
||||
modifiedFieldMetadata,
|
||||
GqlInputTypeDefinitionKind.Filter,
|
||||
);
|
||||
|
||||
let generatedFields;
|
||||
|
||||
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
if (isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
generatedFields =
|
||||
this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldFilterInputType(
|
||||
{
|
||||
@@ -85,7 +93,7 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
|
||||
);
|
||||
} else if (isEnumFieldMetadataType(fieldMetadata.type)) {
|
||||
generatedFields = this.generateEnumFieldFilterInputType(
|
||||
objectMetadata,
|
||||
objectNameSingular,
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
);
|
||||
@@ -120,12 +128,12 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateEnumFieldFilterInputType(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const key = computeEnumFieldGqlTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
objectNameSingular,
|
||||
fieldMetadata.name,
|
||||
);
|
||||
|
||||
@@ -152,7 +160,7 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateCompositeFieldFilterInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const key = computeCompositeFieldInputTypeKey(
|
||||
@@ -181,7 +189,7 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateAtomicFieldFilterInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const type = this.typeMapperService.mapToFilterType(
|
||||
|
||||
+18
-28
@@ -16,14 +16,13 @@ import { GROUP_BY_DATE_GRANULARITY_INPUT_KEY } from 'src/engine/api/graphql/work
|
||||
import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator';
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -31,8 +30,6 @@ export class ObjectMetadataGroupByGqlInputTypeGenerator {
|
||||
private readonly logger = new Logger(
|
||||
ObjectMetadataGroupByGqlInputTypeGenerator.name,
|
||||
);
|
||||
private objectMetadataCollection: ObjectMetadataEntity[] = [];
|
||||
private objectMetadataMaps: ObjectMetadataMaps | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
@@ -41,24 +38,18 @@ export class ObjectMetadataGroupByGqlInputTypeGenerator {
|
||||
) {}
|
||||
|
||||
public buildAndStore(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectMetadataCollection?: ObjectMetadataEntity[],
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
) {
|
||||
if (isDefined(objectMetadataCollection)) {
|
||||
this.objectMetadataCollection = objectMetadataCollection;
|
||||
this.objectMetadataMaps = generateObjectMetadataMaps(
|
||||
objectMetadataCollection,
|
||||
);
|
||||
}
|
||||
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.GroupBy.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.GroupBy.toString()}Input`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () => this.generateFields(fields, context),
|
||||
}) as GraphQLInputObjectType;
|
||||
|
||||
const key = computeObjectMetadataInputTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
GqlInputTypeDefinitionKind.GroupBy,
|
||||
);
|
||||
|
||||
@@ -66,17 +57,16 @@ export class ObjectMetadataGroupByGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
const generatedField = isFieldMetadataRelationOrMorphRelation(
|
||||
fieldMetadata,
|
||||
)
|
||||
for (const fieldMetadata of fields) {
|
||||
const generatedField = isMorphOrRelationFlatFieldMetadata(fieldMetadata)
|
||||
? this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldGroupByInputType(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMaps,
|
||||
context,
|
||||
)
|
||||
: this.generateField(fieldMetadata);
|
||||
|
||||
@@ -86,7 +76,7 @@ export class ObjectMetadataGroupByGqlInputTypeGenerator {
|
||||
return allGeneratedFields;
|
||||
}
|
||||
|
||||
private generateField(fieldMetadata: FieldMetadataEntity) {
|
||||
private generateField(fieldMetadata: FlatFieldMetadata) {
|
||||
if (isCompositeFieldMetadataType(fieldMetadata.type))
|
||||
return this.generateCompositeFieldGroupByInputType(fieldMetadata);
|
||||
|
||||
@@ -124,7 +114,7 @@ export class ObjectMetadataGroupByGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateCompositeFieldGroupByInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
) {
|
||||
const key = computeCompositeFieldInputTypeKey(
|
||||
fieldMetadata.type,
|
||||
|
||||
+22
-11
@@ -6,7 +6,9 @@ import { ObjectMetadataGroupByGqlInputTypeGenerator } from 'src/engine/api/graph
|
||||
import { ObjectMetadataOrderByGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-gql-input-type.generator';
|
||||
import { ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-with-group-by-gql-input-type.generator';
|
||||
import { ObjectMetadataUpdateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.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';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataGqlInputTypeGenerator {
|
||||
@@ -20,28 +22,37 @@ export class ObjectMetadataGqlInputTypeGenerator {
|
||||
) {}
|
||||
|
||||
public buildAndStore(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectMetadataCollection?: ObjectMetadataEntity[],
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
) {
|
||||
this.objectMetadataCreateGqlInputTypeGenerator.buildAndStore(
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
);
|
||||
this.objectMetadataUpdateGqlInputTypeGenerator.buildAndStore(
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
);
|
||||
this.objectMetadataFilterGqlInputTypeGenerator.buildAndStore(
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
);
|
||||
this.objectMetadataOrderByGqlInputTypeGenerator.buildAndStore({
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
});
|
||||
this.objectMetadataOrderByWithGroupByGqlInputTypeGenerator.buildAndStore({
|
||||
objectMetadata,
|
||||
objectMetadataCollection,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
});
|
||||
this.objectMetadataGroupByGqlInputTypeGenerator.buildAndStore(
|
||||
objectMetadata,
|
||||
objectMetadataCollection,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-15
@@ -12,13 +12,12 @@ import {
|
||||
TypeOptions,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
|
||||
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataOrderByBaseGenerator {
|
||||
@@ -29,35 +28,38 @@ export class ObjectMetadataOrderByBaseGenerator {
|
||||
) {}
|
||||
|
||||
public generateFields({
|
||||
objectMetadata,
|
||||
fields,
|
||||
logger,
|
||||
orderByDateGranularity,
|
||||
objectMetadataMaps,
|
||||
context,
|
||||
}: {
|
||||
objectMetadata: ObjectMetadataEntity;
|
||||
fields: FlatFieldMetadata[];
|
||||
logger: Logger;
|
||||
orderByDateGranularity?: boolean;
|
||||
objectMetadataMaps?: ObjectMetadataMaps;
|
||||
context?: SchemaGenerationContext;
|
||||
}): GraphQLInputFieldConfigMap {
|
||||
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
fieldMetadata.isNullable = true;
|
||||
for (const fieldMetadata of fields) {
|
||||
const modifiedFieldMetadata = {
|
||||
...fieldMetadata,
|
||||
isNullable: true,
|
||||
};
|
||||
|
||||
const typeOptions = computeFieldInputTypeOptions(
|
||||
fieldMetadata,
|
||||
modifiedFieldMetadata,
|
||||
GqlInputTypeDefinitionKind.OrderBy,
|
||||
);
|
||||
|
||||
let generatedFields;
|
||||
|
||||
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
if (isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
generatedFields =
|
||||
this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldOrderByInputType(
|
||||
{
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
objectMetadataMaps,
|
||||
context,
|
||||
},
|
||||
);
|
||||
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
@@ -82,7 +84,7 @@ export class ObjectMetadataOrderByBaseGenerator {
|
||||
}
|
||||
|
||||
private generateCompositeFieldOrderByInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
logger: Logger,
|
||||
) {
|
||||
@@ -117,7 +119,7 @@ export class ObjectMetadataOrderByBaseGenerator {
|
||||
logger,
|
||||
orderByDateGranularity,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
typeOptions: TypeOptions;
|
||||
logger: Logger;
|
||||
orderByDateGranularity?: boolean;
|
||||
|
||||
+10
-7
@@ -6,7 +6,8 @@ import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-sch
|
||||
import { ObjectMetadataOrderByBaseGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-base.generator';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -20,22 +21,24 @@ export class ObjectMetadataOrderByGqlInputTypeGenerator {
|
||||
) {}
|
||||
|
||||
public buildAndStore({
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
}: {
|
||||
objectMetadata: ObjectMetadataEntity;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
fields: FlatFieldMetadata[];
|
||||
}) {
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.OrderBy.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.OrderBy.toString()}Input`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () =>
|
||||
this.objectMetadataOrderByBaseGenerator.generateFields({
|
||||
objectMetadata,
|
||||
fields,
|
||||
logger: this.logger,
|
||||
}),
|
||||
}) as GraphQLInputObjectType;
|
||||
|
||||
const key = computeObjectMetadataInputTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
GqlInputTypeDefinitionKind.OrderBy,
|
||||
);
|
||||
|
||||
|
||||
+43
-37
@@ -15,16 +15,15 @@ import {
|
||||
TypeOptions,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
|
||||
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { getAvailableAggregationsFromObjectFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -39,29 +38,24 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator {
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
private objectMetadataMaps?: ObjectMetadataMaps;
|
||||
|
||||
public buildAndStore({
|
||||
objectMetadata,
|
||||
objectMetadataCollection,
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
}: {
|
||||
objectMetadata: ObjectMetadataEntity;
|
||||
objectMetadataCollection?: ObjectMetadataEntity[];
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
fields: FlatFieldMetadata[];
|
||||
context: SchemaGenerationContext;
|
||||
}) {
|
||||
if (isDefined(objectMetadataCollection)) {
|
||||
this.objectMetadataMaps = generateObjectMetadataMaps(
|
||||
objectMetadataCollection,
|
||||
);
|
||||
}
|
||||
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.OrderByWithGroupBy.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.OrderByWithGroupBy.toString()}Input`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () =>
|
||||
this.generateFields(flatObjectMetadata.nameSingular, fields, context),
|
||||
}) as GraphQLInputObjectType;
|
||||
|
||||
const key = computeObjectMetadataInputTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
GqlInputTypeDefinitionKind.OrderByWithGroupBy,
|
||||
);
|
||||
|
||||
@@ -69,36 +63,47 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
return {
|
||||
...this.generateOrderByOnAggregateFields(objectMetadata),
|
||||
...this.generateOrderByOnDimensionValuesFields(objectMetadata),
|
||||
...this.generateOrderByOnAggregateFields(
|
||||
objectNameSingular,
|
||||
fields,
|
||||
context,
|
||||
),
|
||||
...this.generateOrderByOnDimensionValuesFields(fields, context),
|
||||
};
|
||||
}
|
||||
|
||||
private generateOrderByOnAggregateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const allAggregatedFields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
fieldMetadata.isNullable = true;
|
||||
for (const fieldMetadata of fields) {
|
||||
const modifiedFieldMetadata = {
|
||||
...fieldMetadata,
|
||||
isNullable: true,
|
||||
};
|
||||
|
||||
const typeOptions = computeFieldInputTypeOptions(
|
||||
fieldMetadata,
|
||||
modifiedFieldMetadata,
|
||||
GqlInputTypeDefinitionKind.OrderBy,
|
||||
);
|
||||
|
||||
let generatedFields;
|
||||
|
||||
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
if (isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
generatedFields =
|
||||
this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldOrderByInputType(
|
||||
{
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
objectMetadataMaps: this.objectMetadataMaps,
|
||||
context,
|
||||
},
|
||||
);
|
||||
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
@@ -117,7 +122,7 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
const aggregateInputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.OrderByWithGroupBy.toString()}AggregateInput`,
|
||||
name: `${pascalCase(objectNameSingular)}${GqlInputTypeDefinitionKind.OrderByWithGroupBy.toString()}AggregateInput`,
|
||||
description: 'Aggregate-based ordering',
|
||||
fields: () => allAggregatedFields,
|
||||
}) as GraphQLInputObjectType;
|
||||
@@ -131,18 +136,19 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateOrderByOnDimensionValuesFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
) {
|
||||
return this.objectMetadataOrderByBaseGenerator.generateFields({
|
||||
objectMetadata,
|
||||
fields,
|
||||
logger: this.logger,
|
||||
orderByDateGranularity: true,
|
||||
objectMetadataMaps: this.objectMetadataMaps,
|
||||
context,
|
||||
});
|
||||
}
|
||||
|
||||
private generateCompositeFieldOrderByInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const key = computeCompositeFieldInputTypeKey(
|
||||
@@ -169,7 +175,7 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateAtomicFieldOrderByInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
typeOptions: TypeOptions,
|
||||
) {
|
||||
const orderByType = this.typeMapperService.mapToOrderByType(
|
||||
@@ -193,7 +199,7 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateAggregateFieldOrderByInputType(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
) {
|
||||
const aggregations = getAvailableAggregationsFromObjectFields([
|
||||
fieldMetadata,
|
||||
|
||||
+59
-26
@@ -8,15 +8,16 @@ import {
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
import { RELATION_NESTED_QUERY_KEYWORDS } from 'twenty-shared/constants';
|
||||
import { getUniqueConstraintsFields } from 'twenty-shared/utils';
|
||||
import { compositeTypeDefinitions } from 'twenty-shared/types';
|
||||
import { getUniqueConstraintsFields, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeRelationConnectInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util';
|
||||
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 ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -26,44 +27,76 @@ export class RelationConnectGqlInputTypeGenerator {
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
const relationConnectGqlInputType =
|
||||
this.generateRelationConnectInputType(objectMetadata);
|
||||
const key = computeRelationConnectInputTypeKey(objectMetadata.id);
|
||||
public buildAndStore(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
) {
|
||||
const relationConnectGqlInputType = this.generateRelationConnectInputType(
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
);
|
||||
const key = computeRelationConnectInputTypeKey(flatObjectMetadata.id);
|
||||
|
||||
this.gqlTypesStorage.addGqlType(key, relationConnectGqlInputType);
|
||||
}
|
||||
|
||||
private generateRelationConnectInputType(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
): GraphQLInputObjectType {
|
||||
return new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}RelationInput`,
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}RelationInput`,
|
||||
fields: () => ({
|
||||
[RELATION_NESTED_QUERY_KEYWORDS.CONNECT]: {
|
||||
type: new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}ConnectInput`,
|
||||
fields: this.generateRelationWhereInputType(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}ConnectInput`,
|
||||
fields: this.generateRelationWhereInputType(
|
||||
flatObjectMetadata,
|
||||
fields,
|
||||
context,
|
||||
),
|
||||
}),
|
||||
description: `Connect to a ${objectMetadata.nameSingular} record`,
|
||||
description: `Connect to a ${flatObjectMetadata.nameSingular} record`,
|
||||
},
|
||||
[RELATION_NESTED_QUERY_KEYWORDS.DISCONNECT]: {
|
||||
type: GraphQLBoolean,
|
||||
description: `Disconnect from a ${objectMetadata.nameSingular} record`,
|
||||
description: `Disconnect from a ${flatObjectMetadata.nameSingular} record`,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private generateRelationWhereInputType(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
): Record<string, GraphQLInputFieldConfig> {
|
||||
const uniqueConstraints = getUniqueConstraintsFields<
|
||||
FieldMetadataEntity,
|
||||
ObjectMetadataEntity
|
||||
>(objectMetadata);
|
||||
const { flatIndexMaps } = context;
|
||||
|
||||
const fields: Record<
|
||||
const indexMetadatas = flatObjectMetadata.indexMetadataIds
|
||||
.map((indexId) => flatIndexMaps.byId[indexId])
|
||||
.filter(isDefined)
|
||||
.map((flatIndex) => ({
|
||||
id: flatIndex.id,
|
||||
isUnique: flatIndex.isUnique,
|
||||
indexFieldMetadatas: flatIndex.flatIndexFieldMetadatas,
|
||||
}));
|
||||
|
||||
const objectWithIndexes = {
|
||||
id: flatObjectMetadata.id,
|
||||
indexMetadatas,
|
||||
fields,
|
||||
};
|
||||
|
||||
const uniqueConstraints = getUniqueConstraintsFields<
|
||||
FlatFieldMetadata,
|
||||
typeof objectWithIndexes
|
||||
>(objectWithIndexes);
|
||||
|
||||
const inputFields: Record<
|
||||
string,
|
||||
{ type: GraphQLInputType; description: string }
|
||||
> = {};
|
||||
@@ -101,11 +134,11 @@ export class RelationConnectGqlInputTypeGenerator {
|
||||
});
|
||||
|
||||
const compositeInputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${pascalCase(field.name)}WhereInput`,
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${pascalCase(field.name)}WhereInput`,
|
||||
fields: () => compositeFields,
|
||||
});
|
||||
|
||||
fields[field.name] = {
|
||||
inputFields[field.name] = {
|
||||
type: compositeInputType,
|
||||
description: `Connect by ${field.label || field.name}`,
|
||||
};
|
||||
@@ -116,7 +149,7 @@ export class RelationConnectGqlInputTypeGenerator {
|
||||
{ settings: field.settings, isIdField: field.name === 'id' },
|
||||
);
|
||||
|
||||
fields[field.name] = {
|
||||
inputFields[field.name] = {
|
||||
type: scalarType || GraphQLString,
|
||||
description: `Connect by ${field.label || field.name}`,
|
||||
};
|
||||
@@ -127,15 +160,15 @@ export class RelationConnectGqlInputTypeGenerator {
|
||||
return {
|
||||
[RELATION_NESTED_QUERY_KEYWORDS.CONNECT_WHERE]: {
|
||||
type: new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}WhereUniqueInput`,
|
||||
fields: () => fields,
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}WhereUniqueInput`,
|
||||
fields: () => inputFields,
|
||||
}),
|
||||
description: `Find a ${objectMetadata.nameSingular} record based on its unique fields: ${this.formatConstraints(uniqueConstraints)}`,
|
||||
description: `Find a ${flatObjectMetadata.nameSingular} record based on its unique fields: ${this.formatConstraints(uniqueConstraints)}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private formatConstraints(constraints: FieldMetadataEntity[][]) {
|
||||
private formatConstraints(constraints: FlatFieldMetadata[][]) {
|
||||
return constraints
|
||||
.map((constraint) => constraint.map((field) => field.name).join(' and '))
|
||||
.join(' or ');
|
||||
|
||||
+18
-14
@@ -14,11 +14,11 @@ import {
|
||||
TypeOptions,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { computeRelationConnectInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util';
|
||||
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
@Injectable()
|
||||
export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
@@ -35,7 +35,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
typeOptions: TypeOptions;
|
||||
@@ -77,7 +77,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
typeOptions: TypeOptions;
|
||||
@@ -118,13 +118,13 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
public generateSimpleRelationFieldOrderByInputType({
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
objectMetadataMaps,
|
||||
context,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
typeOptions: TypeOptions;
|
||||
objectMetadataMaps?: ObjectMetadataMaps;
|
||||
context?: SchemaGenerationContext;
|
||||
}) {
|
||||
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
|
||||
return {};
|
||||
@@ -158,10 +158,12 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
|
||||
if (
|
||||
isDefined(fieldMetadata.relationTargetObjectMetadataId) &&
|
||||
isDefined(objectMetadataMaps)
|
||||
isDefined(context)
|
||||
) {
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
context.flatObjectMetadataMaps.byId[
|
||||
fieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (isDefined(targetObjectMetadata)) {
|
||||
const targetOrderByInputTypeKey = computeObjectMetadataInputTypeKey(
|
||||
@@ -189,10 +191,10 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
public generateSimpleRelationFieldGroupByInputType(
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
objectMetadataMaps?: ObjectMetadataMaps,
|
||||
context?: SchemaGenerationContext,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
|
||||
return {};
|
||||
@@ -204,10 +206,12 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
|
||||
if (
|
||||
isDefined(fieldMetadata.relationTargetObjectMetadataId) &&
|
||||
isDefined(objectMetadataMaps)
|
||||
isDefined(context)
|
||||
) {
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
context.flatObjectMetadataMaps.byId[
|
||||
fieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (isDefined(targetObjectMetadata)) {
|
||||
const targetGroupByInputTypeKey = computeObjectMetadataInputTypeKey(
|
||||
@@ -238,7 +242,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
typeOptions: TypeOptions;
|
||||
|
||||
+24
-13
@@ -15,14 +15,16 @@ import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-sch
|
||||
import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator';
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
|
||||
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
|
||||
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -36,15 +38,20 @@ export class ObjectMetadataUpdateGqlInputTypeGenerator {
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
public buildAndStore(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
_context: SchemaGenerationContext,
|
||||
) {
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Update.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Update.toString()}Input`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () =>
|
||||
this.generateFields(flatObjectMetadata.nameSingular, fields),
|
||||
}) as GraphQLInputObjectType;
|
||||
|
||||
const key = computeObjectMetadataInputTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
GqlInputTypeDefinitionKind.Update,
|
||||
);
|
||||
|
||||
@@ -52,21 +59,25 @@ export class ObjectMetadataUpdateGqlInputTypeGenerator {
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fields: FlatFieldMetadata[],
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
fieldMetadata.isNullable = true;
|
||||
for (const fieldMetadata of fields) {
|
||||
const modifiedFieldMetadata = {
|
||||
...fieldMetadata,
|
||||
isNullable: true,
|
||||
};
|
||||
|
||||
const typeOptions = computeFieldInputTypeOptions(
|
||||
fieldMetadata,
|
||||
modifiedFieldMetadata,
|
||||
GqlInputTypeDefinitionKind.Update,
|
||||
);
|
||||
|
||||
let generatedFields;
|
||||
|
||||
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
if (isMorphOrRelationFlatFieldMetadata(fieldMetadata)) {
|
||||
generatedFields = {
|
||||
...this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldCreateOrUpdateInputType(
|
||||
{
|
||||
@@ -90,7 +101,7 @@ export class ObjectMetadataUpdateGqlInputTypeGenerator {
|
||||
|
||||
if (isEnumFieldMetadataType(fieldMetadata.type)) {
|
||||
const key = computeEnumFieldGqlTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
objectNameSingular,
|
||||
fieldMetadata.name,
|
||||
);
|
||||
|
||||
|
||||
+4
-5
@@ -4,18 +4,17 @@ import {
|
||||
type AggregationField,
|
||||
getAvailableAggregationsFromObjectFields,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
type AggregationGraphQLType = Pick<AggregationField, 'type' | 'description'>;
|
||||
|
||||
@Injectable()
|
||||
export class AggregationObjectTypeGenerator {
|
||||
public generate(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
flatFields: FlatFieldMetadata[],
|
||||
): Record<string, AggregationGraphQLType> {
|
||||
const availableAggregations = getAvailableAggregationsFromObjectFields(
|
||||
objectMetadata.fields,
|
||||
);
|
||||
const availableAggregations =
|
||||
getAvailableAggregationsFromObjectFields(flatFields);
|
||||
|
||||
return Object.entries(availableAggregations).reduce<
|
||||
Record<string, AggregationGraphQLType>
|
||||
|
||||
+16
-14
@@ -10,7 +10,8 @@ import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-build
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
|
||||
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { 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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -23,51 +24,52 @@ export class ConnectionGqlObjectTypeGenerator {
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
public buildAndStore(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFields: FlatFieldMetadata[],
|
||||
) {
|
||||
const kind = ObjectTypeDefinitionKind.Connection;
|
||||
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
kind,
|
||||
);
|
||||
|
||||
this.gqlTypesStorage.addGqlType(
|
||||
key,
|
||||
new GraphQLObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () => this.generateFields(flatObjectMetadata, flatFields),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFields: FlatFieldMetadata[],
|
||||
): GraphQLOutputTypeFieldConfigMap {
|
||||
const fields: GraphQLOutputTypeFieldConfigMap = {};
|
||||
|
||||
const aggregatedFields =
|
||||
this.aggregationObjectTypeGenerator.generate(objectMetadata);
|
||||
this.aggregationObjectTypeGenerator.generate(flatFields);
|
||||
|
||||
Object.assign(fields, aggregatedFields);
|
||||
|
||||
const edgeType = this.gqlTypesStorage.getGqlTypeByKey(
|
||||
computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
ObjectTypeDefinitionKind.Edge,
|
||||
),
|
||||
);
|
||||
|
||||
if (!isDefined(edgeType) || isInputObjectType(edgeType)) {
|
||||
this.logger.error(
|
||||
`Edge type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
|
||||
{
|
||||
objectMetadata,
|
||||
},
|
||||
`Edge type for ${flatObjectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Edge type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
|
||||
`Edge type for ${flatObjectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+10
-13
@@ -9,7 +9,7 @@ import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-build
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
|
||||
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -21,30 +21,30 @@ export class EdgeGqlObjectTypeGenerator {
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
public buildAndStore(flatObjectMetadata: FlatObjectMetadata) {
|
||||
const kind = ObjectTypeDefinitionKind.Edge;
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
kind,
|
||||
);
|
||||
|
||||
this.gqlTypesStorage.addGqlType(
|
||||
key,
|
||||
new GraphQLObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () => this.generateFields(flatObjectMetadata.nameSingular),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
): GraphQLOutputTypeFieldConfigMap {
|
||||
const fields: GraphQLOutputTypeFieldConfigMap = {};
|
||||
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
objectNameSingular,
|
||||
ObjectTypeDefinitionKind.Plain,
|
||||
);
|
||||
|
||||
@@ -52,14 +52,11 @@ export class EdgeGqlObjectTypeGenerator {
|
||||
|
||||
if (!isDefined(objectType) || isInputObjectType(objectType)) {
|
||||
this.logger.error(
|
||||
`Node type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
|
||||
{
|
||||
objectMetadata,
|
||||
},
|
||||
`Node type for ${objectNameSingular} was not found. Please, check if you have defined it.`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Node type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
|
||||
`Node type for ${objectNameSingular} was not found. Please, check if you have defined it.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+10
-13
@@ -9,7 +9,7 @@ import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-build
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
|
||||
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -23,46 +23,43 @@ export class GroupByConnectionGqlObjectTypeGenerator {
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
public buildAndStore(flatObjectMetadata: FlatObjectMetadata) {
|
||||
const kind = ObjectTypeDefinitionKind.GroupByConnection;
|
||||
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
kind,
|
||||
);
|
||||
|
||||
this.gqlTypesStorage.addGqlType(
|
||||
key,
|
||||
new GraphQLObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: () => this.generateFields(flatObjectMetadata.nameSingular),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
): GraphQLOutputTypeFieldConfigMap {
|
||||
const fields: GraphQLOutputTypeFieldConfigMap = {};
|
||||
|
||||
const connection = this.gqlTypesStorage.getGqlTypeByKey(
|
||||
computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
objectNameSingular,
|
||||
ObjectTypeDefinitionKind.Connection,
|
||||
),
|
||||
);
|
||||
|
||||
if (!isDefined(connection) || !isObjectType(connection)) {
|
||||
this.logger.error(
|
||||
`Connection type for ${objectMetadata.nameSingular} was not found.`,
|
||||
{
|
||||
objectMetadata,
|
||||
},
|
||||
`Connection type for ${objectNameSingular} was not found.`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Connection type for ${objectMetadata.nameSingular} was not found.`,
|
||||
`Connection type for ${objectNameSingular} was not found.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+20
-18
@@ -16,11 +16,11 @@ import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspac
|
||||
import { computeCompositeFieldObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util';
|
||||
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
|
||||
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
|
||||
import { 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 { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.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 { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
@@ -35,29 +35,33 @@ export class ObjectMetadataGqlObjectTypeGenerator {
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
public buildAndStore(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
fields: FlatFieldMetadata[],
|
||||
) {
|
||||
const kind = ObjectTypeDefinitionKind.Plain;
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
ObjectTypeDefinitionKind.Plain,
|
||||
);
|
||||
|
||||
this.gqlTypesStorage.addGqlType(
|
||||
key,
|
||||
new GraphQLObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: objectMetadata.description,
|
||||
fields: this.generateFields(objectMetadata),
|
||||
name: `${pascalCase(flatObjectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: flatObjectMetadata.description,
|
||||
fields: this.generateFields(flatObjectMetadata.nameSingular, fields),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectNameSingular: string,
|
||||
fields: FlatFieldMetadata[],
|
||||
): GraphQLOutputTypeFieldConfigMap {
|
||||
const allGeneratedFields: GraphQLOutputTypeFieldConfigMap = {};
|
||||
|
||||
for (const field of objectMetadata.fields) {
|
||||
for (const field of fields) {
|
||||
const typeFactoryOptions = {
|
||||
nullable: field.isNullable ?? undefined,
|
||||
isArray: field.type === FieldMetadataType.MULTI_SELECT,
|
||||
@@ -66,13 +70,11 @@ export class ObjectMetadataGqlObjectTypeGenerator {
|
||||
isIdField: false,
|
||||
};
|
||||
|
||||
if (isMorphOrRelationFieldMetadataType(field.type)) {
|
||||
if (isMorphOrRelationFlatFieldMetadata(field)) {
|
||||
const relationFieldObjectType =
|
||||
this.relationFieldMetadataGqlObjectTypeGenerator.generateRelationFieldObjectType(
|
||||
{
|
||||
fieldMetadata: field as FieldMetadataEntity<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
fieldMetadata: field,
|
||||
typeOptions: typeFactoryOptions,
|
||||
},
|
||||
);
|
||||
@@ -96,7 +98,7 @@ export class ObjectMetadataGqlObjectTypeGenerator {
|
||||
!isDefined(compositeFieldObjectType) ||
|
||||
isInputObjectType(compositeFieldObjectType)
|
||||
) {
|
||||
const message = `Could not find a GraphQL output type for ${field.name} composite field metadata of object ${objectMetadata.nameSingular}`;
|
||||
const message = `Could not find a GraphQL output type for ${field.name} composite field metadata of object ${objectNameSingular}`;
|
||||
|
||||
this.logger.error(message, {
|
||||
type: field.type,
|
||||
@@ -109,7 +111,7 @@ export class ObjectMetadataGqlObjectTypeGenerator {
|
||||
type = compositeFieldObjectType;
|
||||
} else if (isEnumFieldMetadataType(field.type)) {
|
||||
const enumFieldEnumTypeKey = computeEnumFieldGqlTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
objectNameSingular,
|
||||
field.name,
|
||||
);
|
||||
|
||||
@@ -120,7 +122,7 @@ export class ObjectMetadataGqlObjectTypeGenerator {
|
||||
!isDefined(enumFieldEnumType) ||
|
||||
isInputObjectType(enumFieldEnumType)
|
||||
) {
|
||||
const message = `Could not find a GraphQL output type for ${field.name} enum field metadata of object ${objectMetadata.nameSingular}`;
|
||||
const message = `Could not find a GraphQL output type for ${field.name} enum field metadata of object ${objectNameSingular}`;
|
||||
|
||||
this.logger.error(message, {
|
||||
type: field.type,
|
||||
@@ -138,7 +140,7 @@ export class ObjectMetadataGqlObjectTypeGenerator {
|
||||
);
|
||||
|
||||
if (!isDefined(type)) {
|
||||
const message = `Could not find a GraphQL output type for ${field.name} scalar field metadata of object ${objectMetadata.nameSingular}`;
|
||||
const message = `Could not find a GraphQL output type for ${field.name} scalar field metadata of object ${objectNameSingular}`;
|
||||
|
||||
this.logger.error(message, {
|
||||
type: field.type,
|
||||
|
||||
+49
-59
@@ -15,12 +15,13 @@ import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schem
|
||||
import { ArgsTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
|
||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.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 { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataWithRelationsGqlObjectTypeGenerator {
|
||||
@@ -34,11 +35,12 @@ export class ObjectMetadataWithRelationsGqlObjectTypeGenerator {
|
||||
) {}
|
||||
|
||||
public buildAndStore(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadatas: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
) {
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
flatObjectMetadata.nameSingular,
|
||||
ObjectTypeDefinitionKind.Plain,
|
||||
);
|
||||
|
||||
@@ -46,28 +48,26 @@ export class ObjectMetadataWithRelationsGqlObjectTypeGenerator {
|
||||
|
||||
if (!isDefined(gqlType) || !isObjectType(gqlType)) {
|
||||
this.logger.error(
|
||||
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
|
||||
{
|
||||
objectMetadata,
|
||||
},
|
||||
`Could not find a GraphQL type for ${flatObjectMetadata.id.toString()}`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
|
||||
`Could not find a GraphQL type for ${flatObjectMetadata.id.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
const containsRelationOrMorphField = flatFieldMetadatas.some((field) =>
|
||||
isMorphOrRelationFieldMetadataType(field.type),
|
||||
);
|
||||
|
||||
// Security check to avoid extending an object that does not need to be extended
|
||||
if (!objectContainsRelationField(objectMetadata)) {
|
||||
if (!containsRelationOrMorphField) {
|
||||
this.logger.error(
|
||||
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
|
||||
{
|
||||
objectMetadata,
|
||||
},
|
||||
`This object does not need to be extended: ${flatObjectMetadata.id.toString()}`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
|
||||
`This object does not need to be extended: ${flatObjectMetadata.id.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,66 +81,57 @@ export class ObjectMetadataWithRelationsGqlObjectTypeGenerator {
|
||||
...config,
|
||||
fields: () => ({
|
||||
...config.fields,
|
||||
...this.generateFields(objectMetadata, objectMetadataCollection),
|
||||
...this.generateFields(flatFieldMetadatas, context),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
flatFieldMetadatas: FlatFieldMetadata[],
|
||||
context: SchemaGenerationContext,
|
||||
): GraphQLOutputTypeFieldConfigMap {
|
||||
const fields: GraphQLOutputTypeFieldConfigMap = {};
|
||||
const outputFields: GraphQLOutputTypeFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
// Ignore non-relation fields as they are already defined
|
||||
const isRelation =
|
||||
isFieldMetadataEntityOfType(
|
||||
fieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isFieldMetadataEntityOfType(
|
||||
fieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
);
|
||||
|
||||
if (!isRelation) {
|
||||
for (const flatFieldMetadata of flatFieldMetadatas) {
|
||||
if (!isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fieldMetadata.settings) {
|
||||
if (!flatFieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no settings`,
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${flatFieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!fieldMetadata.relationTargetObjectMetadataId) {
|
||||
if (!flatFieldMetadata.relationTargetObjectMetadataId) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no relation target object metadata id`,
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${flatFieldMetadata.id} has no relation target object metadata id`,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataTarget = objectMetadataCollection.find(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.id === fieldMetadata.relationTargetObjectMetadataId,
|
||||
);
|
||||
const objectMetadataTarget =
|
||||
context.flatObjectMetadataMaps.byId[
|
||||
flatFieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(objectMetadataTarget)) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no relation target object metadata`,
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${flatFieldMetadata.id} has no relation target object metadata`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetObjectMetadataGqlObjectType =
|
||||
this.fetchTargetObjectMetadataGqlObjectType(
|
||||
fieldMetadata,
|
||||
flatFieldMetadata,
|
||||
objectMetadataTarget,
|
||||
);
|
||||
|
||||
let argsType: GraphQLFieldConfigArgumentMap | undefined = undefined;
|
||||
|
||||
if (fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY) {
|
||||
if (
|
||||
flatFieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY
|
||||
) {
|
||||
const args = getResolverArgs('findMany');
|
||||
|
||||
argsType = this.argsTypeGenerator.generate({
|
||||
@@ -149,31 +140,31 @@ export class ObjectMetadataWithRelationsGqlObjectTypeGenerator {
|
||||
});
|
||||
}
|
||||
|
||||
fields[fieldMetadata.name] = {
|
||||
outputFields[flatFieldMetadata.name] = {
|
||||
type: targetObjectMetadataGqlObjectType,
|
||||
args: argsType,
|
||||
description: fieldMetadata.description,
|
||||
description: flatFieldMetadata.description,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
return outputFields;
|
||||
}
|
||||
|
||||
private fetchTargetObjectMetadataGqlObjectType(
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
flatFieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
objectMetadataTarget: ObjectMetadataEntity,
|
||||
objectMetadataTarget: FlatObjectMetadata,
|
||||
): GraphQLOutputType {
|
||||
if (!fieldMetadata.settings) {
|
||||
if (!flatFieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no settings`,
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${flatFieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadataTarget.nameSingular,
|
||||
fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY
|
||||
flatFieldMetadata.settings.relationType === RelationType.ONE_TO_MANY
|
||||
? ObjectTypeDefinitionKind.Connection
|
||||
: ObjectTypeDefinitionKind.Plain,
|
||||
);
|
||||
@@ -186,13 +177,12 @@ export class ObjectMetadataWithRelationsGqlObjectTypeGenerator {
|
||||
!isObjectType(targetObjectMetadataGqlObjectType)
|
||||
) {
|
||||
this.logger.error(
|
||||
`Could not find a relation type for ${fieldMetadata.id}`,
|
||||
{
|
||||
fieldMetadata,
|
||||
},
|
||||
`Could not find a relation type for ${flatFieldMetadata.id}`,
|
||||
);
|
||||
|
||||
throw new Error(`Could not find a relation type for ${fieldMetadata.id}`);
|
||||
throw new Error(
|
||||
`Could not find a relation type for ${flatFieldMetadata.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
return targetObjectMetadataGqlObjectType;
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ import {
|
||||
TypeOptions,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
@Injectable()
|
||||
export class RelationFieldMetadataGqlObjectTypeGenerator {
|
||||
@@ -22,7 +22,7 @@ export class RelationFieldMetadataGqlObjectTypeGenerator {
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
typeOptions: TypeOptions;
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/work
|
||||
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
|
||||
import { RootTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
|
||||
@Injectable()
|
||||
export class MutationTypeGenerator {
|
||||
@@ -15,9 +15,9 @@ export class MutationTypeGenerator {
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
) {}
|
||||
|
||||
buildAndStore(objectMetadataCollection: ObjectMetadataEntity[]) {
|
||||
buildAndStore(context: SchemaGenerationContext) {
|
||||
return this.rootTypeGenerator.buildAndStore(
|
||||
objectMetadataCollection,
|
||||
context,
|
||||
[...workspaceResolverBuilderMethodNames.mutations],
|
||||
GqlOperation.Mutation,
|
||||
);
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/work
|
||||
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
|
||||
import { RootTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
|
||||
@Injectable()
|
||||
export class QueryTypeGenerator {
|
||||
@@ -15,9 +15,9 @@ export class QueryTypeGenerator {
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
) {}
|
||||
|
||||
async buildAndStore(objectMetadataCollection: ObjectMetadataEntity[]) {
|
||||
async buildAndStore(context: SchemaGenerationContext) {
|
||||
return this.rootTypeGenerator.buildAndStore(
|
||||
objectMetadataCollection,
|
||||
context,
|
||||
workspaceResolverBuilderMethodNames.queries,
|
||||
GqlOperation.Query,
|
||||
);
|
||||
|
||||
+8
-3
@@ -11,10 +11,11 @@ import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schem
|
||||
import { ArgsTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator';
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
import { GraphQLRootTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
|
||||
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
|
||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -29,7 +30,7 @@ export class RootTypeGenerator {
|
||||
) {}
|
||||
|
||||
buildAndStore(
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
context: SchemaGenerationContext,
|
||||
workspaceResolverMethodNames: readonly WorkspaceResolverBuilderMethodNames[],
|
||||
objectTypeName: GqlOperation,
|
||||
) {
|
||||
@@ -47,6 +48,10 @@ export class RootTypeGenerator {
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataCollection = Object.values(
|
||||
context.flatObjectMetadataMaps.byId,
|
||||
).filter(isDefined);
|
||||
|
||||
this.gqlTypesStorage.addGqlType(
|
||||
objectTypeName,
|
||||
new GraphQLObjectType({
|
||||
@@ -60,7 +65,7 @@ export class RootTypeGenerator {
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
objectMetadataCollection: FlatObjectMetadata[],
|
||||
workspaceResolverMethodNames: readonly WorkspaceResolverBuilderMethodNames[],
|
||||
): GraphQLRootTypeFieldConfigMap {
|
||||
const fieldConfigMap: GraphQLRootTypeFieldConfigMap = {};
|
||||
|
||||
+7
-4
@@ -1,9 +1,12 @@
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
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 interface WorkspaceSchemaBuilderContext {
|
||||
authContext: AuthContext;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
objectIdByNameSingular: Record<string, string>;
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
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';
|
||||
|
||||
export type SchemaGenerationContext = {
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
flatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
|
||||
};
|
||||
+4
-4
@@ -2,15 +2,15 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
|
||||
import { type TypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export const computeFieldInputTypeOptions = <T extends FieldMetadataType>(
|
||||
fieldMetadata: FieldMetadataEntity<T>,
|
||||
export const computeFieldInputTypeOptions = (
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
kind: GqlInputTypeDefinitionKind,
|
||||
): TypeOptions => {
|
||||
return {
|
||||
nullable: fieldMetadata.isNullable ?? undefined,
|
||||
defaultValue: fieldMetadata.defaultValue,
|
||||
defaultValue: fieldMetadata.defaultValue ?? undefined,
|
||||
isArray:
|
||||
kind !== GqlInputTypeDefinitionKind.Filter &&
|
||||
fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
|
||||
+4
-3
@@ -1,13 +1,14 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export const extractGraphQLRelationFieldNames = (
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
) => {
|
||||
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||
const settings = fieldMetadata.settings;
|
||||
const joinColumnName = settings?.joinColumnName;
|
||||
|
||||
if (!joinColumnName) {
|
||||
throw new Error('Join column name is not defined');
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { capitalize, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { getSubfieldsForAggregateOperation } from 'src/engine/twenty-orm/utils/get-subfields-for-aggregate-operation.util';
|
||||
|
||||
export type AggregationField = {
|
||||
@@ -20,7 +20,7 @@ export type AggregationField = {
|
||||
};
|
||||
|
||||
export const getAvailableAggregationsFromObjectFields = (
|
||||
fields: FieldMetadataEntity[],
|
||||
fields: FlatFieldMetadata[],
|
||||
): Record<string, AggregationField> => {
|
||||
return fields.reduce<Record<string, AggregationField>>(
|
||||
(acc, field) => {
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
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 getFlatFieldsFromFlatObjectMetadata = (
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
): FlatFieldMetadata[] => {
|
||||
return flatObjectMetadata.fieldMetadataIds
|
||||
.map((fieldId) => flatFieldMetadataMaps.byId[fieldId])
|
||||
.filter(isDefined);
|
||||
};
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { isMorphRelationFieldMetadataType } from 'src/engine/utils/is-morph-relation-field-metadata-type.util';
|
||||
|
||||
export const objectContainsMorphRelationField = (
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
): boolean => {
|
||||
return objectMetadata.fields.some((field) =>
|
||||
isMorphRelationFieldMetadataType(field.type),
|
||||
);
|
||||
};
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
|
||||
|
||||
export const objectContainsRelationField = (
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
): boolean => {
|
||||
return objectMetadata.fields.some((field) =>
|
||||
isMorphOrRelationFieldMetadataType(field.type),
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -6,7 +6,7 @@ import { GqlTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builde
|
||||
import { OrphanedTypesGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator';
|
||||
import { MutationTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator';
|
||||
import { QueryTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceGraphQLSchemaGenerator {
|
||||
@@ -18,9 +18,9 @@ export class WorkspaceGraphQLSchemaGenerator {
|
||||
) {}
|
||||
|
||||
async generateSchema(
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
context: SchemaGenerationContext,
|
||||
): Promise<GraphQLSchema> {
|
||||
await this.gqlTypeGenerator.buildAndStore(objectMetadataCollection);
|
||||
await this.gqlTypeGenerator.buildAndStore(context);
|
||||
|
||||
// Assemble schema
|
||||
const schema = new GraphQLSchema({
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
@@ -22,11 +21,7 @@ import {
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util';
|
||||
import { buildObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-metadata-item-with-field-maps.util';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
|
||||
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaFactory {
|
||||
@@ -37,7 +32,6 @@ export class WorkspaceSchemaFactory {
|
||||
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async createGraphQLSchema(authContext: AuthContext): Promise<GraphQLSchema> {
|
||||
@@ -82,8 +76,12 @@ export class WorkspaceSchemaFactory {
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceFeatureFlagsMap =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||
if (!isDefined(flatFieldMetadataMaps)) {
|
||||
throw new FlatEntityMapsException(
|
||||
'Field metadata collection not found',
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
let metadataVersion =
|
||||
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||
@@ -99,37 +97,7 @@ export class WorkspaceSchemaFactory {
|
||||
const { idByNameSingular } = buildObjectIdByNameMaps(
|
||||
flatObjectMetadataMaps,
|
||||
);
|
||||
const objectMetadataMaps: ObjectMetadataMaps = {
|
||||
byId: {},
|
||||
idByNameSingular,
|
||||
};
|
||||
|
||||
for (const [id, flatObj] of Object.entries(flatObjectMetadataMaps.byId)) {
|
||||
if (isDefined(flatObj)) {
|
||||
objectMetadataMaps.byId[id] = buildObjectMetadataItemWithFieldMaps(
|
||||
flatObj,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const objectMetadataCollection = Object.values(objectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((objectMetadataItem) => ({
|
||||
...objectMetadataItem,
|
||||
fields: Object.values(objectMetadataItem.fieldsById),
|
||||
indexes: objectMetadataItem.indexMetadatas,
|
||||
}))
|
||||
.filter((objectMetadata) => {
|
||||
return !shouldExcludeFromWorkspaceApi(
|
||||
objectMetadata,
|
||||
standardObjectMetadataDefinitions,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
});
|
||||
|
||||
// Get typeDefs from cache
|
||||
let typeDefs = await this.workspaceCacheStorageService.getGraphQLTypeDefs(
|
||||
authContext.workspace.id,
|
||||
metadataVersion,
|
||||
@@ -140,12 +108,13 @@ export class WorkspaceSchemaFactory {
|
||||
metadataVersion,
|
||||
);
|
||||
|
||||
// If typeDefs are not cached, generate them
|
||||
if (!typeDefs || !usedScalarNames) {
|
||||
const autoGeneratedSchema =
|
||||
await this.workspaceGraphQLSchemaGenerator.generateSchema(
|
||||
objectMetadataCollection,
|
||||
);
|
||||
await this.workspaceGraphQLSchemaGenerator.generateSchema({
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
});
|
||||
|
||||
usedScalarNames =
|
||||
this.scalarsExplorerService.getUsedScalarNames(autoGeneratedSchema);
|
||||
@@ -165,7 +134,9 @@ export class WorkspaceSchemaFactory {
|
||||
|
||||
const autoGeneratedResolvers = await this.workspaceResolverFactory.create(
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
idByNameSingular,
|
||||
workspaceResolverBuilderMethodNames,
|
||||
);
|
||||
const scalarsResolvers =
|
||||
|
||||
Reference in New Issue
Block a user