Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Aggregate queries for table views - #2] Add aggregate queries footer for simple views #9025

Merged
merged 13 commits into from
Dec 12, 2024
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { buildRecordGqlFieldsAggregate } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregate';
import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard';
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
Expand Down Expand Up @@ -42,7 +42,7 @@ export const useAggregateRecordsForRecordBoardColumn = () => {
);
}

const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregate({
const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregateForRecordBoard({
objectMetadataItem: objectMetadataItem,
recordIndexKanbanAggregateOperation: recordIndexKanbanAggregateOperation,
kanbanFieldName: kanbanFieldName,
Expand Down Expand Up @@ -74,12 +74,13 @@ export const useAggregateRecordsForRecordBoardColumn = () => {
skip: !isAggregateQueryEnabled,
});

const { value, label } = computeAggregateValueAndLabel(
const { value, label } = computeAggregateValueAndLabel({
data,
objectMetadataItem,
recordIndexKanbanAggregateOperation,
kanbanFieldName,
);
fieldMetadataId: recordIndexKanbanAggregateOperation?.fieldMetadataId,
aggregateOperation: recordIndexKanbanAggregateOperation?.operation,
fallbackFieldName: kanbanFieldName,
});

return {
aggregateValue: isAggregateQueryEnabled ? value : recordCount,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { buildRecordGqlFieldsAggregate } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregate';
import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard';
import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { FieldMetadataType } from '~/generated-metadata/graphql';

const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a';
const MOCK_KANBAN_FIELD = 'stage';

describe('buildRecordGqlFieldsAggregate', () => {
describe('buildRecordGqlFieldsAggregateForRecordBoard', () => {
const mockObjectMetadata: ObjectMetadataItem = {
id: '123',
nameSingular: 'opportunity',
Expand Down Expand Up @@ -50,7 +50,7 @@ describe('buildRecordGqlFieldsAggregate', () => {
operation: AGGREGATE_OPERATIONS.sum,
};

const result = buildRecordGqlFieldsAggregate({
const result = buildRecordGqlFieldsAggregateForRecordBoard({
objectMetadataItem: mockObjectMetadata,
recordIndexKanbanAggregateOperation: kanbanAggregateOperation,
kanbanFieldName: MOCK_KANBAN_FIELD,
Expand All @@ -67,7 +67,7 @@ describe('buildRecordGqlFieldsAggregate', () => {
operation: AGGREGATE_OPERATIONS.count,
};

const result = buildRecordGqlFieldsAggregate({
const result = buildRecordGqlFieldsAggregateForRecordBoard({
objectMetadataItem: mockObjectMetadata,
recordIndexKanbanAggregateOperation: operation,
kanbanFieldName: MOCK_KANBAN_FIELD,
Expand All @@ -85,7 +85,7 @@ describe('buildRecordGqlFieldsAggregate', () => {
};

expect(() =>
buildRecordGqlFieldsAggregate({
buildRecordGqlFieldsAggregateForRecordBoard({
objectMetadataItem: mockObjectMetadata,
recordIndexKanbanAggregateOperation: operation,
kanbanFieldName: MOCK_KANBAN_FIELD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/Agg
import { FieldMetadataType } from '~/generated/graphql';

const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a';
const MOCK_KANBAN_FIELD = 'stage';
const MOCK_KANBAN_FIELD_NAME = 'stage';

describe('computeAggregateValueAndLabel', () => {
const mockObjectMetadata: ObjectMetadataItem = {
Expand All @@ -20,12 +20,13 @@ describe('computeAggregateValueAndLabel', () => {
} as ObjectMetadataItem;

it('should return empty object for empty data', () => {
const result = computeAggregateValueAndLabel(
{},
mockObjectMetadata,
{ fieldMetadataId: MOCK_FIELD_ID, operation: AGGREGATE_OPERATIONS.sum },
MOCK_KANBAN_FIELD,
);
const result = computeAggregateValueAndLabel({
data: {},
objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
});

expect(result).toEqual({});
});
Expand All @@ -37,12 +38,13 @@ describe('computeAggregateValueAndLabel', () => {
},
};

const result = computeAggregateValueAndLabel(
mockData,
mockObjectMetadata,
{ fieldMetadataId: MOCK_FIELD_ID, operation: AGGREGATE_OPERATIONS.sum },
MOCK_KANBAN_FIELD,
);
const result = computeAggregateValueAndLabel({
data: mockData,
objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
});

expect(result).toEqual({
value: 2,
Expand All @@ -52,17 +54,16 @@ describe('computeAggregateValueAndLabel', () => {

it('should default to count when field not found', () => {
const mockData = {
[MOCK_KANBAN_FIELD]: {
[MOCK_KANBAN_FIELD_NAME]: {
[AGGREGATE_OPERATIONS.count]: 42,
},
};

const result = computeAggregateValueAndLabel(
mockData,
mockObjectMetadata,
{ fieldMetadataId: 'non-existent', operation: AGGREGATE_OPERATIONS.sum },
MOCK_KANBAN_FIELD,
);
const result = computeAggregateValueAndLabel({
data: mockData,
objectMetadataItem: mockObjectMetadata,
fallbackFieldName: MOCK_KANBAN_FIELD_NAME,
});

expect(result).toEqual({
value: 42,
Expand All @@ -77,12 +78,12 @@ describe('computeAggregateValueAndLabel', () => {
},
};

const result = computeAggregateValueAndLabel(
mockData,
mockObjectMetadata,
{ fieldMetadataId: MOCK_FIELD_ID, operation: AGGREGATE_OPERATIONS.sum },
MOCK_KANBAN_FIELD,
);
const result = computeAggregateValueAndLabel({
data: mockData,
objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
});

expect(result).toEqual({
value: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { KanbanAggregateOperation } from '@/object-record/record-index/states/re
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { isDefined } from '~/utils/isDefined';

export const buildRecordGqlFieldsAggregate = ({
export const buildRecordGqlFieldsAggregateForRecordBoard = ({
objectMetadataItem,
recordIndexKanbanAggregateOperation,
kanbanFieldName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,59 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import isEmpty from 'lodash.isempty';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';

export const computeAggregateValueAndLabel = (
data: AggregateRecordsData,
objectMetadataItem: ObjectMetadataItem,
recordIndexKanbanAggregateOperation: KanbanAggregateOperation,
kanbanFieldName: string,
) => {
export const computeAggregateValueAndLabel = ({
data,
objectMetadataItem,
fieldMetadataId,
aggregateOperation,
fallbackFieldName,
}: {
data: AggregateRecordsData;
ijreilly marked this conversation as resolved.
Show resolved Hide resolved
objectMetadataItem: ObjectMetadataItem;
fieldMetadataId?: string | null;
aggregateOperation?: AGGREGATE_OPERATIONS | null;
fallbackFieldName?: string;
}) => {
ijreilly marked this conversation as resolved.
Show resolved Hide resolved
if (isEmpty(data)) {
return {};
}
const kanbanAggregateOperationField = objectMetadataItem.fields?.find(
(field) =>
field.id === recordIndexKanbanAggregateOperation?.fieldMetadataId,
const field = objectMetadataItem.fields?.find(
(field) => field.id === fieldMetadataId,
);

const kanbanAggregateOperationFieldName = kanbanAggregateOperationField?.name;

if (
!isDefined(kanbanAggregateOperationFieldName) ||
!isDefined(recordIndexKanbanAggregateOperation?.operation)
) {
if (!isDefined(field)) {
if (!fallbackFieldName) {
throw new Error('Missing fallback field name');
}
return {
value: data?.[kanbanFieldName]?.[AGGREGATE_OPERATIONS.count],
value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count],
label: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`,
};
}
ijreilly marked this conversation as resolved.
Show resolved Hide resolved

const aggregateValue =
data[kanbanAggregateOperationFieldName]?.[
recordIndexKanbanAggregateOperation.operation
];
if (!isDefined(aggregateOperation)) {
throw new Error('Missing aggregate operation');
}

const aggregateValue = data[field.name]?.[aggregateOperation];
ijreilly marked this conversation as resolved.
Show resolved Hide resolved

const value =
isDefined(aggregateValue) &&
kanbanAggregateOperationField?.type === FieldMetadataType.Currency
isDefined(aggregateValue) && field.type === FieldMetadataType.Currency
? Number(aggregateValue) / 1_000_000
: aggregateValue;

const label =
aggregateOperation === AGGREGATE_OPERATIONS.count
? `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`
: `${getAggregateOperationLabel(aggregateOperation)} of ${field.name}`;

return {
value,
label: `${getAggregateOperationLabel(recordIndexKanbanAggregateOperation.operation)} of ${kanbanAggregateOperationFieldName}`,
label,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/recor
import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect';
import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects';
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
import { RecordTableFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableFooter';
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRef } from 'react';

const StyledTable = styled.table`
Expand All @@ -33,6 +35,10 @@ export const RecordTable = () => {

const tableBodyRef = useRef<HTMLTableElement>(null);

const isAggregateQueryEnabled = useIsFeatureEnabled(
'IS_AGGREGATE_QUERY_ENABLED',
);

const { toggleClickOutsideListener } = useClickOutsideListener(
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
);
Expand Down Expand Up @@ -90,6 +96,7 @@ export const RecordTable = () => {
<RecordTableRecordGroupsBody />
)}
<RecordTableStickyEffect />
{isAggregateQueryEnabled && <RecordTableFooter />}
</StyledTable>
<DragSelect
dragSelectable={tableBodyRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,19 @@ export const RecordTableStickyEffect = () => {
document
.getElementById('record-table-header')
?.classList.add('first-columns-sticky');
document
.getElementById('record-table-footer')
?.classList.add('first-columns-sticky');
} else {
document
.getElementById('record-table-body')
?.classList.remove('first-columns-sticky');
document
.getElementById('record-table-header')
?.classList.remove('first-columns-sticky');
document
.getElementById('record-table-footer')
?.classList.remove('first-columns-sticky');
}
}, [scrollLeft, setIsRecordTableScrolledLeft]);

Expand Down
Loading
Loading