First step of https://github.com/twentyhq/twenty/issues/6868 Adds min.., max.. queries for DATETIME fields adds min.., max.., avg.., sum.. queries for NUMBER fields (count distinct operation and composite fields such as CURRENCY handling will be dealt with in a future PR) <img width="1422" alt="Capture d’écran 2024-11-06 à 15 48 46" src="https://github.com/user-attachments/assets/4bcdece0-ad3e-4536-9720-fe4044a36719"> --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@ -5,27 +5,27 @@ import {
|
||||
WhereExpressionBuilder,
|
||||
} from 'typeorm';
|
||||
|
||||
import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
|
||||
import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser';
|
||||
|
||||
export class GraphqlQueryFilterConditionParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
private fieldMetadataMapByName: FieldMetadataMap;
|
||||
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
|
||||
|
||||
constructor(fieldMetadataMap: FieldMetadataMap) {
|
||||
this.fieldMetadataMap = fieldMetadataMap;
|
||||
constructor(fieldMetadataMapByName: FieldMetadataMap) {
|
||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
|
||||
this.fieldMetadataMap,
|
||||
this.fieldMetadataMapByName,
|
||||
);
|
||||
}
|
||||
|
||||
public parse(
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
objectNameSingular: string,
|
||||
filter: Partial<RecordFilter>,
|
||||
filter: Partial<ObjectRecordFilter>,
|
||||
): SelectQueryBuilder<any> {
|
||||
if (!filter || Object.keys(filter).length === 0) {
|
||||
return queryBuilder;
|
||||
@ -50,7 +50,7 @@ export class GraphqlQueryFilterConditionParser {
|
||||
switch (key) {
|
||||
case 'and': {
|
||||
const andWhereCondition = new Brackets((qb) => {
|
||||
value.forEach((filter: RecordFilter, index: number) => {
|
||||
value.forEach((filter: ObjectRecordFilter, index: number) => {
|
||||
const whereCondition = new Brackets((qb2) => {
|
||||
Object.entries(filter).forEach(
|
||||
([subFilterkey, subFilterValue], index) => {
|
||||
@ -82,7 +82,7 @@ export class GraphqlQueryFilterConditionParser {
|
||||
}
|
||||
case 'or': {
|
||||
const orWhereCondition = new Brackets((qb) => {
|
||||
value.forEach((filter: RecordFilter, index: number) => {
|
||||
value.forEach((filter: ObjectRecordFilter, index: number) => {
|
||||
const whereCondition = new Brackets((qb2) => {
|
||||
Object.entries(filter).forEach(
|
||||
([subFilterkey, subFilterValue], index) => {
|
||||
|
||||
@ -9,17 +9,17 @@ import {
|
||||
import { computeWhereConditionParts } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
const ARRAY_OPERATORS = ['in', 'contains', 'not_contains'];
|
||||
|
||||
export class GraphqlQueryFilterFieldParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
private fieldMetadataMapByName: FieldMetadataMap;
|
||||
|
||||
constructor(fieldMetadataMap: FieldMetadataMap) {
|
||||
this.fieldMetadataMap = fieldMetadataMap;
|
||||
constructor(fieldMetadataMapByName: FieldMetadataMap) {
|
||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||
}
|
||||
|
||||
public parse(
|
||||
@ -29,7 +29,7 @@ export class GraphqlQueryFilterFieldParser {
|
||||
filterValue: any,
|
||||
isFirst = false,
|
||||
): void {
|
||||
const fieldMetadata = this.fieldMetadataMap[`${key}`];
|
||||
const fieldMetadata = this.fieldMetadataMapByName[`${key}`];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new Error(`Field metadata not found for field: ${key}`);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
ObjectRecordOrderBy,
|
||||
OrderByDirection,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
@ -10,25 +10,25 @@ import {
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
export class GraphqlQueryOrderFieldParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
private fieldMetadataMapByName: FieldMetadataMap;
|
||||
|
||||
constructor(fieldMetadataMap: FieldMetadataMap) {
|
||||
this.fieldMetadataMap = fieldMetadataMap;
|
||||
constructor(fieldMetadataMapByName: FieldMetadataMap) {
|
||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||
}
|
||||
|
||||
parse(
|
||||
orderBy: RecordOrderBy,
|
||||
orderBy: ObjectRecordOrderBy,
|
||||
objectNameSingular: string,
|
||||
isForwardPagination = true,
|
||||
): Record<string, string> {
|
||||
return orderBy.reduce(
|
||||
(acc, item) => {
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
const fieldMetadata = this.fieldMetadataMap[key];
|
||||
const fieldMetadata = this.fieldMetadataMapByName[key];
|
||||
|
||||
if (!fieldMetadata || value === undefined) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import {
|
||||
AggregationField,
|
||||
getAvailableAggregationsFromObjectFields,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
|
||||
export class GraphqlQuerySelectedFieldsAggregateParser {
|
||||
parse(
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||
): void {
|
||||
const availableAggregations: Record<string, AggregationField> =
|
||||
getAvailableAggregationsFromObjectFields(
|
||||
Object.values(fieldMetadataMapByName),
|
||||
);
|
||||
|
||||
for (const selectedField of Object.keys(graphqlSelectedFields)) {
|
||||
const selectedAggregation = availableAggregations[selectedField];
|
||||
|
||||
if (!selectedAggregation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
accumulator.aggregate[selectedField] = selectedAggregation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +1,47 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import {
|
||||
GraphqlQuerySelectedFieldsParser,
|
||||
GraphqlQuerySelectedFieldsResult,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util';
|
||||
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
export class GraphqlQuerySelectedFieldsRelationParser {
|
||||
private objectMetadataMap: ObjectMetadataMap;
|
||||
private objectMetadataMaps: ObjectMetadataMaps;
|
||||
|
||||
constructor(objectMetadataMap: ObjectMetadataMap) {
|
||||
this.objectMetadataMap = objectMetadataMap;
|
||||
constructor(objectMetadataMaps: ObjectMetadataMaps) {
|
||||
this.objectMetadataMaps = objectMetadataMaps;
|
||||
}
|
||||
|
||||
parseRelationField(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
fieldKey: string,
|
||||
fieldValue: any,
|
||||
result: { select: Record<string, any>; relations: Record<string, any> },
|
||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||
): void {
|
||||
if (!fieldValue || typeof fieldValue !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
result.relations[fieldKey] = true;
|
||||
accumulator.relations[fieldKey] = true;
|
||||
|
||||
const referencedObjectMetadata = getRelationObjectMetadata(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMap,
|
||||
this.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const relationFields = referencedObjectMetadata.fields;
|
||||
const relationFields = referencedObjectMetadata.fieldsByName;
|
||||
const fieldParser = new GraphqlQuerySelectedFieldsParser(
|
||||
this.objectMetadataMap,
|
||||
this.objectMetadataMaps,
|
||||
);
|
||||
const subResult = fieldParser.parse(fieldValue, relationFields);
|
||||
const relationAccumulator = fieldParser.parse(fieldValue, relationFields);
|
||||
|
||||
result.select[fieldKey] = {
|
||||
accumulator.select[fieldKey] = {
|
||||
id: true,
|
||||
...subResult.select,
|
||||
...relationAccumulator.select,
|
||||
};
|
||||
result.relations[fieldKey] = subResult.relations;
|
||||
accumulator.relations[fieldKey] = relationAccumulator.relations;
|
||||
accumulator.aggregate[fieldKey] = relationAccumulator.aggregate;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,59 +1,71 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
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 { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import { isPlainObject } from 'src/utils/is-plain-object';
|
||||
|
||||
export type GraphqlQuerySelectedFieldsResult = {
|
||||
select: Record<string, any>;
|
||||
relations: Record<string, any>;
|
||||
aggregate: Record<string, any>;
|
||||
};
|
||||
|
||||
export class GraphqlQuerySelectedFieldsParser {
|
||||
private graphqlQuerySelectedFieldsRelationParser: GraphqlQuerySelectedFieldsRelationParser;
|
||||
private aggregateParser: GraphqlQuerySelectedFieldsAggregateParser;
|
||||
|
||||
constructor(objectMetadataMap: ObjectMetadataMap) {
|
||||
constructor(objectMetadataMaps: ObjectMetadataMaps) {
|
||||
this.graphqlQuerySelectedFieldsRelationParser =
|
||||
new GraphqlQuerySelectedFieldsRelationParser(objectMetadataMap);
|
||||
new GraphqlQuerySelectedFieldsRelationParser(objectMetadataMaps);
|
||||
this.aggregateParser = new GraphqlQuerySelectedFieldsAggregateParser();
|
||||
}
|
||||
|
||||
parse(
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
fieldMetadataMap: Record<string, FieldMetadataInterface>,
|
||||
): { select: Record<string, any>; relations: Record<string, any> } {
|
||||
const result: {
|
||||
select: Record<string, any>;
|
||||
relations: Record<string, any>;
|
||||
} = {
|
||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
||||
): GraphqlQuerySelectedFieldsResult {
|
||||
const accumulator: GraphqlQuerySelectedFieldsResult = {
|
||||
select: {},
|
||||
relations: {},
|
||||
aggregate: {},
|
||||
};
|
||||
|
||||
if (this.isRootConnection(graphqlSelectedFields)) {
|
||||
this.parseConnectionField(
|
||||
graphqlSelectedFields,
|
||||
fieldMetadataMapByName,
|
||||
accumulator,
|
||||
);
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
this.parseRecordField(
|
||||
graphqlSelectedFields,
|
||||
fieldMetadataMapByName,
|
||||
accumulator,
|
||||
);
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
private parseRecordField(
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||
): void {
|
||||
for (const [fieldKey, fieldValue] of Object.entries(
|
||||
graphqlSelectedFields,
|
||||
)) {
|
||||
if (this.shouldNotParseField(fieldKey)) {
|
||||
continue;
|
||||
}
|
||||
if (this.isConnectionField(fieldKey, fieldValue)) {
|
||||
const subResult = this.parse(fieldValue, fieldMetadataMap);
|
||||
|
||||
Object.assign(result.select, subResult.select);
|
||||
Object.assign(result.relations, subResult.relations);
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldMetadata = fieldMetadataMap[fieldKey];
|
||||
const fieldMetadata = fieldMetadataMapByName[fieldKey];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Field "${fieldKey}" does not exist or is not selectable`,
|
||||
GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isRelationFieldMetadataType(fieldMetadata.type)) {
|
||||
@ -61,7 +73,7 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
fieldMetadata,
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
result,
|
||||
accumulator,
|
||||
);
|
||||
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
const compositeResult = this.parseCompositeField(
|
||||
@ -69,23 +81,33 @@ export class GraphqlQuerySelectedFieldsParser {
|
||||
fieldValue,
|
||||
);
|
||||
|
||||
Object.assign(result.select, compositeResult);
|
||||
Object.assign(accumulator.select, compositeResult);
|
||||
} else {
|
||||
result.select[fieldKey] = true;
|
||||
accumulator.select[fieldKey] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private isConnectionField(fieldKey: string, fieldValue: any): boolean {
|
||||
return ['edges', 'node'].includes(fieldKey) && isPlainObject(fieldValue);
|
||||
}
|
||||
|
||||
private shouldNotParseField(fieldKey: string): boolean {
|
||||
return ['__typename', 'totalCount', 'pageInfo', 'cursor'].includes(
|
||||
fieldKey,
|
||||
private parseConnectionField(
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||
): void {
|
||||
this.aggregateParser.parse(
|
||||
graphqlSelectedFields,
|
||||
fieldMetadataMapByName,
|
||||
accumulator,
|
||||
);
|
||||
|
||||
const node = graphqlSelectedFields.edges.node;
|
||||
|
||||
this.parseRecordField(node, fieldMetadataMapByName, accumulator);
|
||||
}
|
||||
|
||||
private isRootConnection(
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
): boolean {
|
||||
return Object.keys(graphqlSelectedFields).includes('edges');
|
||||
}
|
||||
|
||||
private parseCompositeField(
|
||||
|
||||
@ -6,43 +6,44 @@ import {
|
||||
} from 'typeorm';
|
||||
|
||||
import {
|
||||
RecordFilter,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
ObjectRecordFilter,
|
||||
ObjectRecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
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 { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import {
|
||||
FieldMetadataMap,
|
||||
ObjectMetadataMap,
|
||||
ObjectMetadataMapItem,
|
||||
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
GraphqlQuerySelectedFieldsParser,
|
||||
GraphqlQuerySelectedFieldsResult,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
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';
|
||||
|
||||
export class GraphqlQueryParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
private objectMetadataMap: ObjectMetadataMap;
|
||||
private fieldMetadataMapByName: FieldMetadataMap;
|
||||
private objectMetadataMaps: ObjectMetadataMaps;
|
||||
private filterConditionParser: GraphqlQueryFilterConditionParser;
|
||||
private orderFieldParser: GraphqlQueryOrderFieldParser;
|
||||
|
||||
constructor(
|
||||
fieldMetadataMap: FieldMetadataMap,
|
||||
objectMetadataMap: ObjectMetadataMap,
|
||||
fieldMetadataMapByName: FieldMetadataMap,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
) {
|
||||
this.objectMetadataMap = objectMetadataMap;
|
||||
this.fieldMetadataMap = fieldMetadataMap;
|
||||
this.objectMetadataMaps = objectMetadataMaps;
|
||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
|
||||
this.fieldMetadataMap,
|
||||
this.fieldMetadataMapByName,
|
||||
);
|
||||
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
||||
this.fieldMetadataMap,
|
||||
this.fieldMetadataMapByName,
|
||||
);
|
||||
}
|
||||
|
||||
public applyFilterToBuilder(
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
objectNameSingular: string,
|
||||
recordFilter: Partial<RecordFilter>,
|
||||
recordFilter: Partial<ObjectRecordFilter>,
|
||||
): SelectQueryBuilder<any> {
|
||||
return this.filterConditionParser.parse(
|
||||
queryBuilder,
|
||||
@ -53,7 +54,7 @@ export class GraphqlQueryParser {
|
||||
|
||||
public applyDeletedAtToBuilder(
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
recordFilter: RecordFilter,
|
||||
recordFilter: ObjectRecordFilter,
|
||||
): SelectQueryBuilder<any> {
|
||||
if (this.checkForDeletedAtFilter(recordFilter)) {
|
||||
queryBuilder.withDeleted();
|
||||
@ -90,7 +91,7 @@ export class GraphqlQueryParser {
|
||||
|
||||
public applyOrderToBuilder(
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
orderBy: RecordOrderBy,
|
||||
orderBy: ObjectRecordOrderBy,
|
||||
objectNameSingular: string,
|
||||
isForwardPagination = true,
|
||||
): SelectQueryBuilder<any> {
|
||||
@ -104,11 +105,12 @@ export class GraphqlQueryParser {
|
||||
}
|
||||
|
||||
public parseSelectedFields(
|
||||
parentObjectMetadata: ObjectMetadataMapItem,
|
||||
parentObjectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||
): { select: Record<string, any>; relations: Record<string, any> } {
|
||||
): GraphqlQuerySelectedFieldsResult {
|
||||
const parentFields =
|
||||
this.objectMetadataMap[parentObjectMetadata.nameSingular]?.fields;
|
||||
this.objectMetadataMaps.byNameSingular[parentObjectMetadata.nameSingular]
|
||||
?.fieldsByName;
|
||||
|
||||
if (!parentFields) {
|
||||
throw new Error(
|
||||
@ -117,7 +119,7 @@ export class GraphqlQueryParser {
|
||||
}
|
||||
|
||||
const selectedFieldsParser = new GraphqlQuerySelectedFieldsParser(
|
||||
this.objectMetadataMap,
|
||||
this.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return selectedFieldsParser.parse(graphqlSelectedFields, parentFields);
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
Record as IRecord,
|
||||
RecordFilter,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
ObjectRecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
@ -48,11 +48,11 @@ export class GraphqlQueryRunnerService {
|
||||
/** QUERIES */
|
||||
|
||||
@LogExecutionTime()
|
||||
async findOne<ObjectRecord extends IRecord, Filter extends RecordFilter>(
|
||||
async findOne<T extends ObjectRecord, Filter extends ObjectRecordFilter>(
|
||||
args: FindOneResolverArgs<Filter>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
return this.executeQuery<FindOneResolverArgs<Filter>, ObjectRecord>(
|
||||
): Promise<T> {
|
||||
return this.executeQuery<FindOneResolverArgs<Filter>, T>(
|
||||
'findOne',
|
||||
args,
|
||||
options,
|
||||
@ -61,36 +61,36 @@ export class GraphqlQueryRunnerService {
|
||||
|
||||
@LogExecutionTime()
|
||||
async findMany<
|
||||
ObjectRecord extends IRecord,
|
||||
Filter extends RecordFilter,
|
||||
OrderBy extends RecordOrderBy,
|
||||
T extends ObjectRecord,
|
||||
Filter extends ObjectRecordFilter,
|
||||
OrderBy extends ObjectRecordOrderBy,
|
||||
>(
|
||||
args: FindManyResolverArgs<Filter, OrderBy>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord, IEdge<ObjectRecord>>> {
|
||||
): Promise<IConnection<T, IEdge<T>>> {
|
||||
return this.executeQuery<
|
||||
FindManyResolverArgs<Filter, OrderBy>,
|
||||
IConnection<ObjectRecord, IEdge<ObjectRecord>>
|
||||
IConnection<T, IEdge<T>>
|
||||
>('findMany', args, options);
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
async findDuplicates<ObjectRecord extends IRecord>(
|
||||
args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
|
||||
async findDuplicates<T extends ObjectRecord>(
|
||||
args: FindDuplicatesResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>[]> {
|
||||
): Promise<IConnection<T>[]> {
|
||||
return this.executeQuery<
|
||||
FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
|
||||
IConnection<ObjectRecord>[]
|
||||
FindDuplicatesResolverArgs<Partial<T>>,
|
||||
IConnection<T>[]
|
||||
>('findDuplicates', args, options);
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
async search<ObjectRecord extends IRecord = IRecord>(
|
||||
async search<T extends ObjectRecord = ObjectRecord>(
|
||||
args: SearchResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>> {
|
||||
return this.executeQuery<SearchResolverArgs, IConnection<ObjectRecord>>(
|
||||
): Promise<IConnection<T>> {
|
||||
return this.executeQuery<SearchResolverArgs, IConnection<T>>(
|
||||
'search',
|
||||
args,
|
||||
options,
|
||||
@ -100,13 +100,13 @@ export class GraphqlQueryRunnerService {
|
||||
/** MUTATIONS */
|
||||
|
||||
@LogExecutionTime()
|
||||
async createOne<ObjectRecord extends IRecord>(
|
||||
args: CreateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
async createOne<T extends ObjectRecord>(
|
||||
args: CreateOneResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
): Promise<T> {
|
||||
const results = await this.executeQuery<
|
||||
CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
ObjectRecord[]
|
||||
CreateManyResolverArgs<Partial<T>>,
|
||||
T[]
|
||||
>('createMany', { data: [args.data], upsert: args.upsert }, options);
|
||||
|
||||
// TODO: emitCreateEvents should be moved to the ORM layer
|
||||
@ -114,7 +114,7 @@ export class GraphqlQueryRunnerService {
|
||||
this.apiEventEmitterService.emitCreateEvents(
|
||||
results,
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
}
|
||||
|
||||
@ -122,20 +122,20 @@ export class GraphqlQueryRunnerService {
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
async createMany<ObjectRecord extends IRecord>(
|
||||
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
async createMany<T extends ObjectRecord>(
|
||||
args: CreateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
): Promise<T[]> {
|
||||
const results = await this.executeQuery<
|
||||
CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
ObjectRecord[]
|
||||
CreateManyResolverArgs<Partial<T>>,
|
||||
T[]
|
||||
>('createMany', args, options);
|
||||
|
||||
if (results) {
|
||||
this.apiEventEmitterService.emitCreateEvents(
|
||||
results,
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
}
|
||||
|
||||
@ -143,14 +143,11 @@ export class GraphqlQueryRunnerService {
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
public async updateOne<ObjectRecord extends IRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
public async updateOne<T extends ObjectRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const existingRecord = await this.executeQuery<
|
||||
FindOneResolverArgs,
|
||||
ObjectRecord
|
||||
>(
|
||||
): Promise<T> {
|
||||
const existingRecord = await this.executeQuery<FindOneResolverArgs, T>(
|
||||
'findOne',
|
||||
{
|
||||
filter: { id: { eq: args.id } },
|
||||
@ -159,8 +156,8 @@ export class GraphqlQueryRunnerService {
|
||||
);
|
||||
|
||||
const result = await this.executeQuery<
|
||||
UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
ObjectRecord
|
||||
UpdateOneResolverArgs<Partial<T>>,
|
||||
T
|
||||
>('updateOne', args, options);
|
||||
|
||||
this.apiEventEmitterService.emitUpdateEvents(
|
||||
@ -168,20 +165,20 @@ export class GraphqlQueryRunnerService {
|
||||
[result],
|
||||
Object.keys(args.data),
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
public async updateMany<ObjectRecord extends IRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
public async updateMany<T extends ObjectRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
): Promise<T[]> {
|
||||
const existingRecords = await this.executeQuery<
|
||||
FindManyResolverArgs,
|
||||
IConnection<ObjectRecord, IEdge<ObjectRecord>>
|
||||
IConnection<T, IEdge<T>>
|
||||
>(
|
||||
'findMany',
|
||||
{
|
||||
@ -191,8 +188,8 @@ export class GraphqlQueryRunnerService {
|
||||
);
|
||||
|
||||
const result = await this.executeQuery<
|
||||
UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
ObjectRecord[]
|
||||
UpdateManyResolverArgs<Partial<T>>,
|
||||
T[]
|
||||
>('updateMany', args, options);
|
||||
|
||||
this.apiEventEmitterService.emitUpdateEvents(
|
||||
@ -200,25 +197,25 @@ export class GraphqlQueryRunnerService {
|
||||
result,
|
||||
Object.keys(args.data),
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
public async deleteOne<ObjectRecord extends IRecord & { deletedAt?: Date }>(
|
||||
public async deleteOne<T extends ObjectRecord & { deletedAt?: Date }>(
|
||||
args: DeleteOneResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
): Promise<T> {
|
||||
const result = await this.executeQuery<
|
||||
UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
ObjectRecord
|
||||
UpdateOneResolverArgs<Partial<T>>,
|
||||
T
|
||||
>(
|
||||
'deleteOne',
|
||||
{
|
||||
id: args.id,
|
||||
data: { deletedAt: new Date() } as Partial<ObjectRecord>,
|
||||
data: { deletedAt: new Date() } as Partial<T>,
|
||||
},
|
||||
options,
|
||||
);
|
||||
@ -226,26 +223,26 @@ export class GraphqlQueryRunnerService {
|
||||
this.apiEventEmitterService.emitDeletedEvents(
|
||||
[result],
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
public async deleteMany<ObjectRecord extends IRecord & { deletedAt?: Date }>(
|
||||
public async deleteMany<T extends ObjectRecord & { deletedAt?: Date }>(
|
||||
args: DeleteManyResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
): Promise<T[]> {
|
||||
const result = await this.executeQuery<
|
||||
UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
ObjectRecord[]
|
||||
UpdateManyResolverArgs<Partial<T>>,
|
||||
T[]
|
||||
>(
|
||||
'deleteMany',
|
||||
{
|
||||
filter: args.filter,
|
||||
|
||||
data: { deletedAt: new Date() } as Partial<ObjectRecord>,
|
||||
data: { deletedAt: new Date() } as Partial<T>,
|
||||
},
|
||||
options,
|
||||
);
|
||||
@ -253,63 +250,62 @@ export class GraphqlQueryRunnerService {
|
||||
this.apiEventEmitterService.emitDeletedEvents(
|
||||
result,
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
async destroyOne<ObjectRecord extends IRecord>(
|
||||
async destroyOne<T extends ObjectRecord>(
|
||||
args: DestroyOneResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const result = await this.executeQuery<
|
||||
DestroyOneResolverArgs,
|
||||
ObjectRecord
|
||||
>('destroyOne', args, options);
|
||||
): Promise<T> {
|
||||
const result = await this.executeQuery<DestroyOneResolverArgs, T>(
|
||||
'destroyOne',
|
||||
args,
|
||||
options,
|
||||
);
|
||||
|
||||
this.apiEventEmitterService.emitDestroyEvents(
|
||||
[result],
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
async destroyMany<ObjectRecord extends IRecord>(
|
||||
async destroyMany<T extends ObjectRecord>(
|
||||
args: DestroyManyResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const result = await this.executeQuery<
|
||||
DestroyManyResolverArgs,
|
||||
ObjectRecord[]
|
||||
>('destroyMany', args, options);
|
||||
): Promise<T[]> {
|
||||
const result = await this.executeQuery<DestroyManyResolverArgs, T[]>(
|
||||
'destroyMany',
|
||||
args,
|
||||
options,
|
||||
);
|
||||
|
||||
this.apiEventEmitterService.emitDestroyEvents(
|
||||
result,
|
||||
options.authContext,
|
||||
options.objectMetadataItem,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LogExecutionTime()
|
||||
public async restoreMany<ObjectRecord extends IRecord>(
|
||||
public async restoreMany<T extends ObjectRecord>(
|
||||
args: RestoreManyResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
return await this.executeQuery<
|
||||
UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
ObjectRecord
|
||||
>(
|
||||
): Promise<T> {
|
||||
return await this.executeQuery<UpdateManyResolverArgs<Partial<T>>, T>(
|
||||
'restoreMany',
|
||||
{
|
||||
filter: args.filter,
|
||||
data: { deletedAt: null } as Partial<ObjectRecord>,
|
||||
data: { deletedAt: null } as Partial<T>,
|
||||
},
|
||||
options,
|
||||
);
|
||||
@ -320,7 +316,7 @@ export class GraphqlQueryRunnerService {
|
||||
args: Input,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Response> {
|
||||
const { authContext, objectMetadataItem } = options;
|
||||
const { authContext, objectMetadataItemWithFieldMaps } = options;
|
||||
|
||||
const resolver =
|
||||
this.graphqlQueryResolverFactory.getResolver(operationName);
|
||||
@ -330,7 +326,7 @@ export class GraphqlQueryRunnerService {
|
||||
const hookedArgs =
|
||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
||||
authContext,
|
||||
objectMetadataItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
operationName,
|
||||
args,
|
||||
);
|
||||
@ -345,7 +341,7 @@ export class GraphqlQueryRunnerService {
|
||||
|
||||
const resultWithGetters = await this.queryResultGettersFactory.create(
|
||||
results,
|
||||
objectMetadataItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
@ -355,7 +351,7 @@ export class GraphqlQueryRunnerService {
|
||||
|
||||
await this.workspaceQueryHookService.executePostQueryHooks(
|
||||
authContext,
|
||||
objectMetadataItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
operationName,
|
||||
resultWithGettersArray,
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Record as IRecord,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
ObjectRecord,
|
||||
ObjectRecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
@ -12,23 +12,27 @@ import {
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util';
|
||||
import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { FieldMetadataType } 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 { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { isPlainObject } from 'src/utils/is-plain-object';
|
||||
|
||||
export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
private objectMetadataMap: ObjectMetadataMap;
|
||||
private objectMetadataMaps: ObjectMetadataMaps;
|
||||
|
||||
constructor(objectMetadataMap: ObjectMetadataMap) {
|
||||
this.objectMetadataMap = objectMetadataMap;
|
||||
constructor(objectMetadataMaps: ObjectMetadataMaps) {
|
||||
this.objectMetadataMaps = objectMetadataMaps;
|
||||
}
|
||||
|
||||
public createConnection<ObjectRecord extends IRecord = IRecord>({
|
||||
public createConnection<T extends ObjectRecord = ObjectRecord>({
|
||||
objectRecords,
|
||||
parentObjectRecord,
|
||||
objectRecordsAggregatedValues = {},
|
||||
selectedAggregatedFields = [],
|
||||
objectName,
|
||||
take,
|
||||
totalCount,
|
||||
@ -37,19 +41,24 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
hasPreviousPage,
|
||||
depth = 0,
|
||||
}: {
|
||||
objectRecords: ObjectRecord[];
|
||||
objectRecords: T[];
|
||||
parentObjectRecord?: T;
|
||||
objectRecordsAggregatedValues?: Record<string, any>;
|
||||
selectedAggregatedFields?: Record<string, any>;
|
||||
objectName: string;
|
||||
take: number;
|
||||
totalCount: number;
|
||||
order?: RecordOrderBy;
|
||||
order?: ObjectRecordOrderBy;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
depth?: number;
|
||||
}): IConnection<ObjectRecord> {
|
||||
}): IConnection<T> {
|
||||
const edges = (objectRecords ?? []).map((objectRecord) => ({
|
||||
node: this.processRecord({
|
||||
objectRecord,
|
||||
objectName,
|
||||
objectRecordsAggregatedValues,
|
||||
selectedAggregatedFields,
|
||||
take,
|
||||
totalCount,
|
||||
order,
|
||||
@ -58,7 +67,15 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
cursor: encodeCursor(objectRecord, order),
|
||||
}));
|
||||
|
||||
const aggregatedFieldsValues = this.extractAggregatedFieldsValues({
|
||||
selectedAggregatedFields,
|
||||
objectRecordsAggregatedValues: parentObjectRecord
|
||||
? objectRecordsAggregatedValues[parentObjectRecord.id]
|
||||
: objectRecordsAggregatedValues,
|
||||
});
|
||||
|
||||
return {
|
||||
...aggregatedFieldsValues,
|
||||
edges,
|
||||
pageInfo: {
|
||||
hasNextPage,
|
||||
@ -70,9 +87,41 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
};
|
||||
}
|
||||
|
||||
private extractAggregatedFieldsValues = ({
|
||||
selectedAggregatedFields,
|
||||
objectRecordsAggregatedValues,
|
||||
}: {
|
||||
selectedAggregatedFields: Record<string, AggregationField[]>;
|
||||
objectRecordsAggregatedValues: Record<string, any>;
|
||||
}) => {
|
||||
if (!objectRecordsAggregatedValues) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.entries(selectedAggregatedFields).reduce(
|
||||
(acc, [aggregatedFieldName]) => {
|
||||
const aggregatedFieldValue =
|
||||
objectRecordsAggregatedValues[aggregatedFieldName];
|
||||
|
||||
if (!aggregatedFieldValue) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[aggregatedFieldName]:
|
||||
objectRecordsAggregatedValues[aggregatedFieldName],
|
||||
};
|
||||
},
|
||||
{},
|
||||
);
|
||||
};
|
||||
|
||||
public processRecord<T extends Record<string, any>>({
|
||||
objectRecord,
|
||||
objectName,
|
||||
objectRecordsAggregatedValues = {},
|
||||
selectedAggregatedFields = [],
|
||||
take,
|
||||
totalCount,
|
||||
order,
|
||||
@ -80,9 +129,11 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
}: {
|
||||
objectRecord: T;
|
||||
objectName: string;
|
||||
objectRecordsAggregatedValues?: Record<string, any>;
|
||||
selectedAggregatedFields?: Record<string, any>;
|
||||
take: number;
|
||||
totalCount: number;
|
||||
order?: RecordOrderBy;
|
||||
order?: ObjectRecordOrderBy;
|
||||
depth?: number;
|
||||
}): T {
|
||||
if (depth >= CONNECTION_MAX_DEPTH) {
|
||||
@ -92,7 +143,7 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata = this.objectMetadataMap[objectName];
|
||||
const objectMetadata = this.objectMetadataMaps.byNameSingular[objectName];
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
@ -104,7 +155,7 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
const processedObjectRecord: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(objectRecord)) {
|
||||
const fieldMetadata = objectMetadata.fields[key];
|
||||
const fieldMetadata = objectMetadata.fieldsByName[key];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
processedObjectRecord[key] = value;
|
||||
@ -115,12 +166,19 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
if (Array.isArray(value)) {
|
||||
processedObjectRecord[key] = this.createConnection({
|
||||
objectRecords: value,
|
||||
parentObjectRecord: objectRecord,
|
||||
objectRecordsAggregatedValues:
|
||||
objectRecordsAggregatedValues[fieldMetadata.name],
|
||||
selectedAggregatedFields:
|
||||
selectedAggregatedFields[fieldMetadata.name],
|
||||
objectName: getRelationObjectMetadata(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMap,
|
||||
this.objectMetadataMaps,
|
||||
).nameSingular,
|
||||
take,
|
||||
totalCount: value.length,
|
||||
totalCount:
|
||||
objectRecordsAggregatedValues[fieldMetadata.name]?.totalCount ??
|
||||
value.length,
|
||||
order,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
@ -129,9 +187,13 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
||||
} else if (isPlainObject(value)) {
|
||||
processedObjectRecord[key] = this.processRecord({
|
||||
objectRecord: value,
|
||||
objectRecordsAggregatedValues:
|
||||
objectRecordsAggregatedValues[fieldMetadata.name],
|
||||
selectedAggregatedFields:
|
||||
selectedAggregatedFields[fieldMetadata.name],
|
||||
objectName: getRelationObjectMetadata(
|
||||
fieldMetadata,
|
||||
this.objectMetadataMap,
|
||||
this.objectMetadataMaps,
|
||||
).nameSingular,
|
||||
take,
|
||||
totalCount,
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import { SelectQueryBuilder } from 'typeorm';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
|
||||
export class ProcessAggregateHelper {
|
||||
public addSelectedAggregatedFieldsQueriesToQueryBuilder = ({
|
||||
fieldMetadataMapByName,
|
||||
selectedAggregatedFields,
|
||||
queryBuilder,
|
||||
}: {
|
||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>;
|
||||
selectedAggregatedFields: Record<string, AggregationField>;
|
||||
queryBuilder: SelectQueryBuilder<any>;
|
||||
}) => {
|
||||
queryBuilder.select([]);
|
||||
|
||||
for (const [aggregatedFieldName, aggregatedField] of Object.entries(
|
||||
selectedAggregatedFields,
|
||||
)) {
|
||||
const fieldMetadata = fieldMetadataMapByName[aggregatedField.fromField];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldName = fieldMetadata.name;
|
||||
const operation = aggregatedField.aggregationOperation;
|
||||
|
||||
queryBuilder.addSelect(
|
||||
`${operation}("${fieldName}")`,
|
||||
`${aggregatedFieldName}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,64 +1,95 @@
|
||||
import {
|
||||
DataSource,
|
||||
FindManyOptions,
|
||||
FindOptionsRelations,
|
||||
In,
|
||||
ObjectLiteral,
|
||||
Repository,
|
||||
SelectQueryBuilder,
|
||||
} from 'typeorm';
|
||||
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
||||
import {
|
||||
getRelationMetadata,
|
||||
getRelationObjectMetadata,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util';
|
||||
import {
|
||||
ObjectMetadataMap,
|
||||
ObjectMetadataMapItem,
|
||||
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
|
||||
|
||||
export class ProcessNestedRelationsHelper {
|
||||
constructor() {}
|
||||
private processAggregateHelper: ProcessAggregateHelper;
|
||||
|
||||
public async processNestedRelations<ObjectRecord extends IRecord = IRecord>(
|
||||
objectMetadataMap: ObjectMetadataMap,
|
||||
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||
parentObjectRecords: ObjectRecord[],
|
||||
relations: Record<string, FindOptionsRelations<ObjectLiteral>>,
|
||||
limit: number,
|
||||
authContext: any,
|
||||
dataSource: DataSource,
|
||||
): Promise<void> {
|
||||
constructor() {
|
||||
this.processAggregateHelper = new ProcessAggregateHelper();
|
||||
}
|
||||
|
||||
public async processNestedRelations<T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues = {},
|
||||
relations,
|
||||
aggregate = {},
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
parentObjectRecords: T[];
|
||||
parentObjectRecordsAggregatedValues?: Record<string, any>;
|
||||
relations: Record<string, FindOptionsRelations<ObjectLiteral>>;
|
||||
aggregate?: Record<string, AggregationField>;
|
||||
limit: number;
|
||||
authContext: any;
|
||||
dataSource: DataSource;
|
||||
}): Promise<void> {
|
||||
const processRelationTasks = Object.entries(relations).map(
|
||||
([relationName, nestedRelations]) =>
|
||||
this.processRelation(
|
||||
objectMetadataMap,
|
||||
this.processRelation({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationName,
|
||||
nestedRelations,
|
||||
aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(processRelationTasks);
|
||||
}
|
||||
|
||||
private async processRelation<ObjectRecord extends IRecord = IRecord>(
|
||||
objectMetadataMap: ObjectMetadataMap,
|
||||
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||
parentObjectRecords: ObjectRecord[],
|
||||
relationName: string,
|
||||
nestedRelations: any,
|
||||
limit: number,
|
||||
authContext: any,
|
||||
dataSource: DataSource,
|
||||
): Promise<void> {
|
||||
const relationFieldMetadata = parentObjectMetadataItem.fields[relationName];
|
||||
private async processRelation<T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationName,
|
||||
nestedRelations,
|
||||
aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
parentObjectRecords: T[];
|
||||
parentObjectRecordsAggregatedValues: Record<string, any>;
|
||||
relationName: string;
|
||||
nestedRelations: any;
|
||||
aggregate: Record<string, AggregationField>;
|
||||
limit: number;
|
||||
authContext: any;
|
||||
dataSource: DataSource;
|
||||
}): Promise<void> {
|
||||
const relationFieldMetadata =
|
||||
parentObjectMetadataItem.fieldsByName[relationName];
|
||||
const relationMetadata = getRelationMetadata(relationFieldMetadata);
|
||||
const relationDirection = deduceRelationDirection(
|
||||
relationFieldMetadata,
|
||||
@ -70,181 +101,341 @@ export class ProcessNestedRelationsHelper {
|
||||
? this.processToRelation
|
||||
: this.processFromRelation;
|
||||
|
||||
await processor.call(
|
||||
this,
|
||||
objectMetadataMap,
|
||||
await processor.call(this, {
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationName,
|
||||
nestedRelations,
|
||||
aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async processFromRelation<ObjectRecord extends IRecord = IRecord>(
|
||||
objectMetadataMap: ObjectMetadataMap,
|
||||
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||
parentObjectRecords: ObjectRecord[],
|
||||
relationName: string,
|
||||
nestedRelations: any,
|
||||
limit: number,
|
||||
authContext: any,
|
||||
dataSource: DataSource,
|
||||
): Promise<void> {
|
||||
private async processFromRelation<T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationName,
|
||||
nestedRelations,
|
||||
aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
parentObjectRecords: T[];
|
||||
parentObjectRecordsAggregatedValues: Record<string, any>;
|
||||
relationName: string;
|
||||
nestedRelations: any;
|
||||
aggregate: Record<string, AggregationField>;
|
||||
limit: number;
|
||||
authContext: any;
|
||||
dataSource: DataSource;
|
||||
}): Promise<void> {
|
||||
const { inverseRelationName, referenceObjectMetadata } =
|
||||
this.getRelationMetadata(
|
||||
objectMetadataMap,
|
||||
this.getRelationMetadata({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
relationName,
|
||||
);
|
||||
});
|
||||
const relationRepository = dataSource.getRepository(
|
||||
referenceObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
const relationIds = this.getUniqueIds(parentObjectRecords, 'id');
|
||||
const relationResults = await this.findRelations(
|
||||
relationRepository,
|
||||
inverseRelationName,
|
||||
relationIds,
|
||||
limit * parentObjectRecords.length,
|
||||
const referenceQueryBuilder = relationRepository.createQueryBuilder(
|
||||
referenceObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
this.assignRelationResults(
|
||||
parentObjectRecords,
|
||||
const relationIds = this.getUniqueIds({
|
||||
records: parentObjectRecords,
|
||||
idField: 'id',
|
||||
});
|
||||
const { relationResults, relationAggregatedFieldsResult } =
|
||||
await this.findRelations({
|
||||
referenceQueryBuilder,
|
||||
column: `"${inverseRelationName}Id"`,
|
||||
ids: relationIds,
|
||||
limit: limit * parentObjectRecords.length,
|
||||
objectMetadataMaps,
|
||||
referenceObjectMetadata,
|
||||
aggregate,
|
||||
relationName,
|
||||
});
|
||||
|
||||
this.assignFromRelationResults({
|
||||
parentRecords: parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationResults,
|
||||
relationAggregatedFieldsResult,
|
||||
relationName,
|
||||
`${inverseRelationName}Id`,
|
||||
);
|
||||
joinField: `${inverseRelationName}Id`,
|
||||
});
|
||||
|
||||
if (Object.keys(nestedRelations).length > 0) {
|
||||
await this.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMap[referenceObjectMetadata.nameSingular],
|
||||
relationResults as ObjectRecord[],
|
||||
nestedRelations as Record<string, FindOptionsRelations<ObjectLiteral>>,
|
||||
await this.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem:
|
||||
objectMetadataMaps.byNameSingular[
|
||||
referenceObjectMetadata.nameSingular
|
||||
],
|
||||
parentObjectRecords: relationResults as ObjectRecord[],
|
||||
parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult,
|
||||
relations: nestedRelations as Record<
|
||||
string,
|
||||
FindOptionsRelations<ObjectLiteral>
|
||||
>,
|
||||
aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async processToRelation<ObjectRecord extends IRecord = IRecord>(
|
||||
objectMetadataMap: ObjectMetadataMap,
|
||||
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||
parentObjectRecords: ObjectRecord[],
|
||||
relationName: string,
|
||||
nestedRelations: any,
|
||||
limit: number,
|
||||
authContext: any,
|
||||
dataSource: DataSource,
|
||||
): Promise<void> {
|
||||
const { referenceObjectMetadata } = this.getRelationMetadata(
|
||||
objectMetadataMap,
|
||||
private async processToRelation<T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationName,
|
||||
nestedRelations,
|
||||
aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
parentObjectRecords: T[];
|
||||
parentObjectRecordsAggregatedValues: Record<string, any>;
|
||||
relationName: string;
|
||||
nestedRelations: any;
|
||||
aggregate: Record<string, AggregationField>;
|
||||
limit: number;
|
||||
authContext: any;
|
||||
dataSource: DataSource;
|
||||
}): Promise<void> {
|
||||
const { referenceObjectMetadata } = this.getRelationMetadata({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
relationName,
|
||||
);
|
||||
});
|
||||
const relationRepository = dataSource.getRepository(
|
||||
referenceObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
const relationIds = this.getUniqueIds(
|
||||
parentObjectRecords,
|
||||
`${relationName}Id`,
|
||||
);
|
||||
const relationResults = await this.findRelations(
|
||||
relationRepository,
|
||||
'id',
|
||||
relationIds,
|
||||
limit,
|
||||
const referenceQueryBuilder = relationRepository.createQueryBuilder(
|
||||
referenceObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
this.assignToRelationResults(
|
||||
parentObjectRecords,
|
||||
const relationIds = this.getUniqueIds({
|
||||
records: parentObjectRecords,
|
||||
idField: `${relationName}Id`,
|
||||
});
|
||||
const { relationResults, relationAggregatedFieldsResult } =
|
||||
await this.findRelations({
|
||||
referenceQueryBuilder,
|
||||
column: 'id',
|
||||
ids: relationIds,
|
||||
limit,
|
||||
objectMetadataMaps,
|
||||
referenceObjectMetadata,
|
||||
aggregate,
|
||||
relationName,
|
||||
});
|
||||
|
||||
this.assignToRelationResults({
|
||||
parentRecords: parentObjectRecords,
|
||||
parentObjectRecordsAggregatedValues: parentObjectRecordsAggregatedValues,
|
||||
relationResults,
|
||||
relationAggregatedFieldsResult,
|
||||
relationName,
|
||||
);
|
||||
});
|
||||
|
||||
if (Object.keys(nestedRelations).length > 0) {
|
||||
await this.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMap[referenceObjectMetadata.nameSingular],
|
||||
relationResults as ObjectRecord[],
|
||||
nestedRelations as Record<string, FindOptionsRelations<ObjectLiteral>>,
|
||||
await this.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem:
|
||||
objectMetadataMaps.byNameSingular[
|
||||
referenceObjectMetadata.nameSingular
|
||||
],
|
||||
parentObjectRecords: relationResults as ObjectRecord[],
|
||||
parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult,
|
||||
relations: nestedRelations as Record<
|
||||
string,
|
||||
FindOptionsRelations<ObjectLiteral>
|
||||
>,
|
||||
aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getRelationMetadata(
|
||||
objectMetadataMap: ObjectMetadataMap,
|
||||
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||
relationName: string,
|
||||
) {
|
||||
const relationFieldMetadata = parentObjectMetadataItem.fields[relationName];
|
||||
private getRelationMetadata({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem,
|
||||
relationName,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||
relationName: string;
|
||||
}) {
|
||||
const relationFieldMetadata =
|
||||
parentObjectMetadataItem.fieldsByName[relationName];
|
||||
const relationMetadata = getRelationMetadata(relationFieldMetadata);
|
||||
const referenceObjectMetadata = getRelationObjectMetadata(
|
||||
relationFieldMetadata,
|
||||
objectMetadataMap,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
const inverseRelationName =
|
||||
objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[
|
||||
objectMetadataMaps.byId[relationMetadata.toObjectMetadataId]?.fieldsById[
|
||||
relationMetadata.toFieldMetadataId
|
||||
]?.name;
|
||||
|
||||
return { inverseRelationName, referenceObjectMetadata };
|
||||
}
|
||||
|
||||
private getUniqueIds(records: IRecord[], idField: string): any[] {
|
||||
private getUniqueIds({
|
||||
records,
|
||||
idField,
|
||||
}: {
|
||||
records: ObjectRecord[];
|
||||
idField: string;
|
||||
}): any[] {
|
||||
return [...new Set(records.map((item) => item[idField]))];
|
||||
}
|
||||
|
||||
private async findRelations(
|
||||
repository: Repository<any>,
|
||||
field: string,
|
||||
ids: any[],
|
||||
limit: number,
|
||||
): Promise<any[]> {
|
||||
private async findRelations({
|
||||
referenceQueryBuilder,
|
||||
column,
|
||||
ids,
|
||||
limit,
|
||||
objectMetadataMaps,
|
||||
referenceObjectMetadata,
|
||||
aggregate,
|
||||
relationName,
|
||||
}: {
|
||||
referenceQueryBuilder: SelectQueryBuilder<any>;
|
||||
column: string;
|
||||
ids: any[];
|
||||
limit: number;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
referenceObjectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||
aggregate: Record<string, any>;
|
||||
relationName: string;
|
||||
}): Promise<{ relationResults: any[]; relationAggregatedFieldsResult: any }> {
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
return { relationResults: [], relationAggregatedFieldsResult: {} };
|
||||
}
|
||||
const findOptions: FindManyOptions = {
|
||||
where: { [field]: In(ids) },
|
||||
take: limit,
|
||||
};
|
||||
|
||||
return repository.find(findOptions);
|
||||
const aggregateForRelation = aggregate[relationName];
|
||||
let relationAggregatedFieldsResult: Record<string, any> = {};
|
||||
|
||||
if (aggregateForRelation) {
|
||||
const aggregateQueryBuilder = referenceQueryBuilder.clone();
|
||||
|
||||
this.processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder(
|
||||
{
|
||||
fieldMetadataMapByName: referenceObjectMetadata.fieldsByName,
|
||||
selectedAggregatedFields: aggregateForRelation,
|
||||
queryBuilder: aggregateQueryBuilder,
|
||||
},
|
||||
);
|
||||
|
||||
const aggregatedFieldsValues = await aggregateQueryBuilder
|
||||
.addSelect(column)
|
||||
.where(`${column} IN (:...ids)`, {
|
||||
ids,
|
||||
})
|
||||
.groupBy(column)
|
||||
.getRawMany();
|
||||
|
||||
relationAggregatedFieldsResult = aggregatedFieldsValues.reduce(
|
||||
(acc, item) => {
|
||||
const columnWithoutQuotes = column.replace(/["']/g, '');
|
||||
const key = item[columnWithoutQuotes];
|
||||
const { [column]: _, ...itemWithoutColumn } = item;
|
||||
|
||||
acc[key] = itemWithoutColumn;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
const result = await referenceQueryBuilder
|
||||
.where(`${column} IN (:...ids)`, {
|
||||
ids,
|
||||
})
|
||||
.take(limit)
|
||||
.getMany();
|
||||
|
||||
const relationResults = formatResult(
|
||||
result,
|
||||
referenceObjectMetadata,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
return { relationResults, relationAggregatedFieldsResult };
|
||||
}
|
||||
|
||||
private assignRelationResults(
|
||||
parentRecords: IRecord[],
|
||||
relationResults: any[],
|
||||
relationName: string,
|
||||
joinField: string,
|
||||
): void {
|
||||
private assignFromRelationResults({
|
||||
parentRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationResults,
|
||||
relationAggregatedFieldsResult,
|
||||
relationName,
|
||||
joinField,
|
||||
}: {
|
||||
parentRecords: ObjectRecord[];
|
||||
parentObjectRecordsAggregatedValues: Record<string, any>;
|
||||
relationResults: any[];
|
||||
relationAggregatedFieldsResult: Record<string, any>;
|
||||
relationName: string;
|
||||
joinField: string;
|
||||
}): void {
|
||||
parentRecords.forEach((item) => {
|
||||
(item as any)[relationName] = relationResults.filter(
|
||||
item[relationName] = relationResults.filter(
|
||||
(rel) => rel[joinField] === item.id,
|
||||
);
|
||||
});
|
||||
|
||||
parentObjectRecordsAggregatedValues[relationName] =
|
||||
relationAggregatedFieldsResult;
|
||||
}
|
||||
|
||||
private assignToRelationResults(
|
||||
parentRecords: IRecord[],
|
||||
relationResults: any[],
|
||||
relationName: string,
|
||||
): void {
|
||||
private assignToRelationResults({
|
||||
parentRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relationResults,
|
||||
relationAggregatedFieldsResult,
|
||||
relationName,
|
||||
}: {
|
||||
parentRecords: ObjectRecord[];
|
||||
parentObjectRecordsAggregatedValues: Record<string, any>;
|
||||
relationResults: any[];
|
||||
relationAggregatedFieldsResult: Record<string, any>;
|
||||
relationName: string;
|
||||
}): void {
|
||||
parentRecords.forEach((item) => {
|
||||
if (relationResults.length === 0) {
|
||||
(item as any)[`${relationName}Id`] = null;
|
||||
item[`${relationName}Id`] = null;
|
||||
}
|
||||
(item as any)[relationName] =
|
||||
item[relationName] =
|
||||
relationResults.find((rel) => rel.id === item[`${relationName}Id`]) ??
|
||||
null;
|
||||
});
|
||||
|
||||
parentObjectRecordsAggregatedValues[relationName] =
|
||||
relationAggregatedFieldsResult;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import graphqlFields from 'graphql-fields';
|
||||
import { In, InsertResult } from 'typeorm';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -19,35 +19,40 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryCreateManyResolverService
|
||||
implements ResolverService<CreateManyResolverArgs, IRecord[]>
|
||||
implements ResolverService<CreateManyResolverArgs, ObjectRecord[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const { authContext, info, objectMetadataMap, objectMetadataMapItem } =
|
||||
options;
|
||||
): Promise<T[]> {
|
||||
const {
|
||||
authContext,
|
||||
info,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
} = options;
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
@ -59,7 +64,7 @@ export class GraphqlQueryCreateManyResolverService
|
||||
});
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const nonFormattedUpsertedRecords = (await queryBuilder
|
||||
@ -71,42 +76,42 @@ export class GraphqlQueryCreateManyResolverService
|
||||
|
||||
const upsertedRecords = formatResult(
|
||||
nonFormattedUpsertedRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
upsertedRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: upsertedRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return upsertedRecords.map((record: ObjectRecord) =>
|
||||
return upsertedRecords.map((record: T) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async validate<ObjectRecord extends IRecord>(
|
||||
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
async validate<T extends ObjectRecord>(
|
||||
args: CreateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItem);
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
|
||||
|
||||
args.data.forEach((record) => {
|
||||
if (record?.id) {
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -16,46 +16,51 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryDestroyManyResolverService
|
||||
implements ResolverService<DestroyManyResolverArgs, IRecord[]>
|
||||
implements ResolverService<DestroyManyResolverArgs, ObjectRecord[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: DestroyManyResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T[]> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter,
|
||||
);
|
||||
|
||||
@ -66,31 +71,31 @@ export class GraphqlQueryDestroyManyResolverService
|
||||
|
||||
const deletedRecords = formatResult(
|
||||
nonFormattedDeletedObjectRecords.raw,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
deletedRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: deletedRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return deletedRecords.map((record: ObjectRecord) =>
|
||||
return deletedRecords.map((record: T) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -20,45 +20,50 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryDestroyOneResolverService
|
||||
implements ResolverService<DestroyOneResolverArgs, IRecord>
|
||||
implements ResolverService<DestroyOneResolverArgs, ObjectRecord>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: DestroyOneResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
.where(`"${objectMetadataMapItem.nameSingular}".id = :id`, {
|
||||
.where(`"${objectMetadataItemWithFieldMaps.nameSingular}".id = :id`, {
|
||||
id: args.id,
|
||||
})
|
||||
.take(1)
|
||||
@ -75,30 +80,30 @@ export class GraphqlQueryDestroyOneResolverService
|
||||
|
||||
const recordBeforeDeletion = formatResult(
|
||||
nonFormattedDeletedObjectRecords.raw,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
)[0];
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
[recordBeforeDeletion],
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: [recordBeforeDeletion],
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: recordBeforeDeletion,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
@ -5,10 +5,10 @@ import { In } from 'typeorm';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
OrderByDirection,
|
||||
RecordFilter,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@ -21,7 +21,7 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/
|
||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||
import { settings } from 'src/engine/constants/settings';
|
||||
import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants';
|
||||
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
@ -29,60 +29,63 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindDuplicatesResolverService
|
||||
implements
|
||||
ResolverService<FindDuplicatesResolverArgs, IConnection<IRecord>[]>
|
||||
ResolverService<FindDuplicatesResolverArgs, IConnection<ObjectRecord>[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: FindDuplicatesResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>[]> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap } = options;
|
||||
): Promise<IConnection<T>[]> {
|
||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||
options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
const existingRecordsQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
const duplicateRecordsQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMap[objectMetadataMapItem.nameSingular].fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataMaps.byNameSingular[
|
||||
objectMetadataItemWithFieldMaps.nameSingular
|
||||
].fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
let objectRecords: Partial<ObjectRecord>[] = [];
|
||||
let objectRecords: Partial<T>[] = [];
|
||||
|
||||
if (args.ids) {
|
||||
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
|
||||
.where({ id: In(args.ids) })
|
||||
.getMany()) as ObjectRecord[];
|
||||
.getMany()) as T[];
|
||||
|
||||
objectRecords = formatResult(
|
||||
nonFormattedObjectRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
} else if (args.data && !isEmpty(args.data)) {
|
||||
objectRecords = formatData(args.data, objectMetadataMapItem);
|
||||
objectRecords = formatData(args.data, objectMetadataItemWithFieldMaps);
|
||||
}
|
||||
|
||||
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
|
||||
const duplicateConnections: IConnection<T>[] = await Promise.all(
|
||||
objectRecords.map(async (record) => {
|
||||
const duplicateConditions = this.buildDuplicateConditions(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
[record],
|
||||
record.id,
|
||||
);
|
||||
@ -90,7 +93,7 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
if (isEmpty(duplicateConditions)) {
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: [],
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 0,
|
||||
totalCount: 0,
|
||||
order: [{ id: OrderByDirection.AscNullsFirst }],
|
||||
@ -101,22 +104,22 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
duplicateRecordsQueryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
duplicateConditions,
|
||||
);
|
||||
|
||||
const nonFormattedDuplicates =
|
||||
(await withFilterQueryBuilder.getMany()) as ObjectRecord[];
|
||||
(await withFilterQueryBuilder.getMany()) as T[];
|
||||
|
||||
const duplicates = formatResult(
|
||||
nonFormattedDuplicates,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: duplicates,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: duplicates.length,
|
||||
totalCount: duplicates.length,
|
||||
order: [{ id: OrderByDirection.AscNullsFirst }],
|
||||
@ -130,16 +133,16 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
}
|
||||
|
||||
private buildDuplicateConditions(
|
||||
objectMetadataMapItem: ObjectMetadataMapItem,
|
||||
records?: Partial<IRecord>[] | undefined,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
records?: Partial<ObjectRecord>[] | undefined,
|
||||
filteringByExistingRecordId?: string,
|
||||
): Partial<RecordFilter> {
|
||||
): Partial<ObjectRecordFilter> {
|
||||
if (!records || records.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const criteriaCollection = this.getApplicableDuplicateCriteriaCollection(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
const conditions = records.flatMap((record) => {
|
||||
@ -164,7 +167,7 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
});
|
||||
});
|
||||
|
||||
const filter: Partial<RecordFilter> = {};
|
||||
const filter: Partial<ObjectRecordFilter> = {};
|
||||
|
||||
if (conditions && !isEmpty(conditions)) {
|
||||
filter.or = conditions;
|
||||
@ -178,11 +181,12 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
}
|
||||
|
||||
private getApplicableDuplicateCriteriaCollection(
|
||||
objectMetadataMapItem: ObjectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
) {
|
||||
return DUPLICATE_CRITERIA_COLLECTION.filter(
|
||||
(duplicateCriteria) =>
|
||||
duplicateCriteria.objectName === objectMetadataMapItem.nameSingular,
|
||||
duplicateCriteria.objectName ===
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
ObjectRecordOrderBy,
|
||||
OrderByDirection,
|
||||
RecordFilter,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@ -19,35 +18,45 @@ import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||
import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter';
|
||||
import {
|
||||
getCursor,
|
||||
getPaginationInfo,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindManyResolverService
|
||||
implements ResolverService<FindManyResolverArgs, IConnection<IRecord>>
|
||||
implements ResolverService<FindManyResolverArgs, IConnection<ObjectRecord>>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async resolve<
|
||||
ObjectRecord extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
OrderBy extends RecordOrderBy = RecordOrderBy,
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
Filter extends ObjectRecordFilter = ObjectRecordFilter,
|
||||
OrderBy extends ObjectRecordOrderBy = ObjectRecordOrderBy,
|
||||
>(
|
||||
args: FindManyResolverArgs<Filter, OrderBy>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>> {
|
||||
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
|
||||
options;
|
||||
): Promise<IConnection<T>> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
info,
|
||||
objectMetadataMaps,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -55,47 +64,43 @@ export class GraphqlQueryFindManyResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const countQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
const aggregateQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
countQueryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
selectedFields,
|
||||
);
|
||||
const isForwardPagination = !isDefined(args.before);
|
||||
|
||||
const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS;
|
||||
|
||||
const withDeletedCountQueryBuilder =
|
||||
graphqlQueryParser.applyDeletedAtToBuilder(
|
||||
withFilterCountQueryBuilder,
|
||||
const withFilterAggregateQueryBuilder =
|
||||
graphqlQueryParser.applyFilterToBuilder(
|
||||
aggregateQueryBuilder,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const totalCount = isDefined(selectedFields.totalCount)
|
||||
? await withDeletedCountQueryBuilder.getCount()
|
||||
: 0;
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult =
|
||||
graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
const isForwardPagination = !isDefined(args.before);
|
||||
|
||||
const withDeletedAggregateQueryBuilder =
|
||||
graphqlQueryParser.applyDeletedAtToBuilder(
|
||||
withFilterAggregateQueryBuilder,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const cursor = getCursor(args);
|
||||
|
||||
@ -110,7 +115,7 @@ export class GraphqlQueryFindManyResolverService
|
||||
const cursorArgFilter = computeCursorArgFilter(
|
||||
cursor,
|
||||
orderByWithIdCondition,
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
isForwardPagination,
|
||||
);
|
||||
|
||||
@ -123,14 +128,14 @@ export class GraphqlQueryFindManyResolverService
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
appliedFilters,
|
||||
);
|
||||
|
||||
const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder(
|
||||
withFilterQueryBuilder,
|
||||
orderByWithIdCondition,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
isForwardPagination,
|
||||
);
|
||||
|
||||
@ -139,14 +144,36 @@ export class GraphqlQueryFindManyResolverService
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const isAggregationsEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsAggregateQueryEnabled,
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
if (!isAggregationsEnabled) {
|
||||
graphqlQuerySelectedFieldsResult.aggregate = {
|
||||
totalCount: graphqlQuerySelectedFieldsResult.aggregate.totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
const processAggregateHelper = new ProcessAggregateHelper();
|
||||
|
||||
processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({
|
||||
fieldMetadataMapByName: objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
queryBuilder: withDeletedAggregateQueryBuilder,
|
||||
});
|
||||
|
||||
const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS;
|
||||
|
||||
const nonFormattedObjectRecords = await withDeletedQueryBuilder
|
||||
.take(limit + 1)
|
||||
.getMany();
|
||||
|
||||
const objectRecords = formatResult(
|
||||
nonFormattedObjectRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
||||
@ -159,37 +186,42 @@ export class GraphqlQueryFindManyResolverService
|
||||
objectRecords.pop();
|
||||
}
|
||||
|
||||
const parentObjectRecordsAggregatedValues =
|
||||
await withDeletedAggregateQueryBuilder.getRawOne();
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
objectRecords,
|
||||
relations,
|
||||
if (graphqlQuerySelectedFieldsResult.relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: objectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relations: graphqlQuerySelectedFieldsResult.relations,
|
||||
aggregate: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
const result = typeORMObjectRecordsParser.createConnection({
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues,
|
||||
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: limit,
|
||||
totalCount,
|
||||
totalCount: parentObjectRecordsAggregatedValues.totalCount,
|
||||
order: orderByWithIdCondition,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async validate<Filter extends RecordFilter>(
|
||||
async validate<Filter extends ObjectRecordFilter>(
|
||||
args: FindManyResolverArgs<Filter>,
|
||||
_options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
|
||||
@ -4,9 +4,9 @@ import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
RecordFilter,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -27,21 +27,25 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindOneResolverService
|
||||
implements ResolverService<FindOneResolverArgs, IRecord>
|
||||
implements ResolverService<FindOneResolverArgs, ObjectRecord>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<
|
||||
ObjectRecord extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
Filter extends ObjectRecordFilter = ObjectRecordFilter,
|
||||
>(
|
||||
args: FindOneResolverArgs<Filter>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
|
||||
options;
|
||||
): Promise<T> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
info,
|
||||
objectMetadataMaps,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -49,28 +53,28 @@ export class GraphqlQueryFindOneResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
@ -83,8 +87,8 @@ export class GraphqlQueryFindOneResolverService
|
||||
|
||||
const objectRecord = formatResult(
|
||||
nonFormattedObjectRecord,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
if (!objectRecord) {
|
||||
@ -99,29 +103,29 @@ export class GraphqlQueryFindOneResolverService
|
||||
const objectRecords = [objectRecord];
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
objectRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: objectRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: objectRecords[0],
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}) as ObjectRecord;
|
||||
}) as T;
|
||||
}
|
||||
|
||||
async validate<Filter extends RecordFilter>(
|
||||
async validate<Filter extends ObjectRecordFilter>(
|
||||
args: FindOneResolverArgs<Filter>,
|
||||
_options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
|
||||
@ -5,10 +5,10 @@ import { Brackets } from 'typeorm';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
OrderByDirection,
|
||||
RecordFilter,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { SearchResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@ -22,40 +22,39 @@ import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQuerySearchResolverService
|
||||
implements ResolverService<SearchResolverArgs, IConnection<IRecord>>
|
||||
implements ResolverService<SearchResolverArgs, IConnection<ObjectRecord>>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<
|
||||
ObjectRecord extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
Filter extends ObjectRecordFilter = ObjectRecordFilter,
|
||||
>(
|
||||
args: SearchResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>> {
|
||||
): Promise<IConnection<T>> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItem,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const repository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
authContext.workspace.id,
|
||||
objectMetadataItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
if (!isDefined(args.searchInput)) {
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: [],
|
||||
objectName: objectMetadataItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 0,
|
||||
totalCount: 0,
|
||||
order: [{ id: OrderByDirection.AscNullsFirst }],
|
||||
@ -69,16 +68,16 @@ export class GraphqlQuerySearchResolverService
|
||||
const limit = args?.limit ?? QUERY_MAX_RECORDS;
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
@ -109,7 +108,7 @@ export class GraphqlQuerySearchResolverService
|
||||
.setParameter('searchTerms', searchTerms)
|
||||
.setParameter('searchTermsOr', searchTermsOr)
|
||||
.take(limit)
|
||||
.getMany()) as ObjectRecord[];
|
||||
.getMany()) as T[];
|
||||
|
||||
const objectRecords = await repository.formatResult(resultsWithTsVector);
|
||||
|
||||
@ -122,7 +121,7 @@ export class GraphqlQuerySearchResolverService
|
||||
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: objectRecords ?? [],
|
||||
objectName: objectMetadataItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: limit,
|
||||
totalCount,
|
||||
order,
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -20,18 +20,22 @@ import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryUpdateManyResolverService
|
||||
implements ResolverService<UpdateManyResolverArgs, IRecord[]>
|
||||
implements ResolverService<UpdateManyResolverArgs, ObjectRecord[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T[]> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -39,28 +43,28 @@ export class GraphqlQueryUpdateManyResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataMapItem.isCustom,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
@ -69,7 +73,7 @@ export class GraphqlQueryUpdateManyResolverService
|
||||
args.filter,
|
||||
);
|
||||
|
||||
const data = formatData(args.data, objectMetadataMapItem);
|
||||
const data = formatData(args.data, objectMetadataItemWithFieldMaps);
|
||||
|
||||
const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder
|
||||
.update(data)
|
||||
@ -78,42 +82,42 @@ export class GraphqlQueryUpdateManyResolverService
|
||||
|
||||
const updatedRecords = formatResult(
|
||||
nonFormattedUpdatedObjectRecords.raw,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
updatedRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: updatedRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return updatedRecords.map((record: ObjectRecord) =>
|
||||
return updatedRecords.map((record: T) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async validate<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
async validate<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
|
||||
if (!args.filter) {
|
||||
throw new Error('Filter is required');
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -23,18 +23,22 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryUpdateOneResolverService
|
||||
implements ResolverService<UpdateOneResolverArgs, IRecord>
|
||||
implements ResolverService<UpdateOneResolverArgs, ObjectRecord>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -42,26 +46,26 @@ export class GraphqlQueryUpdateOneResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const data = formatData(args.data, objectMetadataMapItem);
|
||||
const data = formatData(args.data, objectMetadataItemWithFieldMaps);
|
||||
|
||||
const result = await queryBuilder
|
||||
.update(data)
|
||||
@ -73,8 +77,8 @@ export class GraphqlQueryUpdateOneResolverService
|
||||
|
||||
const updatedRecords = formatResult(
|
||||
nonFormattedUpdatedObjectRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
if (updatedRecords.length === 0) {
|
||||
@ -84,38 +88,38 @@ export class GraphqlQueryUpdateOneResolverService
|
||||
);
|
||||
}
|
||||
|
||||
const updatedRecord = updatedRecords[0] as ObjectRecord;
|
||||
const updatedRecord = updatedRecords[0] as T;
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
[updatedRecord],
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: [updatedRecord],
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord<ObjectRecord>({
|
||||
return typeORMObjectRecordsParser.processRecord<T>({
|
||||
objectRecord: updatedRecord,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
}
|
||||
|
||||
async validate<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
async validate<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
|
||||
assertIsValidUuid(args.id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class ApiEventEmitterService {
|
||||
constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {}
|
||||
|
||||
public emitCreateEvents<T extends IRecord>(
|
||||
public emitCreateEvents<T extends ObjectRecord>(
|
||||
records: T[],
|
||||
authContext: AuthContext,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
@ -32,7 +32,7 @@ export class ApiEventEmitterService {
|
||||
);
|
||||
}
|
||||
|
||||
public emitUpdateEvents<T extends IRecord>(
|
||||
public emitUpdateEvents<T extends ObjectRecord>(
|
||||
existingRecords: T[],
|
||||
records: T[],
|
||||
updatedFields: string[],
|
||||
@ -77,7 +77,7 @@ export class ApiEventEmitterService {
|
||||
);
|
||||
}
|
||||
|
||||
public emitDeletedEvents<T extends IRecord>(
|
||||
public emitDeletedEvents<T extends ObjectRecord>(
|
||||
records: T[],
|
||||
authContext: AuthContext,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
@ -99,7 +99,7 @@ export class ApiEventEmitterService {
|
||||
);
|
||||
}
|
||||
|
||||
public emitDestroyEvents<T extends IRecord>(
|
||||
public emitDestroyEvents<T extends ObjectRecord>(
|
||||
records: T[],
|
||||
authContext: AuthContext,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
@ -121,9 +121,7 @@ export class ApiEventEmitterService {
|
||||
);
|
||||
}
|
||||
|
||||
private removeGraphQLAndNestedProperties<ObjectRecord extends IRecord>(
|
||||
record: ObjectRecord,
|
||||
) {
|
||||
private removeGraphQLAndNestedProperties<T extends ObjectRecord>(record: T) {
|
||||
if (!record) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import {
|
||||
ObjectRecordFilter,
|
||||
ObjectRecordOrderBy,
|
||||
OrderByDirection,
|
||||
RecordFilter,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
@ -11,14 +11,14 @@ import {
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { FieldMetadataType } 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 { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
|
||||
export const computeCursorArgFilter = (
|
||||
cursor: Record<string, any>,
|
||||
orderBy: RecordOrderBy,
|
||||
fieldMetadataMap: FieldMetadataMap,
|
||||
orderBy: ObjectRecordOrderBy,
|
||||
fieldMetadataMapByName: FieldMetadataMap,
|
||||
isForwardPagination = true,
|
||||
): RecordFilter[] => {
|
||||
): ObjectRecordFilter[] => {
|
||||
const cursorKeys = Object.keys(cursor ?? {});
|
||||
const cursorValues = Object.values(cursor ?? {});
|
||||
|
||||
@ -39,7 +39,7 @@ export const computeCursorArgFilter = (
|
||||
...buildWhereCondition(
|
||||
cursorKeys[subConditionIndex],
|
||||
cursorValues[subConditionIndex],
|
||||
fieldMetadataMap,
|
||||
fieldMetadataMapByName,
|
||||
'eq',
|
||||
),
|
||||
};
|
||||
@ -68,18 +68,18 @@ export const computeCursorArgFilter = (
|
||||
|
||||
return {
|
||||
...whereCondition,
|
||||
...buildWhereCondition(key, value, fieldMetadataMap, operator),
|
||||
} as RecordFilter;
|
||||
...buildWhereCondition(key, value, fieldMetadataMapByName, operator),
|
||||
} as ObjectRecordFilter;
|
||||
});
|
||||
};
|
||||
|
||||
const buildWhereCondition = (
|
||||
key: string,
|
||||
value: any,
|
||||
fieldMetadataMap: FieldMetadataMap,
|
||||
fieldMetadataMapByName: FieldMetadataMap,
|
||||
operator: string,
|
||||
): Record<string, any> => {
|
||||
const fieldMetadata = fieldMetadataMap[key];
|
||||
const fieldMetadata = fieldMetadataMapByName[key];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Record as IRecord,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
ObjectRecord,
|
||||
ObjectRecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import {
|
||||
@ -24,9 +24,9 @@ export const decodeCursor = (cursor: string): CursorData => {
|
||||
}
|
||||
};
|
||||
|
||||
export const encodeCursor = <ObjectRecord extends IRecord = IRecord>(
|
||||
objectRecord: ObjectRecord,
|
||||
order: RecordOrderBy | undefined,
|
||||
export const encodeCursor = <T extends ObjectRecord = ObjectRecord>(
|
||||
objectRecord: T,
|
||||
order: ObjectRecordOrderBy | undefined,
|
||||
): string => {
|
||||
const orderByValues: Record<string, any> = {};
|
||||
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
|
||||
export const getObjectMetadataOrThrow = (
|
||||
objectMetadataMap: Record<string, any>,
|
||||
objectName: string,
|
||||
): ObjectMetadataMapItem => {
|
||||
const objectMetadata = objectMetadataMap[objectName];
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Object metadata not found for ${objectName}`,
|
||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return objectMetadata;
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import {
|
||||
deduceRelationDirection,
|
||||
RelationDirection,
|
||||
@ -9,7 +9,7 @@ import {
|
||||
|
||||
export const getRelationObjectMetadata = (
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
objectMetadataMap: ObjectMetadataMap,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
) => {
|
||||
const relationMetadata = getRelationMetadata(fieldMetadata);
|
||||
|
||||
@ -20,8 +20,8 @@ export const getRelationObjectMetadata = (
|
||||
|
||||
const referencedObjectMetadata =
|
||||
relationDirection === RelationDirection.TO
|
||||
? objectMetadataMap[relationMetadata.fromObjectMetadataId]
|
||||
: objectMetadataMap[relationMetadata.toObjectMetadataId];
|
||||
? objectMetadataMaps.byId[relationMetadata.fromObjectMetadataId]
|
||||
: objectMetadataMaps.byId[relationMetadata.toObjectMetadataId];
|
||||
|
||||
if (!referencedObjectMetadata) {
|
||||
throw new Error(
|
||||
|
||||
Reference in New Issue
Block a user