Fix COUNT operation on view group aggregate header (#9789)
Fixes [sentry](https://twenty-v7.sentry.io/issues/6235128210/?referrer=discord¬ification_uuid=898a081c-f8c7-42b8-b598-7660470a1975&alert_rule_id=15135099&alert_type=issue) In a [previous work](https://github.com/twentyhq/twenty/pull/9749) I set the default field to run totalCount aggregate operation on to the "name" field, which I was wrong think was present on all objects.
This commit is contained in:
@ -6,7 +6,6 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/Agg
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a';
|
||||
const MOCK_KANBAN_FIELD = 'stage';
|
||||
|
||||
describe('buildRecordGqlFieldsAggregateForView', () => {
|
||||
const mockObjectMetadata: ObjectMetadataItem = {
|
||||
@ -53,7 +52,6 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
|
||||
const result = buildRecordGqlFieldsAggregateForView({
|
||||
objectMetadataItem: mockObjectMetadata,
|
||||
recordIndexKanbanAggregateOperation: kanbanAggregateOperation,
|
||||
fieldNameForCount: MOCK_KANBAN_FIELD,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -70,11 +68,10 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
|
||||
const result = buildRecordGqlFieldsAggregateForView({
|
||||
objectMetadataItem: mockObjectMetadata,
|
||||
recordIndexKanbanAggregateOperation: operation,
|
||||
fieldNameForCount: MOCK_KANBAN_FIELD,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
[MOCK_KANBAN_FIELD]: [AGGREGATE_OPERATIONS.count],
|
||||
id: [AGGREGATE_OPERATIONS.count],
|
||||
});
|
||||
});
|
||||
|
||||
@ -88,7 +85,6 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
|
||||
buildRecordGqlFieldsAggregateForView({
|
||||
objectMetadataItem: mockObjectMetadata,
|
||||
recordIndexKanbanAggregateOperation: operation,
|
||||
fieldNameForCount: MOCK_KANBAN_FIELD,
|
||||
}),
|
||||
).toThrow(
|
||||
`No field found to compute aggregate operation ${AGGREGATE_OPERATIONS.sum} on object ${mockObjectMetadata.nameSingular}`,
|
||||
|
||||
@ -9,7 +9,6 @@ import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constant
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a';
|
||||
const MOCK_KANBAN_FIELD_NAME = 'stage';
|
||||
|
||||
describe('computeAggregateValueAndLabel', () => {
|
||||
const mockObjectMetadata: ObjectMetadataItem = {
|
||||
@ -36,7 +35,6 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
objectMetadataItem: mockObjectMetadata,
|
||||
fieldMetadataId: MOCK_FIELD_ID,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.sum,
|
||||
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
|
||||
...defaultParams,
|
||||
});
|
||||
|
||||
@ -55,7 +53,6 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
objectMetadataItem: mockObjectMetadata,
|
||||
fieldMetadataId: MOCK_FIELD_ID,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.sum,
|
||||
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
|
||||
...defaultParams,
|
||||
});
|
||||
|
||||
@ -93,7 +90,6 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
objectMetadataItem: mockObjectMetadataWithPercentageField,
|
||||
fieldMetadataId: MOCK_FIELD_ID,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.avg,
|
||||
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
|
||||
...defaultParams,
|
||||
});
|
||||
|
||||
@ -131,7 +127,6 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
objectMetadataItem: mockObjectMetadataWithDecimalsField,
|
||||
fieldMetadataId: MOCK_FIELD_ID,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.sum,
|
||||
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
|
||||
...defaultParams,
|
||||
});
|
||||
|
||||
@ -166,7 +161,6 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
objectMetadataItem: mockObjectMetadataWithDatetimeField,
|
||||
fieldMetadataId: MOCK_FIELD_ID,
|
||||
aggregateOperation: DATE_AGGREGATE_OPERATIONS.earliest,
|
||||
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
|
||||
...defaultParams,
|
||||
});
|
||||
|
||||
@ -201,7 +195,6 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
objectMetadataItem: mockObjectMetadataWithDatetimeField,
|
||||
fieldMetadataId: MOCK_FIELD_ID,
|
||||
aggregateOperation: DATE_AGGREGATE_OPERATIONS.latest,
|
||||
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
|
||||
...defaultParams,
|
||||
});
|
||||
|
||||
@ -214,7 +207,7 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
|
||||
it('should default to count when field not found', () => {
|
||||
const mockData = {
|
||||
[MOCK_KANBAN_FIELD_NAME]: {
|
||||
id: {
|
||||
[AGGREGATE_OPERATIONS.count]: 42,
|
||||
},
|
||||
} as AggregateRecordsData;
|
||||
@ -222,7 +215,6 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
const result = computeAggregateValueAndLabel({
|
||||
data: mockData,
|
||||
objectMetadataItem: mockObjectMetadata,
|
||||
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
|
||||
...defaultParams,
|
||||
});
|
||||
|
||||
|
||||
@ -2,16 +2,15 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
|
||||
import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const buildRecordGqlFieldsAggregateForView = ({
|
||||
objectMetadataItem,
|
||||
recordIndexKanbanAggregateOperation,
|
||||
fieldNameForCount,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
recordIndexKanbanAggregateOperation: KanbanAggregateOperation;
|
||||
fieldNameForCount: string;
|
||||
}): RecordGqlFieldsAggregate => {
|
||||
let recordGqlFieldsAggregate = {};
|
||||
|
||||
@ -31,7 +30,9 @@ export const buildRecordGqlFieldsAggregateForView = ({
|
||||
);
|
||||
} else {
|
||||
recordGqlFieldsAggregate = {
|
||||
[fieldNameForCount]: [AGGREGATE_OPERATIONS.count],
|
||||
[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: [
|
||||
AGGREGATE_OPERATIONS.count,
|
||||
],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -9,6 +9,7 @@ import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/
|
||||
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
|
||||
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { formatAmount } from '~/utils/format/formatAmount';
|
||||
import { formatNumber } from '~/utils/format/number';
|
||||
@ -21,7 +22,6 @@ export const computeAggregateValueAndLabel = ({
|
||||
objectMetadataItem,
|
||||
fieldMetadataId,
|
||||
aggregateOperation,
|
||||
fallbackFieldName,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
timeZone,
|
||||
@ -30,7 +30,6 @@ export const computeAggregateValueAndLabel = ({
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
fieldMetadataId?: string | null;
|
||||
aggregateOperation?: ExtendedAggregateOperations | null;
|
||||
fallbackFieldName?: string;
|
||||
dateFormat: DateFormat;
|
||||
timeFormat: TimeFormat;
|
||||
timeZone: string;
|
||||
@ -43,11 +42,11 @@ export const computeAggregateValueAndLabel = ({
|
||||
);
|
||||
|
||||
if (!isDefined(field)) {
|
||||
if (!fallbackFieldName) {
|
||||
throw new Error('Missing fallback field name');
|
||||
}
|
||||
return {
|
||||
value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count],
|
||||
value:
|
||||
data?.[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]?.[
|
||||
AGGREGATE_OPERATIONS.count
|
||||
],
|
||||
label: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`,
|
||||
labelWithFieldName: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`,
|
||||
};
|
||||
|
||||
@ -21,7 +21,6 @@ type UseAggregateRecordsProps = {
|
||||
export const useAggregateRecordsForHeader = ({
|
||||
objectMetadataItem,
|
||||
additionalFilters = {},
|
||||
fallbackFieldName,
|
||||
}: UseAggregateRecordsProps) => {
|
||||
const recordIndexViewFilterGroups = useRecoilValue(
|
||||
recordIndexViewFilterGroupsState,
|
||||
@ -47,7 +46,6 @@ export const useAggregateRecordsForHeader = ({
|
||||
const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregateForView({
|
||||
objectMetadataItem,
|
||||
recordIndexKanbanAggregateOperation,
|
||||
fieldNameForCount: fallbackFieldName,
|
||||
});
|
||||
|
||||
const { data } = useAggregateRecords({
|
||||
@ -61,7 +59,6 @@ export const useAggregateRecordsForHeader = ({
|
||||
objectMetadataItem,
|
||||
fieldMetadataId: recordIndexKanbanAggregateOperation?.fieldMetadataId,
|
||||
aggregateOperation: recordIndexKanbanAggregateOperation?.operation,
|
||||
fallbackFieldName,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
timeZone,
|
||||
|
||||
@ -2,7 +2,11 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
|
||||
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { capitalize, isFieldMetadataDateKind } from 'twenty-shared';
|
||||
import {
|
||||
capitalize,
|
||||
FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION,
|
||||
isFieldMetadataDateKind,
|
||||
} from 'twenty-shared';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
type NameForAggregation = {
|
||||
@ -16,59 +20,66 @@ type Aggregations = {
|
||||
export const getAvailableAggregationsFromObjectFields = (
|
||||
fields: FieldMetadataItem[],
|
||||
): Aggregations => {
|
||||
return fields.reduce<Record<string, NameForAggregation>>((acc, field) => {
|
||||
if (field.isSystem === true) {
|
||||
return acc;
|
||||
}
|
||||
return fields.reduce<Record<string, NameForAggregation>>(
|
||||
(acc, field) => {
|
||||
if (field.isSystem === true) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
acc[field.name] = {
|
||||
[AGGREGATE_OPERATIONS.count]: 'totalCount',
|
||||
};
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
acc[field.name] = {
|
||||
[AGGREGATE_OPERATIONS.countUniqueValues]: `countUniqueValues${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.countEmpty]: `countEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.countNotEmpty]: `countNotEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.percentageEmpty]: `percentageEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.percentageNotEmpty]: `percentageNotEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.count]: 'totalCount',
|
||||
};
|
||||
|
||||
if (field.type === FieldMetadataType.NUMBER) {
|
||||
acc[field.name] = {
|
||||
...acc[field.name],
|
||||
[AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.CURRENCY) {
|
||||
acc[field.name] = {
|
||||
...acc[field.name],
|
||||
[AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}AmountMicros`,
|
||||
[AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}AmountMicros`,
|
||||
[AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}AmountMicros`,
|
||||
[AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}AmountMicros`,
|
||||
};
|
||||
}
|
||||
|
||||
if (isFieldMetadataDateKind(field.type) === true) {
|
||||
acc[field.name] = {
|
||||
...acc[field.name],
|
||||
[DATE_AGGREGATE_OPERATIONS.earliest]: `min${capitalize(field.name)}`,
|
||||
[DATE_AGGREGATE_OPERATIONS.latest]: `max${capitalize(field.name)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (acc[field.name] === undefined) {
|
||||
acc[field.name] = {};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[field.name] = {
|
||||
[AGGREGATE_OPERATIONS.countUniqueValues]: `countUniqueValues${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.countEmpty]: `countEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.countNotEmpty]: `countNotEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.percentageEmpty]: `percentageEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.percentageNotEmpty]: `percentageNotEmpty${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.count]: 'totalCount',
|
||||
};
|
||||
|
||||
if (field.type === FieldMetadataType.NUMBER) {
|
||||
acc[field.name] = {
|
||||
...acc[field.name],
|
||||
[AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}`,
|
||||
[AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.CURRENCY) {
|
||||
acc[field.name] = {
|
||||
...acc[field.name],
|
||||
[AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}AmountMicros`,
|
||||
[AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}AmountMicros`,
|
||||
[AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}AmountMicros`,
|
||||
[AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}AmountMicros`,
|
||||
};
|
||||
}
|
||||
|
||||
if (isFieldMetadataDateKind(field.type) === true) {
|
||||
acc[field.name] = {
|
||||
...acc[field.name],
|
||||
[DATE_AGGREGATE_OPERATIONS.earliest]: `min${capitalize(field.name)}`,
|
||||
[DATE_AGGREGATE_OPERATIONS.latest]: `max${capitalize(field.name)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (acc[field.name] === undefined) {
|
||||
acc[field.name] = {};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
{
|
||||
[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: {
|
||||
[AGGREGATE_OPERATIONS.count]: 'totalCount',
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { GraphQLISODateTime } from '@nestjs/graphql';
|
||||
import { GraphQLFloat, GraphQLInt, GraphQLScalarType } from 'graphql';
|
||||
import {
|
||||
capitalize,
|
||||
FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION,
|
||||
FieldMetadataType,
|
||||
isFieldMetadataDateKind,
|
||||
} from 'twenty-shared';
|
||||
@ -176,7 +177,7 @@ export const getAvailableAggregationsFromObjectFields = (
|
||||
totalCount: {
|
||||
type: GraphQLInt,
|
||||
description: `Total number of records in the connection`,
|
||||
fromField: 'id',
|
||||
fromField: FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION,
|
||||
fromFieldType: FieldMetadataType.UUID,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.count,
|
||||
},
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION = 'id';
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './constants/FieldForTotalCountAggregateOperation';
|
||||
export * from './constants/TwentyCompaniesBaseUrl';
|
||||
export * from './constants/TwentyIconsBaseUrl';
|
||||
export * from './types/FieldMetadataType';
|
||||
@ -5,3 +6,4 @@ export * from './utils/fieldMetadata/isFieldMetadataDateKind';
|
||||
export * from './utils/image/getImageAbsoluteURI';
|
||||
export * from './utils/strings';
|
||||
export * from './workspace';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user