Aggregated queries #1 (#8345)

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:
Marie
2024-11-14 18:05:05 +01:00
committed by GitHub
parent c966533f26
commit a799370483
93 changed files with 1590 additions and 1178 deletions

View File

@ -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,

View File

@ -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}`,
);
}
};
}

View File

@ -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;
}
}