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:
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user