Aggregate queries follow up (#9581)

In this PR

- fixing Collapse on view groups views: aggregate bar should be included
in the collapse (@magrinj )
- respect the html table pattern: the aggregate bar is now a <tr>
element included in a <table> (before that, it was a <tr> not included
in anything)
- add a top-border on the aggregate bar
- introduce short labels for the on-cell value display (display "Empty"
instead of "Count empty" to lighten the interface)
- remove the feature flag !
This commit is contained in:
Marie
2025-01-13 17:20:35 +01:00
committed by GitHub
parent 739611afa8
commit 17850b76ab
23 changed files with 111 additions and 182 deletions

View File

@ -389,7 +389,6 @@ export type FeatureFlagFilter = {
export enum FeatureFlagKey { export enum FeatureFlagKey {
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled', IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
IsAggregateQueryEnabled = 'IsAggregateQueryEnabled',
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled', IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled', IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',

View File

@ -1,5 +1,5 @@
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
import { gql } from '@apollo/client';
export type Maybe<T> = T | null; export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>; export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }; export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
@ -321,7 +321,6 @@ export type FeatureFlagFilter = {
export enum FeatureFlagKey { export enum FeatureFlagKey {
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled', IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
IsAggregateQueryEnabled = 'IsAggregateQueryEnabled',
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled', IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled', IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',

View File

@ -1,6 +1,5 @@
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName'; import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
@ -8,16 +7,6 @@ jest.mock('@apollo/client', () => ({
useApolloClient: jest.fn(), useApolloClient: jest.fn(),
})); }));
jest.mock('@/workspace/hooks/useIsFeatureEnabled', () => ({
useIsFeatureEnabled: jest.fn(),
}));
jest.mock('~/generated/graphql', () => ({
FeatureFlagKey: {
IsAggregateQueryEnabled: 'IsAggregateQueryEnabled',
},
}));
describe('useRefetchAggregateQueries', () => { describe('useRefetchAggregateQueries', () => {
const mockRefetchQueries = jest.fn(); const mockRefetchQueries = jest.fn();
const mockApolloClient = { const mockApolloClient = {
@ -29,9 +18,8 @@ describe('useRefetchAggregateQueries', () => {
(useApolloClient as jest.Mock).mockReturnValue(mockApolloClient); (useApolloClient as jest.Mock).mockReturnValue(mockApolloClient);
}); });
it('should refetch queries when feature flag is enabled', async () => { it('should refetch queries', async () => {
// Arrange // Arrange
(useIsFeatureEnabled as jest.Mock).mockReturnValue(true);
const objectMetadataNamePlural = 'opportunities'; const objectMetadataNamePlural = 'opportunities';
const expectedQueryName = getAggregateQueryName(objectMetadataNamePlural); const expectedQueryName = getAggregateQueryName(objectMetadataNamePlural);
@ -48,24 +36,8 @@ describe('useRefetchAggregateQueries', () => {
}); });
}); });
it('should not refetch queries when feature flag is disabled', async () => {
// Arrange
(useIsFeatureEnabled as jest.Mock).mockReturnValue(false);
const objectMetadataNamePlural = 'opportunities';
// Act
const { result } = renderHook(() =>
useRefetchAggregateQueries({ objectMetadataNamePlural }),
);
await result.current.refetchAggregateQueries();
// Assert
expect(mockRefetchQueries).not.toHaveBeenCalled();
});
it('should handle errors during refetch', async () => { it('should handle errors during refetch', async () => {
// Arrange // Arrange
(useIsFeatureEnabled as jest.Mock).mockReturnValue(true);
const error = new Error('Refetch failed'); const error = new Error('Refetch failed');
mockRefetchQueries.mockRejectedValue(error); mockRefetchQueries.mockRejectedValue(error);
const objectMetadataNamePlural = 'opportunities'; const objectMetadataNamePlural = 'opportunities';

View File

@ -1,7 +1,5 @@
import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName'; import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { FeatureFlagKey } from '~/generated/graphql';
export const useRefetchAggregateQueries = ({ export const useRefetchAggregateQueries = ({
objectMetadataNamePlural, objectMetadataNamePlural,
@ -9,17 +7,13 @@ export const useRefetchAggregateQueries = ({
objectMetadataNamePlural: string; objectMetadataNamePlural: string;
}) => { }) => {
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const isAggregateQueryEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
);
const refetchAggregateQueries = async () => {
if (isAggregateQueryEnabled) {
const queryName = getAggregateQueryName(objectMetadataNamePlural);
await apolloClient.refetchQueries({ const refetchAggregateQueries = async () => {
include: [queryName], const queryName = getAggregateQueryName(objectMetadataNamePlural);
});
} await apolloClient.refetchQueries({
include: [queryName],
});
}; };
return { return {

View File

@ -44,7 +44,6 @@ export const RecordBoardColumn = ({
<RecordBoardColumnContext.Provider <RecordBoardColumnContext.Provider
value={{ value={{
columnDefinition: recordGroupDefinition, columnDefinition: recordGroupDefinition,
recordCount: recordIdsByGroup.length,
columnId: recordBoardColumnId, columnId: recordBoardColumnId,
recordIds: recordIdsByGroup, recordIds: recordIdsByGroup,
}} }}

View File

@ -12,9 +12,7 @@ import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-b
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope'; import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui'; import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated/graphql';
const StyledHeader = styled.div` const StyledHeader = styled.div`
align-items: center; align-items: center;
@ -42,16 +40,6 @@ const StyledLeftContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
`; `;
const StyledRecordCountChildren = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
height: 24px;
justify-content: center;
line-height: ${({ theme }) => theme.text.lineHeight.lg};
width: 22px;
`;
const StyledRightContainer = styled.div` const StyledRightContainer = styled.div`
align-items: center; align-items: center;
display: flex; display: flex;
@ -103,10 +91,6 @@ export const RecordBoardColumnHeader = () => {
columnDefinition.id ?? '', columnDefinition.id ?? '',
); );
const isAggregateQueryEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
);
const { isOpportunitiesCompanyFieldDisabled } = const { isOpportunitiesCompanyFieldDisabled } =
useIsOpportunitiesCompanyFieldDisabled(); useIsOpportunitiesCompanyFieldDisabled();
@ -141,18 +125,12 @@ export const RecordBoardColumnHeader = () => {
: 'medium' : 'medium'
} }
/> />
{isAggregateQueryEnabled ? ( <RecordBoardColumnHeaderAggregateDropdown
<RecordBoardColumnHeaderAggregateDropdown aggregateValue={aggregateValue}
aggregateValue={aggregateValue} dropdownId={`record-board-column-aggregate-dropdown-${columnDefinition.id}`}
dropdownId={`record-board-column-aggregate-dropdown-${columnDefinition.id}`} objectMetadataItem={objectMetadataItem}
objectMetadataItem={objectMetadataItem} aggregateLabel={aggregateLabel}
aggregateLabel={aggregateLabel} />
/>
) : (
<StyledRecordCountChildren>
{aggregateValue}
</StyledRecordCountChildren>
)}
</StyledLeftContainer> </StyledLeftContainer>
<StyledRightContainer> <StyledRightContainer>
{isHeaderHovered && ( {isHeaderHovered && (

View File

@ -32,7 +32,6 @@ export const RecordBoardColumnHeaderWrapper = ({
value={{ value={{
columnId, columnId,
columnDefinition: recordGroupDefinition, columnDefinition: recordGroupDefinition,
recordCount: recordIdsByGroup.length,
recordIds: recordIdsByGroup, recordIds: recordIdsByGroup,
}} }}
> >

View File

@ -4,7 +4,6 @@ import { RecordGroupDefinition } from '@/object-record/record-group/types/Record
type RecordBoardColumnContextProps = { type RecordBoardColumnContextProps = {
columnDefinition: RecordGroupDefinition; columnDefinition: RecordGroupDefinition;
recordCount: number;
columnId: string; columnId: string;
recordIds: string[]; recordIds: string[];
}; };

View File

@ -10,20 +10,12 @@ import { recordIndexKanbanAggregateOperationState } from '@/object-record/record
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { UserContext } from '@/users/contexts/UserContext'; import { UserContext } from '@/users/contexts/UserContext';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FeatureFlagKey } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const useAggregateRecordsForRecordBoardColumn = () => { export const useAggregateRecordsForRecordBoardColumn = () => {
const isAggregateQueryEnabled = useIsFeatureEnabled( const { columnDefinition } = useContext(RecordBoardColumnContext);
FeatureFlagKey.IsAggregateQueryEnabled,
);
const { columnDefinition, recordCount } = useContext(
RecordBoardColumnContext,
);
const { objectMetadataItem } = useContext(RecordBoardContext); const { objectMetadataItem } = useContext(RecordBoardContext);
@ -78,7 +70,6 @@ export const useAggregateRecordsForRecordBoardColumn = () => {
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
recordGqlFieldsAggregate, recordGqlFieldsAggregate,
filter, filter,
skip: !isAggregateQueryEnabled,
}); });
const { dateFormat, timeFormat, timeZone } = useContext(UserContext); const { dateFormat, timeFormat, timeZone } = useContext(UserContext);
@ -95,7 +86,7 @@ export const useAggregateRecordsForRecordBoardColumn = () => {
}); });
return { return {
aggregateValue: isAggregateQueryEnabled ? value : recordCount, aggregateValue: value,
aggregateLabel: isDefined(value) ? labelWithFieldName : undefined, aggregateLabel: isDefined(value) ? labelWithFieldName : undefined,
}; };
}; };

View File

@ -171,7 +171,7 @@ describe('computeAggregateValueAndLabel', () => {
}); });
expect(result).toEqual({ expect(result).toEqual({
label: 'Earliest date', label: 'Earliest',
labelWithFieldName: 'Earliest date of Created At', labelWithFieldName: 'Earliest date of Created At',
value: '1 Jan, 2023 12:00', value: '1 Jan, 2023 12:00',
}); });
@ -207,7 +207,7 @@ describe('computeAggregateValueAndLabel', () => {
expect(result).toEqual({ expect(result).toEqual({
value: '31 Dec, 2023 23:59', value: '31 Dec, 2023 23:59',
label: 'Latest date', label: 'Latest',
labelWithFieldName: 'Latest date of Updated At', labelWithFieldName: 'Latest date of Updated At',
}); });
}); });

View File

@ -3,6 +3,7 @@ import { TimeFormat } from '@/localization/constants/TimeFormat';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords'; import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { getAggregateOperationShortLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions'; import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions';
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions'; import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
@ -118,7 +119,7 @@ export const computeAggregateValueAndLabel = ({
} }
} }
} }
const label = getAggregateOperationLabel(aggregateOperation); const label = getAggregateOperationShortLabel(aggregateOperation);
const labelWithFieldName = const labelWithFieldName =
aggregateOperation === AGGREGATE_OPERATIONS.count aggregateOperation === AGGREGATE_OPERATIONS.count
? `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}` ? `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`

View File

@ -0,0 +1,34 @@
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';
export const getAggregateOperationShortLabel = (
operation: ExtendedAggregateOperations,
) => {
switch (operation) {
case AGGREGATE_OPERATIONS.min:
return 'Min';
case AGGREGATE_OPERATIONS.max:
return 'Max';
case AGGREGATE_OPERATIONS.avg:
return 'Average';
case AGGREGATE_OPERATIONS.sum:
return 'Sum';
case AGGREGATE_OPERATIONS.count:
return 'All';
case AGGREGATE_OPERATIONS.countEmpty:
case AGGREGATE_OPERATIONS.percentageEmpty:
return 'Empty';
case AGGREGATE_OPERATIONS.countNotEmpty:
case AGGREGATE_OPERATIONS.percentageNotEmpty:
return 'Not empty';
case AGGREGATE_OPERATIONS.countUniqueValues:
return 'Unique';
case DATE_AGGREGATE_OPERATIONS.earliest:
return 'Earliest';
case DATE_AGGREGATE_OPERATIONS.latest:
return 'Latest';
default:
throw new Error(`Unknown aggregate operation: ${operation}`);
}
};

View File

@ -13,16 +13,13 @@ import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/recor
import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect'; 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 { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects';
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody'; import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { hasPendingRecordComponentSelector } from '@/object-record/record-table/states/selectors/hasPendingRecordComponentSelector'; import { hasPendingRecordComponentSelector } from '@/object-record/record-table/states/selectors/hasPendingRecordComponentSelector';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRef } from 'react'; import { useRef } from 'react';
import { FeatureFlagKey } from '~/generated/graphql';
const StyledTable = styled.table` const StyledTable = styled.table`
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
@ -36,10 +33,6 @@ export const RecordTable = () => {
const tableBodyRef = useRef<HTMLTableElement>(null); const tableBodyRef = useRef<HTMLTableElement>(null);
const isAggregateQueryEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
);
const { toggleClickOutsideListener } = useClickOutsideListener( const { toggleClickOutsideListener } = useClickOutsideListener(
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
); );
@ -97,12 +90,6 @@ export const RecordTable = () => {
<RecordTableRecordGroupsBody /> <RecordTableRecordGroupsBody />
)} )}
<RecordTableStickyEffect /> <RecordTableStickyEffect />
{isAggregateQueryEnabled &&
!hasRecordGroups &&
!isRecordTableInitialLoading &&
allRecordIds.length > 0 && (
<RecordTableAggregateFooter endOfTableSticky />
)}
</StyledTable> </StyledTable>
<DragSelect <DragSelect
dragSelectable={tableBodyRef} dragSelectable={tableBodyRef}

View File

@ -1,6 +1,8 @@
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFetchMoreLoader'; import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFetchMoreLoader';
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow'; import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordTableNoRecordGroupRows = () => { export const RecordTableNoRecordGroupRows = () => {
@ -8,6 +10,10 @@ export const RecordTableNoRecordGroupRows = () => {
recordIndexAllRecordIdsComponentSelector, recordIndexAllRecordIdsComponentSelector,
); );
const isRecordTableInitialLoading = useRecoilComponentValueV2(
isRecordTableInitialLoadingComponentState,
);
return ( return (
<> <>
{allRecordIds.map((recordId, rowIndex) => { {allRecordIds.map((recordId, rowIndex) => {
@ -21,6 +27,9 @@ export const RecordTableNoRecordGroupRows = () => {
); );
})} })}
<RecordTableBodyFetchMoreLoader /> <RecordTableBodyFetchMoreLoader />
{!isRecordTableInitialLoading && allRecordIds.length > 0 && (
<RecordTableAggregateFooter endOfTableSticky />
)}
</> </>
); );
}; };

View File

@ -1,6 +1,7 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
import { RecordTablePendingRecordGroupRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow'; import { RecordTablePendingRecordGroupRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow';
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow'; import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew'; import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew';
@ -59,6 +60,10 @@ export const RecordTableRecordGroupRows = () => {
<RecordTablePendingRecordGroupRow /> <RecordTablePendingRecordGroupRow />
<RecordTableRecordGroupSectionAddNew /> <RecordTableRecordGroupSectionAddNew />
<RecordTableRecordGroupSectionLoadMore /> <RecordTableRecordGroupSectionLoadMore />
<RecordTableAggregateFooter
key={currentRecordGroupId}
currentRecordGroupId={currentRecordGroupId}
/>
</> </>
); );
}; };

View File

@ -6,14 +6,11 @@ import { RecordTableRecordGroupRows } from '@/object-record/record-table/compone
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable'; import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading'; import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
import { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider'; import { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider';
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection'; import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';
export const RecordTableRecordGroupsBody = () => { export const RecordTableRecordGroupsBody = () => {
const allRecordIds = useRecoilComponentValueV2( const allRecordIds = useRecoilComponentValueV2(
@ -29,10 +26,6 @@ export const RecordTableRecordGroupsBody = () => {
ViewType.Table, ViewType.Table,
); );
const isAggregateQueryEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
);
if (isRecordTableInitialLoading && allRecordIds.length === 0) { if (isRecordTableInitialLoading && allRecordIds.length === 0) {
return <RecordTableBodyLoading />; return <RecordTableBodyLoading />;
} }
@ -50,12 +43,6 @@ export const RecordTableRecordGroupsBody = () => {
<RecordTableRecordGroupSection /> <RecordTableRecordGroupSection />
<RecordTableRecordGroupRows /> <RecordTableRecordGroupRows />
</RecordTableBodyDroppable> </RecordTableBodyDroppable>
{isAggregateQueryEnabled && (
<RecordTableAggregateFooter
key={recordGroupId}
currentRecordGroupId={recordGroupId}
/>
)}
</RecordGroupContext.Provider> </RecordGroupContext.Provider>
</RecordTableRecordGroupBodyContextProvider> </RecordTableRecordGroupBodyContextProvider>
))} ))}

View File

@ -8,38 +8,41 @@ import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/state
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { MOBILE_VIEWPORT } from 'twenty-ui'; import { MOBILE_VIEWPORT } from 'twenty-ui';
const StyledTh = styled.th` const StyledTd = styled.td`
background-color: ${({ theme }) => theme.background.primary}; background-color: ${({ theme }) => theme.background.primary};
`; `;
const StyledTableFoot = styled.thead<{ const StyledTableRow = styled.tr<{
endOfTableSticky?: boolean; endOfTableSticky?: boolean;
hasHorizontalOverflow?: boolean; hasHorizontalOverflow?: boolean;
}>` }>`
td {
border-top: 1px solid ${({ theme }) => theme.border.color.light};
}
cursor: pointer; cursor: pointer;
th:nth-of-type(1) { td:nth-of-type(1) {
width: ${FIRST_TH_WIDTH}; width: ${FIRST_TH_WIDTH};
left: 0; left: 0;
border-right-color: ${({ theme }) => theme.background.primary}; border-right-color: ${({ theme }) => theme.background.primary};
border-top: none;
} }
th:nth-of-type(2) { td:nth-of-type(2) {
border-right-color: ${({ theme }) => theme.background.primary}; border-right-color: ${({ theme }) => theme.background.primary};
border-top: 1px solid ${({ theme }) => theme.border.color.light};
} }
&.first-columns-sticky { &.first-columns-sticky {
th:nth-of-type(1) { td:nth-of-type(1) {
position: sticky; position: sticky;
left: 0; left: 0;
z-index: 5; z-index: 5;
transition: 0.3s ease; transition: 0.3s ease;
} }
th:nth-of-type(2) { td:nth-of-type(2) {
position: sticky; position: sticky;
left: 11px; left: 11px;
z-index: 5; z-index: 5;
transition: 0.3s ease; transition: 0.3s ease;
} }
th:nth-of-type(3) { td:nth-of-type(3) {
position: sticky; position: sticky;
left: 43px; left: 43px;
z-index: 5; z-index: 5;
@ -60,13 +63,12 @@ const StyledTableFoot = styled.thead<{
} }
} }
} }
tr { position: sticky;
position: sticky; z-index: 5;
z-index: 5; background: ${({ theme }) => theme.background.primary};
background: ${({ theme }) => theme.background.primary}; ${({ endOfTableSticky, hasHorizontalOverflow }) =>
${({ endOfTableSticky, hasHorizontalOverflow }) => endOfTableSticky &&
endOfTableSticky && `
`
bottom: ${hasHorizontalOverflow ? '10px' : '0'}; bottom: ${hasHorizontalOverflow ? '10px' : '0'};
${ ${
hasHorizontalOverflow && hasHorizontalOverflow &&
@ -83,7 +85,6 @@ const StyledTableFoot = styled.thead<{
` `
} }
`} `}
}
`; `;
export const RecordTableAggregateFooter = ({ export const RecordTableAggregateFooter = ({
@ -108,32 +109,30 @@ export const RecordTableAggregateFooter = ({
: false; : false;
return ( return (
<StyledTableFoot <StyledTableRow
id={`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`} id={`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
data-select-disable data-select-disable
endOfTableSticky={endOfTableSticky} endOfTableSticky={endOfTableSticky}
hasHorizontalOverflow={hasHorizontalOverflow} hasHorizontalOverflow={hasHorizontalOverflow}
> >
<tr> <StyledTd />
<StyledTh /> <StyledTd />
<StyledTh /> {visibleTableColumns.map((column, index) => {
{visibleTableColumns.map((column, index) => { return (
return ( <RecordTableColumnAggregateFooterCellContext.Provider
<RecordTableColumnAggregateFooterCellContext.Provider key={`${column.fieldMetadataId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
key={`${column.fieldMetadataId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`} value={{
value={{ viewFieldId: column.viewFieldId || '',
viewFieldId: column.viewFieldId || '', fieldMetadataId: column.fieldMetadataId,
fieldMetadataId: column.fieldMetadataId, }}
}} >
> <RecordTableAggregateFooterCell
<RecordTableAggregateFooterCell currentRecordGroupId={currentRecordGroupId}
currentRecordGroupId={currentRecordGroupId} isFirstCell={index === 0}
isFirstCell={index === 0} />
/> </RecordTableColumnAggregateFooterCellContext.Provider>
</RecordTableColumnAggregateFooterCellContext.Provider> );
); })}
})} </StyledTableRow>
</tr>
</StyledTableFoot>
); );
}; };

View File

@ -9,7 +9,7 @@ import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
const COLUMN_MIN_WIDTH = 104; const COLUMN_MIN_WIDTH = 104;
const StyledColumnFooterCell = styled.th<{ const StyledColumnFooterCell = styled.td<{
columnWidth: number; columnWidth: number;
}>` }>`
background-color: ${({ theme }) => theme.background.primary}; background-color: ${({ theme }) => theme.background.primary};
@ -43,7 +43,6 @@ const StyledColumnFooterCell = styled.th<{
*::-webkit-scrollbar { *::-webkit-scrollbar {
display: none; display: none;
} }
border-top: 1px solid ${({ theme }) => theme.border.color.light};
`; `;
const StyledColumnFootContainer = styled.div` const StyledColumnFootContainer = styled.div`

View File

@ -9,18 +9,13 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext'; import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
import { UserContext } from '@/users/contexts/UserContext'; import { UserContext } from '@/users/contexts/UserContext';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FeatureFlagKey } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const useAggregateRecordsForRecordTableColumnFooter = ( export const useAggregateRecordsForRecordTableColumnFooter = (
fieldMetadataId: string, fieldMetadataId: string,
) => { ) => {
const isAggregateQueryEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
);
const { objectMetadataItem } = useRecordTableContextOrThrow(); const { objectMetadataItem } = useRecordTableContextOrThrow();
const { recordGroupFilter } = useRecordGroupFilter(objectMetadataItem.fields); const { recordGroupFilter } = useRecordGroupFilter(objectMetadataItem.fields);
@ -61,8 +56,7 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
recordGqlFieldsAggregate, recordGqlFieldsAggregate,
filter: { ...requestFilters, ...recordGroupFilter }, filter: { ...requestFilters, ...recordGroupFilter },
skip: skip: !isDefined(aggregateOperationForViewField),
!isAggregateQueryEnabled || !isDefined(aggregateOperationForViewField),
}); });
const { dateFormat, timeFormat, timeZone } = useContext(UserContext); const { dateFormat, timeFormat, timeZone } = useContext(UserContext);

View File

@ -6,6 +6,10 @@ const StyledTr = styled.tr<{ isDragging: boolean }>`
? `1px solid ${theme.border.color.medium}` ? `1px solid ${theme.border.color.medium}`
: '1px solid transparent'}; : '1px solid transparent'};
transition: border-left-color 0.2s ease-in-out; transition: border-left-color 0.2s ease-in-out;
&:nth-last-child(2) td {
border-bottom: none;
}
`; `;
export const RecordTableTr = StyledTr; export const RecordTableTr = StyledTr;

View File

@ -70,11 +70,6 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId, workspaceId: workspaceId,
value: false, value: false,
}, },
{
key: FeatureFlagKey.IsAggregateQueryEnabled,
workspaceId: workspaceId,
value: true,
},
{ {
key: FeatureFlagKey.IsCommandMenuV2Enabled, key: FeatureFlagKey.IsCommandMenuV2Enabled,
workspaceId: workspaceId, workspaceId: workspaceId,

View File

@ -27,7 +27,6 @@ import {
getCursor, getCursor,
getPaginationInfo, getPaginationInfo,
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { isDefined } from 'src/utils/is-defined'; import { isDefined } from 'src/utils/is-defined';
@ -109,19 +108,6 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
appliedFilters, appliedFilters,
); );
const isAggregationsEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
authContext.workspace.id,
);
if (!isAggregationsEnabled) {
executionArgs.graphqlQuerySelectedFieldsResult.aggregate = {
totalCount:
executionArgs.graphqlQuerySelectedFieldsResult.aggregate.totalCount,
};
}
const processAggregateHelper = new ProcessAggregateHelper(); const processAggregateHelper = new ProcessAggregateHelper();
processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({ processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({

View File

@ -12,7 +12,6 @@ export enum FeatureFlagKey {
IsUniqueIndexesEnabled = 'IS_UNIQUE_INDEXES_ENABLED', IsUniqueIndexesEnabled = 'IS_UNIQUE_INDEXES_ENABLED',
IsMicrosoftSyncEnabled = 'IS_MICROSOFT_SYNC_ENABLED', IsMicrosoftSyncEnabled = 'IS_MICROSOFT_SYNC_ENABLED',
IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED', IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED',
IsAggregateQueryEnabled = 'IS_AGGREGATE_QUERY_ENABLED',
IsCommandMenuV2Enabled = 'IS_COMMAND_MENU_V2_ENABLED', IsCommandMenuV2Enabled = 'IS_COMMAND_MENU_V2_ENABLED',
IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED', IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED',
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED', IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',