From 7099b1c174458e640a825f2513b669d17b8e024a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 10 Nov 2025 22:00:39 +0100 Subject: [PATCH] Fix export CSV not using right columns (#15752) Fixes https://github.com/twentyhq/twenty/issues/15575 --- ...lumnDefinitionsFromObjectMetadata.test.ts} | 6 +-- ...useColumnDefinitionsFromObjectMetadata.ts} | 2 +- .../hooks/useObjectOptionsForBoard.ts | 4 +- .../RecordIndexTableContainerEffect.tsx | 4 +- .../components/RecordIndexViewBarEffect.tsx | 4 +- .../useRecordIndexLazyFetchRecords.test.ts | 10 +---- .../hooks/useRecordIndexExportRecords.ts | 10 ++++- .../hooks/useRecordIndexLazyFetchRecords.ts | 41 ++++++++++++++++--- .../hooks/useHandleToggleColumnSort.ts | 4 +- .../hooks/useHandleToggleTrashColumnFilter.ts | 4 +- 10 files changed, 58 insertions(+), 31 deletions(-) rename packages/twenty-front/src/modules/object-metadata/hooks/__tests__/{useColumnDefinitionsFromFieldMetadata.test.ts => useColumnDefinitionsFromObjectMetadata.test.ts} (90%) rename packages/twenty-front/src/modules/object-metadata/hooks/{useColumnDefinitionsFromFieldMetadata.ts => useColumnDefinitionsFromObjectMetadata.ts} (97%) diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts similarity index 90% rename from packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts rename to packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts index 0264aec7db..9e5e9870e6 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; +import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { SubscriptionInterval, @@ -59,7 +59,7 @@ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ }, }); -describe('useColumnDefinitionsFromFieldMetadata', () => { +describe('useColumnDefinitionsFromObjectMetadata', () => { it('should return expected definitions', () => { const companyObjectMetadata = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'company', @@ -67,7 +67,7 @@ describe('useColumnDefinitionsFromFieldMetadata', () => { const { result } = renderHook( (objectMetadataItem: ObjectMetadataItem) => { - return useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + return useColumnDefinitionsFromObjectMetadata(objectMetadataItem); }, { initialProps: companyObjectMetadata, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata.ts similarity index 97% rename from packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata.ts rename to packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata.ts index 62cc28ef09..9b05bc6282 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata.ts @@ -8,7 +8,7 @@ import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metad import { useRecoilValue } from 'recoil'; import { formatFieldMetadataItemAsColumnDefinition } from '../utils/formatFieldMetadataItemAsColumnDefinition'; -export const useColumnDefinitionsFromFieldMetadata = ( +export const useColumnDefinitionsFromObjectMetadata = ( objectMetadataItem: ObjectMetadataItem, ) => { const activeFieldMetadataItems = objectMetadataItem.readableFields.filter( diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard.ts index 49be00f25f..025ac6397d 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard.ts +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard.ts @@ -2,7 +2,7 @@ import { type OnDragEndResponder } from '@hello-pangea/dnd'; import { useCallback, useMemo } from 'react'; import { useRecoilState } from 'recoil'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; +import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useReorderVisibleRecordFields } from '@/object-record/record-field/hooks/useReorderVisibleRecordFields'; import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField'; @@ -41,7 +41,7 @@ export const useObjectOptionsForBoard = ({ }); const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + useColumnDefinitionsFromObjectMetadata(objectMetadataItem); const availableColumnDefinitions = useMemo( () => diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 3e1e0f0175..407e4bea53 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; +import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; @@ -18,7 +18,7 @@ export const RecordIndexTableContainerEffect = () => { }); const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + useColumnDefinitionsFromObjectMetadata(objectMetadataItem); const { currentView } = useGetCurrentViewOnly(); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx index 8b5b950505..1dc892e502 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexViewBarEffect.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; +import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useInitViewBar } from '@/views/hooks/useInitViewBar'; @@ -24,7 +24,7 @@ export const RecordIndexViewBarEffect = ({ }); const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + useColumnDefinitionsFromObjectMetadata(objectMetadataItem); const { setViewObjectMetadataId, setAvailableFieldDefinitions } = useInitViewBar(viewBarId); diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts index 52643bf9dd..1f56ec15c2 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts @@ -6,7 +6,6 @@ import { useRecordIndexLazyFetchRecords, } from '../useRecordIndexLazyFetchRecords'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; import { ViewType } from '@/views/types/ViewType'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; @@ -132,9 +131,6 @@ describe('useRecordData', () => { const { result } = renderHook( () => { - const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); - const lazyFetchResult = useRecordIndexLazyFetchRecords({ recordIndexId, objectMetadataItem, @@ -144,7 +140,6 @@ describe('useRecordData', () => { }); return { - columnDefinitions, lazyFetchResult, }; }, @@ -156,10 +151,7 @@ describe('useRecordData', () => { }); await waitFor(() => { - expect(callback).toHaveBeenCalledWith( - [mockPerson], - result.current.columnDefinitions, - ); + expect(callback).toHaveBeenCalledWith([mockPerson], []); }); }); }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts index 6d5fee485a..0ad1e44533 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts @@ -23,7 +23,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; type GenerateExportOptions = { columns: Pick< ColumnDefinition, - 'size' | 'label' | 'type' | 'metadata' + 'label' | 'type' | 'metadata' >[]; rows: Record[]; }; @@ -169,7 +169,13 @@ export const useRecordIndexExportRecords = ({ const downloadCsv = useMemo( () => - (records: ObjectRecord[], columns: ColumnDefinition[]) => { + ( + records: ObjectRecord[], + columns: Pick< + ColumnDefinition, + 'label' | 'type' | 'metadata' + >[], + ) => { const recordsProcessedForExport = processRecordsForCSVExport(records); csvDownloader(filename, { rows: recordsProcessedForExport, columns }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts index 46fc0378ac..9d51ea2a7a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts @@ -7,16 +7,18 @@ import { contextStoreFilterGroupsComponentState } from '@/context-store/states/c import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; +import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector'; +import { type RecordField } from '@/object-record/record-field/types/RecordField'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { ViewType } from '@/views/types/ViewType'; +import { isDefined } from 'twenty-shared/utils'; export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -33,7 +35,10 @@ export type UseRecordDataOptions = { recordIndexId: string; callback: ( rows: ObjectRecord[], - columns: ColumnDefinition[], + columns: Pick< + ColumnDefinition, + 'label' | 'type' | 'metadata' + >[], ) => void | Promise; viewType?: ViewType; }; @@ -93,11 +98,35 @@ export const useRecordIndexLazyFetchRecords = ({ contextStoreAnyFieldFilterValue, }); - const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + const visibleRecordFields = useRecoilComponentValue( + visibleRecordFieldsComponentSelector, + ); - const finalColumns = [ - ...columnDefinitions, + const finalColumns: Pick< + ColumnDefinition, + 'label' | 'type' | 'metadata' + >[] = [ + ...visibleRecordFields + .map((field: RecordField) => { + const fieldMetadataItem = objectMetadataItem.fields.find( + (fieldMetadataItem) => + fieldMetadataItem.id === field.fieldMetadataItemId, + ); + + if (!fieldMetadataItem) { + return null; + } + + return { + label: fieldMetadataItem.label, + type: fieldMetadataItem.type, + metadata: { + fieldName: fieldMetadataItem.name, + relationType: fieldMetadataItem.relation?.type, + }, + }; + }) + .filter(isDefined), ...(hiddenKanbanFieldColumn && viewType === ViewType.Kanban ? [hiddenKanbanFieldColumn] : []), diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts index 2994770e12..35c34e304d 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; +import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { useUpsertRecordSort } from '@/object-record/record-sort/hooks/useUpsertRecordSort'; import { type RecordSort } from '@/object-record/record-sort/types/RecordSort'; @@ -20,7 +20,7 @@ export const useHandleToggleColumnSort = ({ }); const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + useColumnDefinitionsFromObjectMetadata(objectMetadataItem); const { upsertRecordSort } = useUpsertRecordSort(); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts index 8e20c75771..94317db901 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { v4 } from 'uuid'; -import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; +import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; @@ -26,7 +26,7 @@ export const useHandleToggleTrashColumnFilter = ({ }); const { columnDefinitions } = - useColumnDefinitionsFromFieldMetadata(objectMetadataItem); + useColumnDefinitionsFromObjectMetadata(objectMetadataItem); const isSoftDeleteFilterActiveComponentRecoilState = useRecoilComponentCallbackState(