mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 12:02:10 +03:00
Add composite fields to aggregation (#8518)
## Context This PR introduces a first aggregation for a composite field ## Test <img width="1074" alt="Screenshot 2024-11-15 at 15 37 05" src="https://github.com/user-attachments/assets/db2563f9-26b7-421f-9431-48fc13bce49e">
This commit is contained in:
parent
2f5dc26545
commit
0f1cf0e4e9
@ -1,16 +1,14 @@
|
||||
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';
|
||||
import { formatColumnNameFromCompositeFieldAndSubfield } from 'src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
export class ProcessAggregateHelper {
|
||||
public addSelectedAggregatedFieldsQueriesToQueryBuilder = ({
|
||||
fieldMetadataMapByName,
|
||||
selectedAggregatedFields,
|
||||
queryBuilder,
|
||||
}: {
|
||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>;
|
||||
selectedAggregatedFields: Record<string, AggregationField>;
|
||||
queryBuilder: SelectQueryBuilder<any>;
|
||||
}) => {
|
||||
@ -19,17 +17,21 @@ export class ProcessAggregateHelper {
|
||||
for (const [aggregatedFieldName, aggregatedField] of Object.entries(
|
||||
selectedAggregatedFields,
|
||||
)) {
|
||||
const fieldMetadata = fieldMetadataMapByName[aggregatedField.fromField];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
if (
|
||||
!isDefined(aggregatedField?.fromField) ||
|
||||
!isDefined(aggregatedField?.aggregationOperation)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldName = fieldMetadata.name;
|
||||
const columnName = formatColumnNameFromCompositeFieldAndSubfield(
|
||||
aggregatedField.fromField,
|
||||
aggregatedField.fromSubField,
|
||||
);
|
||||
const operation = aggregatedField.aggregationOperation;
|
||||
|
||||
queryBuilder.addSelect(
|
||||
`${operation}("${fieldName}")`,
|
||||
`${operation}("${columnName}")`,
|
||||
`${aggregatedFieldName}`,
|
||||
);
|
||||
}
|
||||
|
@ -344,7 +344,6 @@ export class ProcessNestedRelationsHelper {
|
||||
|
||||
this.processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder(
|
||||
{
|
||||
fieldMetadataMapByName: referenceObjectMetadata.fieldsByName,
|
||||
selectedAggregatedFields: aggregateForRelation,
|
||||
queryBuilder: aggregateQueryBuilder,
|
||||
},
|
||||
|
@ -159,7 +159,6 @@ export class GraphqlQueryFindManyResolverService
|
||||
const processAggregateHelper = new ProcessAggregateHelper();
|
||||
|
||||
processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({
|
||||
fieldMetadataMapByName: objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
queryBuilder: withDeletedAggregateQueryBuilder,
|
||||
});
|
||||
@ -214,7 +213,7 @@ export class GraphqlQueryFindManyResolverService
|
||||
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: limit,
|
||||
totalCount: parentObjectRecordsAggregatedValues.totalCount,
|
||||
totalCount: parentObjectRecordsAggregatedValues?.totalCount,
|
||||
order: orderByWithIdCondition,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
|
@ -19,6 +19,7 @@ export type AggregationField = {
|
||||
type: GraphQLScalarType;
|
||||
description: string;
|
||||
fromField: string;
|
||||
fromSubField?: string;
|
||||
aggregationOperation: AGGREGATION_OPERATIONS;
|
||||
};
|
||||
|
||||
@ -79,6 +80,16 @@ export const getAvailableAggregationsFromObjectFields = (
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.CURRENCY) {
|
||||
acc[`avg${capitalize(field.name)}AmountMicros`] = {
|
||||
type: GraphQLFloat,
|
||||
description: `Average amount contained in the field ${field.name}`,
|
||||
fromField: field.name,
|
||||
fromSubField: 'amountMicros',
|
||||
aggregationOperation: AGGREGATION_OPERATIONS.avg,
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { formatColumnNameFromCompositeFieldAndSubfield } from 'src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util';
|
||||
|
||||
describe('formatColumnNameFromCompositeFieldAndSubfield', () => {
|
||||
it('should return fieldName when subFieldName is not defined', () => {
|
||||
const result = formatColumnNameFromCompositeFieldAndSubfield('firstName');
|
||||
|
||||
expect(result).toBe('firstName');
|
||||
});
|
||||
|
||||
it('should return concatenated fieldName and capitalized subFieldName when subFieldName is defined', () => {
|
||||
const result = formatColumnNameFromCompositeFieldAndSubfield(
|
||||
'user',
|
||||
'firstName',
|
||||
);
|
||||
|
||||
expect(result).toBe('userFirstName');
|
||||
});
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
export const formatColumnNameFromCompositeFieldAndSubfield = (
|
||||
fieldName: string,
|
||||
subFieldName?: string,
|
||||
): string => {
|
||||
if (isDefined(subFieldName)) {
|
||||
return `${fieldName}${capitalize(subFieldName)}`;
|
||||
}
|
||||
|
||||
return fieldName;
|
||||
};
|
Loading…
Reference in New Issue
Block a user