Marie
2025-01-06 17:57:32 +01:00
committed by GitHub
parent b22a598d7d
commit a9b95bcf03
30 changed files with 503 additions and 328 deletions

View File

@ -3,7 +3,7 @@ import { SelectQueryBuilder } from 'typeorm';
import { AGGREGATE_OPERATIONS } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
import { FIELD_METADATA_TYPES_TO_TEXT_COLUMN_TYPE } from 'src/engine/metadata-modules/workspace-migration/constants/fieldMetadataTypesToTextColumnType';
import { formatColumnNameFromCompositeFieldAndSubfield } from 'src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util';
import { formatColumnNamesFromCompositeFieldAndSubfields } from 'src/engine/twenty-orm/utils/format-column-names-from-composite-field-and-subfield.util';
import { isDefined } from 'src/utils/is-defined';
export class ProcessAggregateHelper {
@ -26,11 +26,20 @@ export class ProcessAggregateHelper {
continue;
}
const columnName = formatColumnNameFromCompositeFieldAndSubfield(
const columnNames = formatColumnNamesFromCompositeFieldAndSubfields(
aggregatedField.fromField,
aggregatedField.fromSubField,
aggregatedField.fromSubFields,
);
const columnNameForNumericOperation = isDefined(
aggregatedField.subFieldForNumericOperation,
)
? formatColumnNamesFromCompositeFieldAndSubfields(
aggregatedField.fromField,
[aggregatedField.subFieldForNumericOperation],
)[0]
: columnNames[0];
if (
!Object.values(AGGREGATE_OPERATIONS).includes(
aggregatedField.aggregateOperation,
@ -39,49 +48,54 @@ export class ProcessAggregateHelper {
continue;
}
const columnEmptyValueExpression =
const concatenatedColumns = columnNames
.map((col) => `"${col}"`)
.join(", ' ', ");
const columnExpression =
FIELD_METADATA_TYPES_TO_TEXT_COLUMN_TYPE.includes(
aggregatedField.fromFieldType,
)
? `NULLIF("${columnName}", '')`
: `"${columnName}"`;
? `NULLIF(CONCAT(${concatenatedColumns}), '')`
: `CONCAT(${concatenatedColumns})`;
switch (aggregatedField.aggregateOperation) {
case AGGREGATE_OPERATIONS.countEmpty:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(*) - COUNT(${columnEmptyValueExpression}) END`,
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(*) - COUNT(${columnExpression}) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.countNotEmpty:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(${columnEmptyValueExpression}) END`,
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(${columnExpression}) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.countUniqueValues:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(DISTINCT "${columnName}") END`,
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(DISTINCT ${columnExpression}) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.percentageEmpty:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST(((COUNT(*) - COUNT(${columnEmptyValueExpression})::decimal) / COUNT(*)) AS DECIMAL) END`,
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST(((COUNT(*) - COUNT(${columnExpression})::decimal) / COUNT(*)) AS DECIMAL) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.percentageNotEmpty:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST((COUNT(${columnEmptyValueExpression})::decimal / COUNT(*)) AS DECIMAL) END`,
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST((COUNT(${columnExpression})::decimal / COUNT(*)) AS DECIMAL) END`,
`${aggregatedFieldName}`,
);
break;
default:
default: {
queryBuilder.addSelect(
`${aggregatedField.aggregateOperation}("${columnName}")`,
`${aggregatedField.aggregateOperation}("${columnNameForNumericOperation}")`,
`${aggregatedFieldName}`,
);
}
}
}
};

View File

@ -1,10 +1,7 @@
import { GraphQLISODateTime } from '@nestjs/graphql';
import { GraphQLFloat, GraphQLInt, GraphQLScalarType } from 'graphql';
import {
getColumnNameForAggregateOperation,
getSubfieldForAggregateOperation,
} from 'twenty-shared';
import { getSubfieldsForAggregateOperation } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
@ -17,7 +14,8 @@ export type AggregationField = {
description: string;
fromField: string;
fromFieldType: FieldMetadataType;
fromSubField?: string;
fromSubFields?: string[];
subFieldForNumericOperation?: string;
aggregateOperation: AGGREGATE_OPERATIONS;
};
@ -30,55 +28,50 @@ export const getAvailableAggregationsFromObjectFields = (
return acc;
}
const columnName = getColumnNameForAggregateOperation(
field.name,
field.type,
);
const fromSubFields = getSubfieldsForAggregateOperation(field.type);
const fromSubField = getSubfieldForAggregateOperation(field.type);
acc[`countUniqueValues${capitalize(columnName)}`] = {
acc[`countUniqueValues${capitalize(field.name)}`] = {
type: GraphQLInt,
description: `Number of unique values for ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
fromSubField,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countUniqueValues,
};
acc[`countEmpty${capitalize(columnName)}`] = {
acc[`countEmpty${capitalize(field.name)}`] = {
type: GraphQLInt,
description: `Number of empty values for ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
fromSubField,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countEmpty,
};
acc[`countNotEmpty${capitalize(columnName)}`] = {
acc[`countNotEmpty${capitalize(field.name)}`] = {
type: GraphQLInt,
description: `Number of non-empty values for ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
fromSubField,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countNotEmpty,
};
acc[`percentageEmpty${capitalize(columnName)}`] = {
acc[`percentageEmpty${capitalize(field.name)}`] = {
type: GraphQLFloat,
description: `Percentage of empty values for ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
fromSubField,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.percentageEmpty,
};
acc[`percentageNotEmpty${capitalize(columnName)}`] = {
acc[`percentageNotEmpty${capitalize(field.name)}`] = {
type: GraphQLFloat,
description: `Percentage of non-empty values for ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
fromSubField,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.percentageNotEmpty,
};
@ -138,7 +131,8 @@ export const getAvailableAggregationsFromObjectFields = (
type: GraphQLFloat,
description: `Minimum amount contained in the field ${field.name}`,
fromField: field.name,
fromSubField: 'amountMicros',
fromSubFields: getSubfieldsForAggregateOperation(field.type),
subFieldForNumericOperation: 'amountMicros',
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.min,
};
@ -147,7 +141,7 @@ export const getAvailableAggregationsFromObjectFields = (
type: GraphQLFloat,
description: `Maximal amount contained in the field ${field.name}`,
fromField: field.name,
fromSubField: 'amountMicros',
fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.max,
};
@ -156,7 +150,7 @@ export const getAvailableAggregationsFromObjectFields = (
type: GraphQLFloat,
description: `Sum of amounts contained in the field ${field.name}`,
fromField: field.name,
fromSubField: 'amountMicros',
fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
};
@ -165,7 +159,7 @@ export const getAvailableAggregationsFromObjectFields = (
type: GraphQLFloat,
description: `Average amount contained in the field ${field.name}`,
fromField: field.name,
fromSubField: 'amountMicros',
fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.avg,
};