From 324dadca637b861b8f7fff89b1c3dc9f3f8dc687 Mon Sep 17 00:00:00 2001 From: Naifer <161821705+omarNaifer12@users.noreply.github.com> Date: Fri, 4 Jul 2025 21:58:24 +0100 Subject: [PATCH] feat: enable export of deleted records (#12776) resolve #12662 This PR enables exporting deleted records by detecting when deleted view mode is active and adding deletedAt: { is: 'NOT_NULL' } to graphqlFilter. --------- Co-authored-by: Charles Bochet --- .../constants/DefaultRecordActionsConfig.tsx | 25 +++++-- .../constants/WorkflowActionsConfig.tsx | 11 ++- .../constants/WorkflowRunsActionsConfig.tsx | 9 ++- .../WorkflowVersionsActionsConfig.tsx | 9 ++- .../ExportMultipleRecordsAction.tsx | 4 +- .../components/ExportSingleRecordAction.tsx | 30 ++++++++ .../types/SingleRecordActionsKey.ts | 3 +- .../mock/action-menu-actions.mock.tsx | 7 +- .../computeContextStoreFilters.test.ts | 11 ++- .../utils/computeContextStoreFilters.ts | 16 +++-- ...ts => useRecordIndexExportRecords.test.ts} | 5 +- ...=> useRecordIndexLazyFetchRecords.test.ts} | 12 ++-- ...ords.ts => useRecordIndexExportRecords.ts} | 13 ++-- ...s.ts => useRecordIndexLazyFetchRecords.ts} | 10 +-- .../hooks/useExportSingleRecord.ts | 69 +++++++++++++++++++ 15 files changed, 186 insertions(+), 48 deletions(-) create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/ExportSingleRecordAction.tsx rename packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/{useExportRecords.test.ts => useRecordIndexExportRecords.test.ts} (96%) rename packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/{useExportFetchRecords.test.ts => useRecordIndexLazyFetchRecords.test.ts} (97%) rename packages/twenty-front/src/modules/object-record/record-index/export/hooks/{useExportRecords.ts => useRecordIndexExportRecords.ts} (93%) rename packages/twenty-front/src/modules/object-record/record-index/export/hooks/{useExportFetchRecords.ts => useRecordIndexLazyFetchRecords.ts} (98%) create mode 100644 packages/twenty-front/src/modules/object-record/record-show/hooks/useExportSingleRecord.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx index 66e59e112..bdf8aafe0 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx @@ -13,6 +13,7 @@ import { AddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-a import { DeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/components/DeleteSingleRecordAction'; import { DestroySingleRecordAction } from '@/action-menu/actions/record-actions/single-record/components/DestroySingleRecordAction'; import { ExportNoteActionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/components/ExportNoteActionSingleRecordAction'; +import { ExportSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/components/ExportSingleRecordAction'; import { NavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/components/NavigateToNextRecordSingleRecordAction'; import { NavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/components/NavigateToPreviousRecordSingleRecordAction'; import { RemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/components/RemoveFromFavoritesSingleRecordAction'; @@ -137,10 +138,10 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record< ], component: , }, - [SingleRecordActionKeys.EXPORT]: { + [SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX]: { type: ActionType.Standard, scope: ActionScope.RecordSelection, - key: SingleRecordActionKeys.EXPORT, + key: SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX, label: msg`Export`, shortLabel: msg`Export`, position: 4, @@ -149,12 +150,24 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record< isPinned: false, shouldBeRegistered: ({ selectedRecord }) => isDefined(selectedRecord) && !selectedRecord.isRemote, - availableOn: [ - ActionViewType.SHOW_PAGE, - ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, - ], + availableOn: [ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION], component: , }, + [SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW]: { + type: ActionType.Standard, + scope: ActionScope.RecordSelection, + key: SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW, + label: msg`Export`, + shortLabel: msg`Export`, + position: 4, + Icon: IconFileExport, + accent: 'default', + isPinned: false, + shouldBeRegistered: ({ selectedRecord }) => + isDefined(selectedRecord) && !selectedRecord.isRemote, + availableOn: [ActionViewType.SHOW_PAGE], + component: , + }, [MultipleRecordsActionKeys.EXPORT]: { type: ActionType.Standard, scope: ActionScope.RecordSelection, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx index 10144c61a..847e9a1cf 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx @@ -222,7 +222,8 @@ export const WORKFLOW_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ SingleRecordActionKeys.DELETE, SingleRecordActionKeys.DESTROY, SingleRecordActionKeys.RESTORE, - SingleRecordActionKeys.EXPORT, + SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX, + SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW, MultipleRecordsActionKeys.DELETE, MultipleRecordsActionKeys.DESTROY, MultipleRecordsActionKeys.RESTORE, @@ -259,14 +260,18 @@ export const WORKFLOW_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ position: 12, label: msg`Permanently destroy workflow`, }, - [SingleRecordActionKeys.EXPORT]: { + [SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX]: { position: 13, label: msg`Export workflow`, shouldBeRegistered: ({ selectedRecord }) => !isDefined(selectedRecord?.deletedAt), }, - [MultipleRecordsActionKeys.EXPORT]: { + [SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW]: { position: 14, + label: msg`Export workflow`, + }, + [MultipleRecordsActionKeys.EXPORT]: { + position: 15, label: msg`Export workflows`, }, [NoSelectionRecordActionKeys.EXPORT_VIEW]: { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.tsx index a76ce0223..868a8adfb 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.tsx @@ -51,7 +51,8 @@ export const WORKFLOW_RUNS_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ SingleRecordActionKeys.REMOVE_FROM_FAVORITES, SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD, SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD, - SingleRecordActionKeys.EXPORT, + SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX, + SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW, MultipleRecordsActionKeys.EXPORT, NoSelectionRecordActionKeys.EXPORT_VIEW, NoSelectionRecordActionKeys.SEE_DELETED_RECORDS, @@ -73,7 +74,11 @@ export const WORKFLOW_RUNS_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ isPinned: false, position: 3, }, - [SingleRecordActionKeys.EXPORT]: { + [SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX]: { + position: 4, + label: msg`Export run`, + }, + [SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW]: { position: 4, label: msg`Export run`, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx index 11dea90db..b05d292f5 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx @@ -118,7 +118,8 @@ export const WORKFLOW_VERSIONS_ACTIONS_CONFIG = inheritActionsFromDefaultConfig( SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD, SingleRecordActionKeys.ADD_TO_FAVORITES, SingleRecordActionKeys.REMOVE_FROM_FAVORITES, - SingleRecordActionKeys.EXPORT, + SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX, + SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW, MultipleRecordsActionKeys.EXPORT, NoSelectionRecordActionKeys.EXPORT_VIEW, NoSelectionRecordActionKeys.SEE_DELETED_RECORDS, @@ -140,7 +141,11 @@ export const WORKFLOW_VERSIONS_ACTIONS_CONFIG = inheritActionsFromDefaultConfig( position: 6, isPinned: false, }, - [SingleRecordActionKeys.EXPORT]: { + [SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX]: { + position: 7, + label: msg`Export version`, + }, + [SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW]: { position: 7, label: msg`Export version`, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/ExportMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/ExportMultipleRecordsAction.tsx index 294fa6dcc..89f505a6f 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/ExportMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/ExportMultipleRecordsAction.tsx @@ -1,7 +1,7 @@ import { Action } from '@/action-menu/actions/components/Action'; import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; -import { useExportRecords } from '@/object-record/record-index/export/hooks/useExportRecords'; +import { useRecordIndexExportRecords } from '@/object-record/record-index/export/hooks/useRecordIndexExportRecords'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -16,7 +16,7 @@ export const ExportMultipleRecordsAction = () => { throw new Error('Current view ID is not defined'); } - const { download } = useExportRecords({ + const { download } = useRecordIndexExportRecords({ delayMs: 100, objectMetadataItem, recordIndexId: getRecordIndexIdFromObjectNamePluralAndViewId( diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/ExportSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/ExportSingleRecordAction.tsx new file mode 100644 index 000000000..28a485829 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/ExportSingleRecordAction.tsx @@ -0,0 +1,30 @@ +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow'; +import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; +import { useExportSingleRecord } from '@/object-record/record-show/hooks/useExportSingleRecord'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +import { Action } from '@/action-menu/actions/components/Action'; + +export const ExportSingleRecordAction = () => { + const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow(); + + const contextStoreCurrentViewId = useRecoilComponentValueV2( + contextStoreCurrentViewIdComponentState, + ); + + if (!contextStoreCurrentViewId) { + throw new Error('Current view ID is not defined'); + } + + const recordId = useSelectedRecordIdOrThrow(); + + const filename = `${objectMetadataItem.nameSingular}.csv`; + const { download } = useExportSingleRecord({ + filename, + objectMetadataItem, + recordId, + }); + + return ; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts index 019962423..229ca24fb 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey.ts @@ -6,6 +6,7 @@ export enum SingleRecordActionKeys { NAVIGATE_TO_NEXT_RECORD = 'navigate-to-next-record-single-record', NAVIGATE_TO_PREVIOUS_RECORD = 'navigate-to-previous-record-single-record', EXPORT_NOTE_TO_PDF = 'export-note-to-pdf-single-record', - EXPORT = 'export-single-record', + EXPORT_FROM_RECORD_INDEX = 'export-from-record-index-single-record', + EXPORT_FROM_RECORD_SHOW = 'export-from-record-show-single-record', RESTORE = 'restore-single-record', } diff --git a/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx b/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx index 888d0c788..e3fa6a26e 100644 --- a/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx +++ b/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx @@ -45,7 +45,7 @@ export const createMockActionMenuActions = ({ { type: ActionType.Standard, scope: ActionScope.RecordSelection, - key: SingleRecordActionKeys.EXPORT, + key: SingleRecordActionKeys.EXPORT_FROM_RECORD_INDEX, label: msg`Export`, shortLabel: msg`Export`, position: 4, @@ -53,10 +53,7 @@ export const createMockActionMenuActions = ({ accent: 'default', isPinned: false, shouldBeRegistered: () => true, - availableOn: [ - ActionViewType.SHOW_PAGE, - ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, - ], + availableOn: [ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION], component: , }, { diff --git a/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts b/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts index 94a9a5efb..bf0f47e7c 100644 --- a/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts +++ b/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts @@ -29,9 +29,14 @@ describe('computeContextStoreFilters', () => { ); expect(filters).toEqual({ - id: { - in: ['1', '2', '3'], - }, + and: [ + { + id: { + in: ['1', '2', '3'], + }, + }, + {}, + ], }); }); diff --git a/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts b/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts index 73381a503..7842a1c8e 100644 --- a/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts +++ b/packages/twenty-front/src/modules/context-store/utils/computeContextStoreFilters.ts @@ -34,19 +34,21 @@ export const computeContextStoreFilters = ( ]); } if (contextStoreTargetedRecordsRule.mode === 'selection') { - queryFilter = + queryFilter = makeAndFilterVariables([ contextStoreTargetedRecordsRule.selectedRecordIds.length > 0 ? { id: { in: contextStoreTargetedRecordsRule.selectedRecordIds, }, } - : computeRecordGqlOperationFilter({ - filterValueDependencies, - fields: objectMetadataItem?.fields ?? [], - recordFilters: contextStoreFilters, - recordFilterGroups: [], - }); + : undefined, + computeRecordGqlOperationFilter({ + filterValueDependencies, + fields: objectMetadataItem?.fields ?? [], + recordFilters: contextStoreFilters, + recordFilterGroups: [], + }), + ]); } return queryFilter; diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportRecords.test.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexExportRecords.test.ts similarity index 96% rename from packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportRecords.test.ts rename to packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexExportRecords.test.ts index 181990a42..c0f61ad1a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportRecords.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexExportRecords.test.ts @@ -2,7 +2,10 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata' import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql'; -import { displayedExportProgress, generateCsv } from '../useExportRecords'; +import { + displayedExportProgress, + generateCsv, +} from '../useRecordIndexExportRecords'; jest.useFakeTimers(); diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts similarity index 97% rename from packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts rename to packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts index e91d76750..c75b40107 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts @@ -3,8 +3,8 @@ import { act } from 'react'; import { percentage, sleep, - useExportFetchRecords, -} from '../useExportFetchRecords'; + useRecordIndexLazyFetchRecords, +} from '../useRecordIndexLazyFetchRecords'; import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; @@ -107,7 +107,7 @@ describe('useRecordData', () => { const { result } = renderHook( () => - useExportFetchRecords({ + useRecordIndexLazyFetchRecords({ recordIndexId, objectMetadataItem, pageSize: 30, @@ -134,7 +134,7 @@ describe('useRecordData', () => { mockFetchAllRecords.mockReturnValue([mockPerson]); const { result } = renderHook( () => - useExportFetchRecords({ + useRecordIndexLazyFetchRecords({ recordIndexId, objectMetadataItem, callback, @@ -167,7 +167,7 @@ describe('useRecordData', () => { ); return { - tableData: useExportFetchRecords({ + tableData: useRecordIndexLazyFetchRecords({ recordIndexId, objectMetadataItem, callback, @@ -260,7 +260,7 @@ describe('useRecordData', () => { ); return { - tableData: useExportFetchRecords({ + tableData: useRecordIndexLazyFetchRecords({ recordIndexId, objectMetadataItem, callback, diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts similarity index 93% rename from packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportRecords.ts rename to packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts index 890022662..81581a8a9 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportRecords.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts @@ -7,8 +7,8 @@ import { useExportProcessRecordsForCSV } from '@/object-record/object-options-dr import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { UseRecordDataOptions, - useExportFetchRecords, -} from '@/object-record/record-index/export/hooks/useExportFetchRecords'; + useRecordIndexLazyFetchRecords, +} from '@/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { COMPOSITE_FIELD_SUB_FIELD_LABELS } from '@/settings/data-model/constants/CompositeFieldSubFieldLabel'; @@ -20,7 +20,10 @@ import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; type GenerateExportOptions = { - columns: ColumnDefinition[]; + columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[]; rows: Record[]; }; @@ -121,7 +124,7 @@ type UseExportTableDataOptions = Omit & { filename: string; }; -export const useExportRecords = ({ +export const useRecordIndexExportRecords = ({ delayMs, filename, maximumRequests = 100, @@ -144,7 +147,7 @@ export const useExportRecords = ({ [filename, processRecordsForCSVExport], ); - const { getTableData: download, progress } = useExportFetchRecords({ + const { getTableData: download, progress } = useRecordIndexLazyFetchRecords({ delayMs, maximumRequests, objectMetadataItem, diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts similarity index 98% rename from packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts rename to packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts index 2f8c0d90e..88d5b57d7 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts @@ -36,7 +36,7 @@ export type UseRecordDataOptions = { viewType?: ViewType; }; -export const useExportFetchRecords = ({ +export const useRecordIndexLazyFetchRecords = ({ objectMetadataItem, delayMs, maximumRequests = 100, @@ -74,6 +74,10 @@ export const useExportFetchRecords = ({ const { filterValueDependencies } = useFilterValueDependencies(); + const findManyRecordsParams = useFindManyRecordIndexTableParams( + objectMetadataItem.nameSingular, + ); + const queryFilter = computeContextStoreFilters( contextStoreTargetedRecordsRule, contextStoreFilters, @@ -81,10 +85,6 @@ export const useExportFetchRecords = ({ filterValueDependencies, ); - const findManyRecordsParams = useFindManyRecordIndexTableParams( - objectMetadataItem.nameSingular, - ); - const finalColumns = [ ...columns, ...(hiddenKanbanFieldColumn && viewType === ViewType.Kanban diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useExportSingleRecord.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useExportSingleRecord.ts new file mode 100644 index 000000000..eaf36a0b3 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useExportSingleRecord.ts @@ -0,0 +1,69 @@ +import { useMemo } from 'react'; + +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { useExportProcessRecordsForCSV } from '@/object-record/object-options-dropdown/hooks/useExportProcessRecordsForCSV'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { csvDownloader } from '@/object-record/record-index/export/hooks/useRecordIndexExportRecords'; +import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isDefined } from 'twenty-shared/utils'; + +export type UseSingleExportTableDataOptions = { + filename: string; + objectMetadataItem: ObjectMetadataItem; + recordId: string; +}; +export const useExportSingleRecord = ({ + filename, + objectMetadataItem, + recordId, +}: UseSingleExportTableDataOptions) => { + const { processRecordsForCSVExport } = useExportProcessRecordsForCSV( + objectMetadataItem.nameSingular, + ); + + const downloadCsv = useMemo( + () => + ( + record: ObjectRecord, + columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[], + ) => { + const recordToArray = [record]; + const recordsProcessedForExport = + processRecordsForCSVExport(recordToArray); + + csvDownloader(filename, { rows: recordsProcessedForExport, columns }); + }, + [filename, processRecordsForCSVExport], + ); + + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = objectMetadataItem.fields + .filter((field) => field.isActive) + .map((field, index) => + formatFieldMetadataItemAsColumnDefinition({ + field, + objectMetadataItem, + position: index, + }), + ); + const { record, error } = useFindOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + objectRecordId: recordId, + withSoftDeleted: true, + }); + const download = () => { + if (isDefined(error) || !isDefined(record)) { + return; + } + downloadCsv(record, columns); + }; + return { download }; +};