Display and update aggregate queries in kanban views (#8833)

Closes #8752, #8753, #8754

Implements usage of aggregate queries in kanban views.

https://github.com/user-attachments/assets/732590ca-2785-4c57-82d5-d999a2279e92

TO DO

1. write tests + storybook
2. Fix values displayed should have the same format as defined in number
fields + Fix display for amountMicros

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Marie
2024-12-03 22:46:57 +01:00
committed by GitHub
parent 5e891a135b
commit 2fc247cb21
67 changed files with 1670 additions and 104 deletions

View File

@ -0,0 +1,70 @@
import { useQuery } from '@apollo/client';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
import { useAggregateManyRecordsQuery } from '@/object-record/hooks/useAggregateManyRecordsQuery';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import isEmpty from 'lodash.isempty';
import { isDefined } from 'twenty-ui';
export type AggregateManyRecordsData = {
[fieldName: string]: {
[operation in AGGREGATE_OPERATIONS]?: string | number | undefined;
};
};
export const useAggregateManyRecords = ({
objectNameSingular,
filter,
recordGqlFieldsAggregate,
skip,
}: {
objectNameSingular: string;
recordGqlFieldsAggregate: RecordGqlFieldsAggregate;
filter?: RecordGqlOperationFilter;
skip?: boolean;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { aggregateQuery, gqlFieldToFieldMap } = useAggregateManyRecordsQuery({
objectNameSingular,
recordGqlFieldsAggregate,
});
const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
aggregateQuery,
{
skip: skip || !objectMetadataItem,
variables: {
filter,
},
},
);
const formattedData: AggregateManyRecordsData = {};
if (!isEmpty(data)) {
Object.entries(data?.[objectMetadataItem.namePlural] ?? {})?.forEach(
([gqlField, result]) => {
if (isDefined(gqlFieldToFieldMap[gqlField])) {
const [fieldName, aggregateOperation] = gqlFieldToFieldMap[gqlField];
formattedData[fieldName] = {
...(formattedData[fieldName] ?? {}),
[aggregateOperation]: result,
};
}
},
);
}
return {
objectMetadataItem,
data: formattedData,
loading,
error,
};
};

View File

@ -0,0 +1,69 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { generateAggregateQuery } from '@/object-record/utils/generateAggregateQuery';
import { getAvailableAggregationsFromObjectFields } from '@/object-record/utils/getAvailableAggregationsFromObjectFields';
import { useMemo } from 'react';
import { isDefined } from 'twenty-ui';
export type GqlFieldToFieldMap = {
[gqlField: string]: [
fieldName: string,
aggregateOperation: AGGREGATE_OPERATIONS,
];
};
export const useAggregateManyRecordsQuery = ({
objectNameSingular,
recordGqlFieldsAggregate = {},
}: {
objectNameSingular: string;
recordGqlFieldsAggregate: RecordGqlFieldsAggregate;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const availableAggregations = useMemo(
() => getAvailableAggregationsFromObjectFields(objectMetadataItem.fields),
[objectMetadataItem.fields],
);
const recordGqlFields: RecordGqlFields = {};
const gqlFieldToFieldMap: GqlFieldToFieldMap = {};
Object.entries(recordGqlFieldsAggregate).forEach(
([fieldName, aggregateOperation]) => {
if (
!isDefined(fieldName) &&
aggregateOperation === AGGREGATE_OPERATIONS.count
) {
recordGqlFields.totalCount = true;
return;
}
const fieldToQuery =
availableAggregations[fieldName]?.[aggregateOperation];
if (!isDefined(fieldToQuery)) {
throw new Error(
`Cannot query operation ${aggregateOperation} on field ${fieldName}`,
);
}
gqlFieldToFieldMap[fieldToQuery] = [fieldName, aggregateOperation];
recordGqlFields[fieldToQuery] = true;
},
);
const aggregateQuery = generateAggregateQuery({
objectMetadataItem,
recordGqlFields,
});
return {
aggregateQuery,
gqlFieldToFieldMap,
};
};