From e3f7a0572e398d5aac6f70dfd690e18b993459b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:15:27 +0100 Subject: [PATCH] 9260 refactor multiple record actions and no selection actions (#9314) Closes #9260 - Refactored multiple record actions and no selection record actions to use config file - Simplified actions registration logic - Updated tests --- .../RecordActionMenuEntriesSetter.tsx | 104 ++++---- .../components/RegisterRecordActionEffect.tsx | 55 ++++ .../DefaultActionsConfigV1.ts} | 56 +++- .../DefaultActionsConfigV2.ts} | 56 +++- .../WorkflowActionsConfig.ts} | 52 +++- .../WorkflowRunsActionsConfig.ts} | 36 ++- .../WorkflowVersionsActionsConfig.ts} | 36 ++- ...ipleRecordsActionMenuEntrySetterEffect.tsx | 24 -- .../useDeleteMultipleRecordsAction.test.tsx | 151 ++++------- .../useExportMultipleRecordsAction.test.tsx | 124 +++------ .../hooks/useDeleteMultipleRecordsAction.tsx | 241 +++++++----------- .../hooks/useExportMultipleRecordsAction.tsx | 55 +--- .../hooks/useMultipleRecordsActions.tsx | 38 --- ...NoSelectionActionMenuEntrySetterEffect.tsx | 26 -- ...ExportViewNoSelectionRecordAction.test.tsx | 108 -------- .../useExportViewNoSelectionRecordAction.tsx | 54 ---- .../hooks/useNoSelectionRecordActions.tsx | 28 -- ...ingleRecordActionMenuEntrySetterEffect.tsx | 34 --- ...eAddToFavoritesSingleRecordAction.test.tsx | 3 - .../useDeleteSingleRecordAction.test.tsx | 1 - ...veFromFavoritesSingleRecordAction.test.tsx | 3 - .../useAddToFavoritesSingleRecordAction.ts | 9 +- .../hooks/useDeleteSingleRecordAction.tsx | 126 ++++----- .../hooks/useDestroySingleRecordAction.tsx | 98 +++---- .../hooks/useExportNoteAction.ts | 68 ++--- ...eNavigateToNextRecordSingleRecordAction.ts | 9 +- ...igateToPreviousRecordSingleRecordAction.ts | 10 +- ...seRemoveFromFavoritesSingleRecordAction.ts | 9 +- .../hooks/useSelectedRecordIdOrThrow.tsx | 18 ++ .../single-record/utils/getActionConfig.ts | 27 -- ...ateDraftWorkflowSingleRecordAction.test.ts | 10 +- ...dVersionWorkflowSingleRecordAction.test.ts | 10 +- ...activateWorkflowSingleRecordAction.test.ts | 15 +- ...ardDraftWorkflowSingleRecordAction.test.ts | 20 +- ...ActivateDraftWorkflowSingleRecordAction.ts | 9 +- ...lishedVersionWorkflowSingleRecordAction.ts | 9 +- ...useDeactivateWorkflowSingleRecordAction.ts | 9 +- ...eDiscardDraftWorkflowSingleRecordAction.ts | 9 +- ...ActiveVersionWorkflowSingleRecordAction.ts | 9 +- .../useSeeRunsWorkflowSingleRecordAction.ts | 9 +- ...seSeeVersionsWorkflowSingleRecordAction.ts | 9 +- .../useTestWorkflowSingleRecordAction.ts | 9 +- ...eeRunsWorkflowVersionSingleRecordAction.ts | 9 +- ...rsionsWorkflowVersionSingleRecordAction.ts | 14 +- ...DraftWorkflowVersionSingleRecordAction.tsx | 9 +- .../action-menu/actions/types/ActionHook.ts | 14 + .../actions/types/SingleRecordActionHook.ts | 20 -- .../actions/utils/getActionConfig.ts | 25 ++ .../actions/utils/getActionViewType.ts | 32 +++ .../useActionMenuEntriesWithCallbacks.ts | 70 ----- .../testing/jest/JestContextStoreSetter.tsx | 10 + ...taAndApolloMocksAndContextStoreWrapper.tsx | 5 + 52 files changed, 871 insertions(+), 1123 deletions(-) create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/constants/DefaultSingleRecordActionsConfigV1.ts => constants/DefaultActionsConfigV1.ts} (50%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/constants/DefaultSingleRecordActionsConfigV2.ts => constants/DefaultActionsConfigV2.ts} (70%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts => constants/WorkflowActionsConfig.ts} (83%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts => constants/WorkflowRunsActionsConfig.ts} (67%) rename packages/twenty-front/src/modules/action-menu/actions/record-actions/{single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts => constants/WorkflowVersionsActionsConfig.ts} (78%) delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx index 02dfb9cc04c7..6927fe94c3ac 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx @@ -1,13 +1,10 @@ -import { MultipleRecordsActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect'; -import { NoSelectionActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect'; -import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect'; +import { RegisterRecordActionEffect } from '@/action-menu/actions/record-actions/components/RegisterRecordActionEffect'; import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter'; -import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; +import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig'; +import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; -import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; @@ -19,33 +16,13 @@ export const RecordActionMenuEntriesSetter = () => { const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataIdComponentState, ); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (item) => item.id === contextStoreCurrentObjectMetadataId, ); - if ( - !isDefined(contextStoreCurrentObjectMetadataId) || - !isDefined(objectMetadataItem) - ) { - return null; - } - - return ( - - ); -}; - -const ActionEffects = ({ - objectMetadataItemId, -}: { - objectMetadataItemId: string; -}) => { - const { objectMetadataItem } = useObjectMetadataItemById({ - objectId: objectMetadataItemId, - }); - const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( contextStoreTargetedRecordsRuleComponentState, ); @@ -58,43 +35,52 @@ const ActionEffects = ({ FeatureFlagKey.IsWorkflowEnabled, ); + const isPageHeaderV2Enabled = useIsFeatureEnabled( + FeatureFlagKey.IsPageHeaderV2Enabled, + ); + + if ( + !isDefined(contextStoreCurrentObjectMetadataId) || + !isDefined(objectMetadataItem) + ) { + return null; + } + + const viewType = getActionViewType( + contextStoreCurrentViewType, + contextStoreTargetedRecordsRule, + ); + + const actionConfig = getActionConfig( + objectMetadataItem, + isPageHeaderV2Enabled, + ); + + const actionsToRegister = isDefined(viewType) + ? Object.values(actionConfig ?? {}).filter((action) => + action.availableOn?.includes(viewType), + ) + : []; + return ( <> - {contextStoreTargetedRecordsRule.mode === 'selection' && - contextStoreTargetedRecordsRule.selectedRecordIds.length === 0 && ( - ( + + ))} + + {isWorkflowEnabled && + !( + contextStoreTargetedRecordsRule?.mode === 'selection' && + contextStoreTargetedRecordsRule?.selectedRecordIds.length === 0 + ) && ( + )} - {contextStoreTargetedRecordsRule.mode === 'selection' && - contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && ( - <> - {contextStoreCurrentViewType === ContextStoreViewType.ShowPage && ( - - )} - {(contextStoreCurrentViewType === ContextStoreViewType.Table || - contextStoreCurrentViewType === ContextStoreViewType.Kanban) && ( - - )} - {isWorkflowEnabled && ( - - )} - - )} - {(contextStoreTargetedRecordsRule.mode === 'exclusion' || - contextStoreTargetedRecordsRule.selectedRecordIds.length > 1) && ( - - )} ); }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx new file mode 100644 index 000000000000..25159234c624 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RegisterRecordActionEffect.tsx @@ -0,0 +1,55 @@ +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; +import { wrapActionInCallbacks } from '@/action-menu/actions/utils/wrapActionInCallbacks'; +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; +import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { useContext, useEffect } from 'react'; + +type RegisterRecordActionEffectProps = { + action: ActionMenuEntry & { + actionHook: ActionHook; + }; + objectMetadataItem: ObjectMetadataItem; +}; + +export const RegisterRecordActionEffect = ({ + action, + objectMetadataItem, +}: RegisterRecordActionEffectProps) => { + const { shouldBeRegistered, onClick, ConfirmationModal } = action.actionHook({ + objectMetadataItem, + }); + + const { onActionStartedCallback, onActionExecutedCallback } = + useContext(ActionMenuContext); + + const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); + + const wrappedAction = wrapActionInCallbacks({ + action: { + ...action, + onClick, + ConfirmationModal, + }, + onActionStartedCallback, + onActionExecutedCallback, + }); + + useEffect(() => { + if (shouldBeRegistered) { + addActionMenuEntry(wrappedAction); + } + + return () => { + removeActionMenuEntry(wrappedAction.key); + }; + }, [ + addActionMenuEntry, + removeActionMenuEntry, + shouldBeRegistered, + wrappedAction, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts similarity index 50% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts index ba6584225023..12da07f3f96a 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV1.ts @@ -1,20 +1,29 @@ +import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction'; import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, ActionMenuEntryType, } from '@/action-menu/types/ActionMenuEntry'; -import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui'; +import { + IconDatabaseExport, + IconHeart, + IconHeartOff, + IconTrash, +} from 'twenty-ui'; -export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record< +export const DEFAULT_ACTIONS_CONFIG_V1: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { addToFavoritesSingleRecord: { @@ -58,4 +67,43 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record< ], actionHook: useDeleteSingleRecordAction, }, + deleteMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.DELETE, + label: 'Delete records', + shortLabel: 'Delete', + position: 3, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useDeleteMultipleRecordsAction, + }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 4, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 5, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts similarity index 70% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts index 6e41b853e495..f7dd5b223500 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultActionsConfigV2.ts @@ -1,3 +1,7 @@ +import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction'; import { useDestroySingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction'; @@ -6,8 +10,8 @@ import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction'; import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -16,6 +20,7 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconFileExport, IconHeart, IconHeartOff, @@ -23,10 +28,10 @@ import { IconTrashX, } from 'twenty-ui'; -export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< +export const DEFAULT_ACTIONS_CONFIG_V2: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { exportNoteToPdf: { @@ -87,13 +92,52 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< ], actionHook: useDeleteSingleRecordAction, }, + deleteMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.DELETE, + label: 'Delete records', + shortLabel: 'Delete', + position: 4, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useDeleteMultipleRecordsAction, + }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 5, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 6, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, destroySingleRecord: { type: ActionMenuEntryType.Standard, scope: ActionMenuEntryScope.RecordSelection, key: SingleRecordActionKeys.DESTROY, label: 'Permanently destroy record', shortLabel: 'Destroy', - position: 4, + position: 7, Icon: IconTrashX, accent: 'danger', isPinned: true, @@ -109,7 +153,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< key: SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD, label: 'Navigate to previous record', shortLabel: '', - position: 5, + position: 8, isPinned: true, Icon: IconChevronUp, availableOn: [ActionViewType.SHOW_PAGE], @@ -121,7 +165,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< key: SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD, label: 'Navigate to next record', shortLabel: '', - position: 6, + position: 9, isPinned: true, Icon: IconChevronDown, availableOn: [ActionViewType.SHOW_PAGE], diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts similarity index 83% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts index f6d85ae3b5ee..773ff4ce673d 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.ts @@ -1,3 +1,7 @@ +import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction'; import { useDestroySingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction'; @@ -14,8 +18,8 @@ import { useSeeRunsWorkflowSingleRecordAction } from '@/action-menu/actions/reco import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction'; import { useTestWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction'; import { WorkflowSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/workflow-actions/types/WorkflowSingleRecordActionsKeys'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -24,6 +28,7 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconHeart, IconHeartOff, IconHistory, @@ -35,10 +40,10 @@ import { IconTrashX, } from 'twenty-ui'; -export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record< +export const WORKFLOW_ACTIONS_CONFIG: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { activateWorkflowDraftSingleRecord: { @@ -229,13 +234,26 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record< ], actionHook: useDeleteSingleRecordAction, }, + deleteMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.DELETE, + label: 'Delete records', + shortLabel: 'Delete', + position: 14, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useDeleteMultipleRecordsAction, + }, destroySingleRecord: { type: ActionMenuEntryType.Standard, scope: ActionMenuEntryScope.RecordSelection, key: SingleRecordActionKeys.DESTROY, label: 'Permanently destroy record', shortLabel: 'Destroy', - position: 14, + position: 15, Icon: IconTrashX, accent: 'danger', isPinned: false, @@ -245,4 +263,30 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record< ], actionHook: useDestroySingleRecordAction, }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 16, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 17, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.ts similarity index 67% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.ts index 7f6f24633207..ee0f56f972be 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig.ts @@ -1,10 +1,13 @@ +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction'; import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction'; import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -13,14 +16,15 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconHeart, IconHeartOff, } from 'twenty-ui'; -export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record< +export const WORKFLOW_RUNS_ACTIONS_CONFIG: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { addToFavoritesSingleRecord: { @@ -77,4 +81,30 @@ export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record< availableOn: [ActionViewType.SHOW_PAGE], actionHook: useNavigateToNextRecordSingleRecordAction, }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 4, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 5, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.ts similarity index 78% rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.ts index 95f531329704..2fbff23aabe3 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.ts @@ -1,3 +1,6 @@ +import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; +import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction'; import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction'; import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction'; @@ -7,8 +10,8 @@ import { useSeeRunsWorkflowVersionSingleRecordAction } from '@/action-menu/actio import { useSeeVersionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction'; import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction'; import { WorkflowVersionSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/types/WorkflowVersionSingleRecordActionsKeys'; +import { ActionHook } from '@/action-menu/actions/types/ActionHook'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook'; import { ActionMenuEntry, ActionMenuEntryScope, @@ -17,6 +20,7 @@ import { import { IconChevronDown, IconChevronUp, + IconDatabaseExport, IconHeart, IconHeartOff, IconHistory, @@ -24,10 +28,10 @@ import { IconPencil, } from 'twenty-ui'; -export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record< +export const WORKFLOW_VERSIONS_ACTIONS_CONFIG: Record< string, ActionMenuEntry & { - actionHook: SingleRecordActionHook; + actionHook: ActionHook; } > = { useAsDraftWorkflowVersionSingleRecord: { @@ -125,4 +129,30 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record< ], actionHook: useRemoveFromFavoritesSingleRecordAction, }, + exportMultipleRecords: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: MultipleRecordsActionKeys.EXPORT, + label: 'Export records', + shortLabel: 'Export', + position: 8, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, + exportView: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: NoSelectionRecordActionKeys.EXPORT_VIEW, + label: 'Export view', + shortLabel: 'Export', + position: 9, + Icon: IconDatabaseExport, + accent: 'default', + isPinned: false, + availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION], + actionHook: useExportMultipleRecordsAction, + }, }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx deleted file mode 100644 index 67b96f1b7ab4..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useMultipleRecordsActions } from '@/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useEffect } from 'react'; - -export const MultipleRecordsActionMenuEntrySetterEffect = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { registerMultipleRecordsActions, unregisterMultipleRecordsActions } = - useMultipleRecordsActions({ - objectMetadataItem, - }); - - useEffect(() => { - registerMultipleRecordsActions(); - - return () => { - unregisterMultipleRecordsActions(); - }; - }, [registerMultipleRecordsActions, unregisterMultipleRecordsActions]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx index 447a08447877..9eba7ca23eea 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx @@ -1,132 +1,87 @@ -import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { expect } from '@storybook/test'; -import { renderHook } from '@testing-library/react'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; -import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getPeopleMock } from '~/testing/mock-data/people'; import { useDeleteMultipleRecordsAction } from '../useDeleteMultipleRecordsAction'; +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +)!; + +const peopleMock = getPeopleMock(); + +const deleteManyRecordsMock = jest.fn(); +const resetTableRowSelectionMock = jest.fn(); + jest.mock('@/object-record/hooks/useDeleteManyRecords', () => ({ useDeleteManyRecords: () => ({ - deleteManyRecords: jest.fn(), + deleteManyRecords: deleteManyRecordsMock, }), })); -jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({ - useDeleteFavorite: () => ({ - deleteFavorite: jest.fn(), - }), -})); -jest.mock('@/favorites/hooks/useFavorites', () => ({ - useFavorites: () => ({ - sortedFavorites: [], - }), + +jest.mock('@/object-record/hooks/useLazyFetchAllRecords', () => ({ + useLazyFetchAllRecords: () => { + return { + fetchAllRecords: () => [peopleMock[0], peopleMock[1]], + }; + }, })); + jest.mock('@/object-record/record-table/hooks/useRecordTable', () => ({ useRecordTable: () => ({ - resetTableRowSelection: jest.fn(), + resetTableRowSelection: resetTableRowSelectionMock, }), })); -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', -)!; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ +const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ apolloMocks: [], - onInitializeRecoilSnapshot: ({ set }) => { - set( - contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ - instanceId: '1', - }), - 3, - ); + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + personMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[0].id, peopleMock[1].id], + }, + contextStoreNumberOfSelectedRecords: 2, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]); + snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]); }, }); describe('useDeleteMultipleRecordsAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - {children} - - - - ); - - it('should register delete action', () => { + it('should call deleteManyRecords on click', async () => { const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeleteMultipleRecordsAction: useDeleteMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; + () => + useDeleteMultipleRecordsAction({ + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper, }, - { wrapper }, ); + expect(result.current.ConfirmationModal?.props?.isOpen).toBe(false); + act(() => { - result.current.useDeleteMultipleRecordsAction.registerDeleteMultipleRecordsAction( - { position: 1 }, - ); + result.current.onClick(); }); - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('delete-multiple-records'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('delete-multiple-records')?.position, - ).toBe(1); - }); - - it('should unregister delete action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeleteMultipleRecordsAction: useDeleteMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); + expect(result.current.ConfirmationModal?.props?.isOpen).toBe(true); act(() => { - result.current.useDeleteMultipleRecordsAction.registerDeleteMultipleRecordsAction( - { position: 1 }, - ); + result.current.ConfirmationModal?.props?.onConfirmClick(); }); - expect(result.current.actionMenuEntries.size).toBe(1); + await waitFor(() => { + expect(resetTableRowSelectionMock).toHaveBeenCalled(); - act(() => { - result.current.useDeleteMultipleRecordsAction.unregisterDeleteMultipleRecordsAction(); + expect(deleteManyRecordsMock).toHaveBeenCalledWith([ + peopleMock[0].id, + peopleMock[1].id, + ]); }); - - expect(result.current.actionMenuEntries.size).toBe(0); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx index 7d9dbff6ef77..46e645080da9 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx @@ -1,105 +1,59 @@ -import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { expect } from '@storybook/test'; -import { renderHook } from '@testing-library/react'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; -import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getPeopleMock } from '~/testing/mock-data/people'; import { useExportMultipleRecordsAction } from '../useExportMultipleRecordsAction'; -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', )!; -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); +const peopleMock = getPeopleMock(); -describe('useExportMultipleRecordsAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register export multiple records action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); +const downloadMock = jest.fn(); - return { - actionMenuEntries, - useExportMultipleRecordsAction: useExportMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useExportMultipleRecordsAction.registerExportMultipleRecordsAction( - { position: 1 }, - ); - }); +jest.mock('@/object-record/record-index/export/hooks/useExportRecords', () => ({ + useExportRecords: () => ({ + download: downloadMock, + }), +})); - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('export-multiple-records'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('export-multiple-records')?.position, - ).toBe(1); - }); +const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + personMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[0].id, peopleMock[1].id], + }, + contextStoreNumberOfSelectedRecords: 2, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]); + snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]); + }, +}); - it('should unregister export multiple records action', () => { +describe('useExportMultipleRecordsAction', () => { + it('should call exportManyRecords on click', async () => { const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useExportMultipleRecordsAction: useExportMultipleRecordsAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; + () => + useExportMultipleRecordsAction({ + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper, }, - { wrapper }, ); act(() => { - result.current.useExportMultipleRecordsAction.registerExportMultipleRecordsAction( - { position: 1 }, - ); + result.current.onClick(); }); - expect(result.current.actionMenuEntries.size).toBe(1); - - act(() => { - result.current.useExportMultipleRecordsAction.unregisterExportMultipleRecordsAction(); + await waitFor(() => { + expect(downloadMock).toHaveBeenCalled(); }); - - expect(result.current.actionMenuEntries.size).toBe(0); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx index 7c48a63a0b92..dfcbb5350d48 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx @@ -1,15 +1,9 @@ -import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; -import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; + import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize'; import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; @@ -18,140 +12,101 @@ import { FilterOperand } from '@/object-record/object-filter-dropdown/types/Filt import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; -import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useCallback, useContext, useState } from 'react'; -import { IconTrash, isDefined } from 'twenty-ui'; - -export const useDeleteMultipleRecordsAction = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = - useState(false); - - const { resetTableRowSelection } = useRecordTable({ - recordTableId: objectMetadataItem.namePlural, - }); - - const { deleteManyRecords } = useDeleteManyRecords({ - objectNameSingular: objectMetadataItem.nameSingular, - }); - - const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2( - contextStoreNumberOfSelectedRecordsComponentState, - ); - - const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( - contextStoreTargetedRecordsRuleComponentState, - ); - - const contextStoreFilters = useRecoilComponentValueV2( - contextStoreFiltersComponentState, - ); - - const { filterValueDependencies } = useFilterValueDependencies(); - - const graphqlFilter = computeContextStoreFilters( - contextStoreTargetedRecordsRule, - contextStoreFilters, - objectMetadataItem, - filterValueDependencies, - ); - - const deletedAtFieldMetadata = objectMetadataItem.fields.find( - (field) => field.name === 'deletedAt', - ); - - const isDeletedFilterActive = contextStoreFilters.some( - (filter) => - filter.fieldMetadataId === deletedAtFieldMetadata?.id && - filter.operand === FilterOperand.IsNotEmpty, - ); - - const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({ - objectNameSingular: objectMetadataItem.nameSingular, - filter: graphqlFilter, - limit: DEFAULT_QUERY_PAGE_SIZE, - recordGqlFields: { id: true }, - }); - - const { closeRightDrawer } = useRightDrawer(); - - const handleDeleteClick = useCallback(async () => { - const recordsToDelete = await fetchAllRecordIds(); - const recordIdsToDelete = recordsToDelete.map((record) => record.id); - - resetTableRowSelection(); - - await deleteManyRecords(recordIdsToDelete); - }, [deleteManyRecords, fetchAllRecordIds, resetTableRowSelection]); - - const isRemoteObject = objectMetadataItem.isRemote; - - const canDelete = - !isRemoteObject && - !isDeletedFilterActive && - isDefined(contextStoreNumberOfSelectedRecords) && - contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT && - contextStoreNumberOfSelectedRecords > 0; - - const { isInRightDrawer, onActionStartedCallback, onActionExecutedCallback } = - useContext(ActionMenuContext); - - const registerDeleteMultipleRecordsAction = ({ - position, - }: { - position: number; - }) => { - if (canDelete) { - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - key: MultipleRecordsActionKeys.DELETE, - label: 'Delete records', - shortLabel: 'Delete', - position, - Icon: IconTrash, - accent: 'danger', - isPinned: true, - onClick: () => { - setIsDeleteRecordsModalOpen(true); - }, - ConfirmationModal: ( - { - onActionStartedCallback?.({ - key: 'delete-multiple-records', - }); - await handleDeleteClick(); - onActionExecutedCallback?.({ - key: 'delete-multiple-records', - }); - if (isInRightDrawer) { - closeRightDrawer(); - } - }} - deleteButtonText={'Delete Records'} - /> - ), - }); - } - }; - - const unregisterDeleteMultipleRecordsAction = () => { - removeActionMenuEntry('delete-multiple-records'); - }; - - return { - registerDeleteMultipleRecordsAction, - unregisterDeleteMultipleRecordsAction, +import { useCallback, useState } from 'react'; +import { isDefined } from 'twenty-ui'; + +export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = + useState(false); + + const { resetTableRowSelection } = useRecordTable({ + recordTableId: objectMetadataItem.namePlural, + }); + + const { deleteManyRecords } = useDeleteManyRecords({ + objectNameSingular: objectMetadataItem.nameSingular, + }); + + const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2( + contextStoreNumberOfSelectedRecordsComponentState, + ); + + const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( + contextStoreTargetedRecordsRuleComponentState, + ); + + const contextStoreFilters = useRecoilComponentValueV2( + contextStoreFiltersComponentState, + ); + + const { filterValueDependencies } = useFilterValueDependencies(); + + const graphqlFilter = computeContextStoreFilters( + contextStoreTargetedRecordsRule, + contextStoreFilters, + objectMetadataItem, + filterValueDependencies, + ); + + const deletedAtFieldMetadata = objectMetadataItem.fields.find( + (field) => field.name === 'deletedAt', + ); + + const isDeletedFilterActive = contextStoreFilters.some( + (filter) => + filter.fieldMetadataId === deletedAtFieldMetadata?.id && + filter.operand === FilterOperand.IsNotEmpty, + ); + + const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({ + objectNameSingular: objectMetadataItem.nameSingular, + filter: graphqlFilter, + limit: DEFAULT_QUERY_PAGE_SIZE, + recordGqlFields: { id: true }, + }); + + const handleDeleteClick = useCallback(async () => { + const recordsToDelete = await fetchAllRecordIds(); + const recordIdsToDelete = recordsToDelete.map((record) => record.id); + + resetTableRowSelection(); + + await deleteManyRecords(recordIdsToDelete); + }, [deleteManyRecords, fetchAllRecordIds, resetTableRowSelection]); + + const isRemoteObject = objectMetadataItem.isRemote; + + const shouldBeRegistered = + !isRemoteObject && + !isDeletedFilterActive && + isDefined(contextStoreNumberOfSelectedRecords) && + contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT && + contextStoreNumberOfSelectedRecords > 0; + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + setIsDeleteRecordsModalOpen(true); + }; + + const confirmationModal = ( + + ); + + return { + shouldBeRegistered, + onClick, + ConfirmationModal: confirmationModal, + }; }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx index 3b4d10bec890..9d07a41c1c22 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx @@ -1,68 +1,25 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { IconDatabaseExport } from 'twenty-ui'; -import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys'; -import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { - displayedExportProgress, - useExportRecords, -} from '@/object-record/record-index/export/hooks/useExportRecords'; -import { useContext } from 'react'; +import { useExportRecords } from '@/object-record/record-index/export/hooks/useExportRecords'; export const useExportMultipleRecordsAction = ({ objectMetadataItem, }: { objectMetadataItem: ObjectMetadataItem; }) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { progress, download } = useExportRecords({ + const { download } = useExportRecords({ delayMs: 100, objectMetadataItem, recordIndexId: objectMetadataItem.namePlural, filename: `${objectMetadataItem.nameSingular}.csv`, }); - const { onActionStartedCallback, onActionExecutedCallback } = - useContext(ActionMenuContext); - - const registerExportMultipleRecordsAction = ({ - position, - }: { - position: number; - }) => { - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - key: MultipleRecordsActionKeys.EXPORT, - position, - label: displayedExportProgress(progress), - shortLabel: 'Export', - Icon: IconDatabaseExport, - accent: 'default', - onClick: async () => { - await onActionStartedCallback?.({ - key: MultipleRecordsActionKeys.EXPORT, - }); - await download(); - await onActionExecutedCallback?.({ - key: MultipleRecordsActionKeys.EXPORT, - }); - }, - }); - }; - - const unregisterExportMultipleRecordsAction = () => { - removeActionMenuEntry('export-multiple-records'); + const onClick = async () => { + await download(); }; return { - registerExportMultipleRecordsAction, - unregisterExportMultipleRecordsAction, + shouldBeRegistered: true, + onClick, }; }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx deleted file mode 100644 index 2b135aaa0e33..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction'; -import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const useMultipleRecordsActions = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { - registerDeleteMultipleRecordsAction, - unregisterDeleteMultipleRecordsAction, - } = useDeleteMultipleRecordsAction({ - objectMetadataItem, - }); - - const { - registerExportMultipleRecordsAction, - unregisterExportMultipleRecordsAction, - } = useExportMultipleRecordsAction({ - objectMetadataItem, - }); - - const registerMultipleRecordsActions = () => { - registerDeleteMultipleRecordsAction({ position: 1 }); - registerExportMultipleRecordsAction({ position: 2 }); - }; - - const unregisterMultipleRecordsActions = () => { - unregisterDeleteMultipleRecordsAction(); - unregisterExportMultipleRecordsAction(); - }; - - return { - registerMultipleRecordsActions, - unregisterMultipleRecordsActions, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx deleted file mode 100644 index 2cba1582912f..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useNoSelectionRecordActions } from '@/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useEffect } from 'react'; - -export const NoSelectionActionMenuEntrySetterEffect = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { - registerNoSelectionRecordActions, - unregisterNoSelectionRecordActions, - } = useNoSelectionRecordActions({ - objectMetadataItem, - }); - - useEffect(() => { - registerNoSelectionRecordActions(); - - return () => { - unregisterNoSelectionRecordActions(); - }; - }, [registerNoSelectionRecordActions, unregisterNoSelectionRecordActions]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx deleted file mode 100644 index 7b00c186536a..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { expect } from '@storybook/test'; -import { renderHook } from '@testing-library/react'; -import { act } from 'react'; -import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; - -import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction'; -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', -)!; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); - -describe('useExportViewNoSelectionRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register export view action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useExportViewNoSelectionRecordAction: - useExportViewNoSelectionRecordAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useExportViewNoSelectionRecordAction.registerExportViewNoSelectionRecordsAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('export-view-no-selection'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('export-view-no-selection') - ?.position, - ).toBe(1); - }); - - it('should unregister export view action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useExportViewNoSelectionRecordAction: - useExportViewNoSelectionRecordAction({ - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useExportViewNoSelectionRecordAction.registerExportViewNoSelectionRecordsAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - - act(() => { - result.current.useExportViewNoSelectionRecordAction.unregisterExportViewNoSelectionRecordsAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); - }); -}); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx deleted file mode 100644 index e3ace2ee667b..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { IconDatabaseExport } from 'twenty-ui'; - -import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { - displayedExportProgress, - useExportRecords, -} from '@/object-record/record-index/export/hooks/useExportRecords'; - -export const useExportViewNoSelectionRecordAction = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { progress, download } = useExportRecords({ - delayMs: 100, - objectMetadataItem, - recordIndexId: objectMetadataItem.namePlural, - filename: `${objectMetadataItem.nameSingular}.csv`, - }); - - const registerExportViewNoSelectionRecordsAction = ({ - position, - }: { - position: number; - }) => { - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.Global, - key: NoSelectionRecordActionKeys.EXPORT_VIEW, - position, - label: displayedExportProgress(progress), - Icon: IconDatabaseExport, - accent: 'default', - onClick: () => download(), - }); - }; - - const unregisterExportViewNoSelectionRecordsAction = () => { - removeActionMenuEntry('export-view-no-selection'); - }; - - return { - registerExportViewNoSelectionRecordsAction, - unregisterExportViewNoSelectionRecordsAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx deleted file mode 100644 index af08e721b404..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const useNoSelectionRecordActions = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const { - registerExportViewNoSelectionRecordsAction, - unregisterExportViewNoSelectionRecordsAction, - } = useExportViewNoSelectionRecordAction({ - objectMetadataItem, - }); - - const registerNoSelectionRecordActions = () => { - registerExportViewNoSelectionRecordsAction({ position: 1 }); - }; - - const unregisterNoSelectionRecordActions = () => { - unregisterExportViewNoSelectionRecordsAction(); - }; - - return { - registerNoSelectionRecordActions, - unregisterNoSelectionRecordActions, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx deleted file mode 100644 index 3280eb62cb06..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { useActionMenuEntriesWithCallbacks } from '@/action-menu/hooks/useActionMenuEntriesWithCallbacks'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useEffect } from 'react'; - -export const SingleRecordActionMenuEntrySetterEffect = ({ - objectMetadataItem, - viewType, -}: { - objectMetadataItem: ObjectMetadataItem; - viewType: ActionViewType; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { actionMenuEntries } = useActionMenuEntriesWithCallbacks( - objectMetadataItem, - viewType, - ); - - useEffect(() => { - for (const action of actionMenuEntries) { - addActionMenuEntry(action); - } - - return () => { - for (const action of actionMenuEntries) { - removeActionMenuEntry(action.key); - } - }; - }, [actionMenuEntries, addActionMenuEntry, removeActionMenuEntry]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx index 9137cb8538d3..4e7f6a162d50 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx @@ -85,7 +85,6 @@ describe('useAddToFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useAddToFavoritesSingleRecordAction({ - recordId: peopleMock[1].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -100,7 +99,6 @@ describe('useAddToFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useAddToFavoritesSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -115,7 +113,6 @@ describe('useAddToFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useAddToFavoritesSingleRecordAction({ - recordId: peopleMock[1].id, objectMetadataItem: personMockObjectMetadataItem, }), { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx index a650d86e4614..9c9f39520266 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx @@ -39,7 +39,6 @@ describe('useDeleteSingleRecordAction', () => { const { result } = renderHook( () => useDeleteSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx index 8cf965dd5c2e..6da41f206a83 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx @@ -85,7 +85,6 @@ describe('useRemoveFromFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useRemoveFromFavoritesSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -100,7 +99,6 @@ describe('useRemoveFromFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useRemoveFromFavoritesSingleRecordAction({ - recordId: peopleMock[1].id, objectMetadataItem: personMockObjectMetadataItem, }), { @@ -115,7 +113,6 @@ describe('useRemoveFromFavoritesSingleRecordAction', () => { const { result } = renderHook( () => useRemoveFromFavoritesSingleRecordAction({ - recordId: peopleMock[0].id, objectMetadataItem: personMockObjectMetadataItem, }), { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts index ec3f6ddf042d..eace0a761de8 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -6,8 +7,10 @@ import { isNull } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useAddToFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useAddToFavoritesSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { sortedFavorites: favorites } = useFavorites(); const { createFavorite } = useCreateFavorite(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx index e499c72d062a..0fbc2ad3679e 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; @@ -12,80 +13,83 @@ import { useCallback, useContext, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useDeleteSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { - const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = - useState(false); +export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}) => { + const recordId = useSelectedRecordIdOrThrow(); - const { resetTableRowSelection } = useRecordTable({ - recordTableId: objectMetadataItem.namePlural, - }); + const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = + useState(false); - const { deleteOneRecord } = useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, - }); + const { resetTableRowSelection } = useRecordTable({ + recordTableId: objectMetadataItem.namePlural, + }); - const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); + const { deleteOneRecord } = useDeleteOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + }); - const { sortedFavorites: favorites } = useFavorites(); - const { deleteFavorite } = useDeleteFavorite(); + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); - const { closeRightDrawer } = useRightDrawer(); + const { sortedFavorites: favorites } = useFavorites(); + const { deleteFavorite } = useDeleteFavorite(); - const handleDeleteClick = useCallback(async () => { - resetTableRowSelection(); + const { closeRightDrawer } = useRightDrawer(); - const foundFavorite = favorites?.find( - (favorite) => favorite.recordId === recordId, - ); + const handleDeleteClick = useCallback(async () => { + resetTableRowSelection(); - if (isDefined(foundFavorite)) { - deleteFavorite(foundFavorite.id); - } + const foundFavorite = favorites?.find( + (favorite) => favorite.recordId === recordId, + ); - await deleteOneRecord(recordId); - }, [ - deleteFavorite, - deleteOneRecord, - favorites, - resetTableRowSelection, - recordId, - ]); + if (isDefined(foundFavorite)) { + deleteFavorite(foundFavorite.id); + } - const isRemoteObject = objectMetadataItem.isRemote; + await deleteOneRecord(recordId); + }, [ + deleteFavorite, + deleteOneRecord, + favorites, + resetTableRowSelection, + recordId, + ]); - const { isInRightDrawer } = useContext(ActionMenuContext); + const isRemoteObject = objectMetadataItem.isRemote; - const shouldBeRegistered = - !isRemoteObject && isNull(selectedRecord?.deletedAt); + const { isInRightDrawer } = useContext(ActionMenuContext); - const onClick = () => { - if (!shouldBeRegistered) { - return; - } + const shouldBeRegistered = + !isRemoteObject && isNull(selectedRecord?.deletedAt); - setIsDeleteRecordsModalOpen(true); - }; + const onClick = () => { + if (!shouldBeRegistered) { + return; + } - return { - shouldBeRegistered, - onClick, - ConfirmationModal: ( - { + handleDeleteClick(); + if (isInRightDrawer) { + closeRightDrawer(); } - onConfirmClick={() => { - handleDeleteClick(); - if (isInRightDrawer) { - closeRightDrawer(); - } - }} - deleteButtonText={'Delete Record'} - /> - ), - }; + }} + deleteButtonText={'Delete Record'} + /> + ), }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx index 85bcf89e861f..107d68aab3b4 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -9,63 +10,66 @@ import { useCallback, useContext, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useDestroySingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { - const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] = - useState(false); +export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}) => { + const recordId = useSelectedRecordIdOrThrow(); - const { resetTableRowSelection } = useRecordTable({ - recordTableId: objectMetadataItem.namePlural, - }); + const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] = + useState(false); - const { destroyOneRecord } = useDestroyOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, - }); + const { resetTableRowSelection } = useRecordTable({ + recordTableId: objectMetadataItem.namePlural, + }); - const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); + const { destroyOneRecord } = useDestroyOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + }); - const { closeRightDrawer } = useRightDrawer(); + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); - const handleDeleteClick = useCallback(async () => { - resetTableRowSelection(); + const { closeRightDrawer } = useRightDrawer(); - await destroyOneRecord(recordId); - }, [resetTableRowSelection, destroyOneRecord, recordId]); + const handleDeleteClick = useCallback(async () => { + resetTableRowSelection(); - const isRemoteObject = objectMetadataItem.isRemote; + await destroyOneRecord(recordId); + }, [resetTableRowSelection, destroyOneRecord, recordId]); - const { isInRightDrawer } = useContext(ActionMenuContext); + const isRemoteObject = objectMetadataItem.isRemote; - const shouldBeRegistered = - !isRemoteObject && isDefined(selectedRecord?.deletedAt); + const { isInRightDrawer } = useContext(ActionMenuContext); - const onClick = () => { - if (!shouldBeRegistered) { - return; - } + const shouldBeRegistered = + !isRemoteObject && isDefined(selectedRecord?.deletedAt); - setIsDestroyRecordsModalOpen(true); - }; + const onClick = () => { + if (!shouldBeRegistered) { + return; + } - return { - shouldBeRegistered, - onClick, - ConfirmationModal: ( - { + await handleDeleteClick(); + if (isInRightDrawer) { + closeRightDrawer(); } - onConfirmClick={async () => { - await handleDeleteClick(); - if (isInRightDrawer) { - closeRightDrawer(); - } - }} - deleteButtonText={'Permanently Destroy Record'} - /> - ), - }; + }} + deleteButtonText={'Permanently Destroy Record'} + /> + ), }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts index bce0cfdf006e..80f3b196066a 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useExportNoteAction.ts @@ -1,49 +1,51 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { BlockNoteEditor } from '@blocknote/core'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useExportNoteAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { - const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); +export const useExportNoteAction: ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}) => { + const recordId = useSelectedRecordIdOrThrow(); - const filename = `${(selectedRecord?.title || 'Untitled Note').replace(/[<>:"/\\|?*]/g, '-')}`; + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); - const isNoteOrTask = - objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note || - objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task; + const filename = `${(selectedRecord?.title || 'Untitled Note').replace(/[<>:"/\\|?*]/g, '-')}`; - const shouldBeRegistered = - isDefined(objectMetadataItem) && - isDefined(selectedRecord) && - isNoteOrTask; + const isNoteOrTask = + objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note || + objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task; - const onClick = async () => { - if (!shouldBeRegistered || !selectedRecord?.body) { - return; - } + const shouldBeRegistered = + isDefined(objectMetadataItem) && isDefined(selectedRecord) && isNoteOrTask; - const editor = await BlockNoteEditor.create({ - initialContent: JSON.parse(selectedRecord.body), - }); + const onClick = async () => { + if (!shouldBeRegistered || !selectedRecord?.body) { + return; + } - const { exportBlockNoteEditorToPdf } = await import( - '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToPdf' - ); + const editor = await BlockNoteEditor.create({ + initialContent: JSON.parse(selectedRecord.body), + }); - await exportBlockNoteEditorToPdf(editor, filename); + const { exportBlockNoteEditorToPdf } = await import( + '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToPdf' + ); - // TODO later: implement DOCX export - // const { exportBlockNoteEditorToDocx } = await import( - // '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToDocx' - // ); - // await exportBlockNoteEditorToDocx(editor, filename); - }; + await exportBlockNoteEditorToPdf(editor, filename); - return { - shouldBeRegistered, - onClick, - }; + // TODO later: implement DOCX export + // const { exportBlockNoteEditorToDocx } = await import( + // '@/action-menu/actions/record-actions/single-record/utils/exportBlockNoteEditorToDocx' + // ); + // await exportBlockNoteEditorToDocx(editor, filename); }; + + return { + shouldBeRegistered, + onClick, + }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts index 4e55d6315e73..35757df3ba98 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination'; import { useContext } from 'react'; -export const useNavigateToNextRecordSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useNavigateToNextRecordSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { isInRightDrawer } = useContext(ActionMenuContext); const { navigateToNextRecord } = useRecordShowPagePagination( diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts index 127cfa97a9ec..36cec0c5fedb 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction.ts @@ -1,11 +1,15 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination'; import { useContext } from 'react'; -export const useNavigateToPreviousRecordSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useNavigateToPreviousRecordSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { isInRightDrawer } = useContext(ActionMenuContext); + const { navigateToPreviousRecord } = useRecordShowPagePagination( objectMetadataItem.nameSingular, recordId, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts index 57802d53957d..00d924365a44 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { isDefined } from 'twenty-ui'; -export const useRemoveFromFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = - ({ recordId, objectMetadataItem }) => { +export const useRemoveFromFavoritesSingleRecordAction: ActionHookWithObjectMetadataItem = + ({ objectMetadataItem }) => { + const recordId = useSelectedRecordIdOrThrow(); + const { sortedFavorites: favorites } = useFavorites(); const { deleteFavorite } = useDeleteFavorite(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx new file mode 100644 index 000000000000..17f5aab14b48 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow.tsx @@ -0,0 +1,18 @@ +import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +export const useSelectedRecordIdOrThrow = () => { + const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( + contextStoreTargetedRecordsRuleComponentState, + ); + + if ( + contextStoreTargetedRecordsRule.mode === 'exclusion' || + (contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 0) + ) { + throw new Error('Selected record ID is required'); + } + + return contextStoreTargetedRecordsRule.selectedRecordIds[0]; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts deleted file mode 100644 index 6c567e0c5155..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1 } from '@/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1'; -import { DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2'; -import { WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig'; -import { WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig'; -import { WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const getActionConfig = ( - objectMetadataItem: ObjectMetadataItem, - isPageHeaderV2Enabled: boolean, -) => { - if (objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow) { - return WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG; - } - if ( - objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkflowVersion - ) { - return WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG; - } - if (objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkflowRun) { - return WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG; - } - return isPageHeaderV2Enabled - ? DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2 - : DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts index 19e5e64b1688..279c1f079f6c 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts @@ -55,10 +55,7 @@ const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ describe('useActivateDraftWorkflowSingleRecordAction', () => { it('should be registered', () => { const { result } = renderHook( - () => - useActivateDraftWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateDraftWorkflowSingleRecordAction(), { wrapper, }, @@ -69,10 +66,7 @@ describe('useActivateDraftWorkflowSingleRecordAction', () => { it('should call activateWorkflowVersion on click', () => { const { result } = renderHook( - () => - useActivateDraftWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateDraftWorkflowSingleRecordAction(), { wrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts index 9cac8dfb5d08..e7d3c536d1aa 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts @@ -56,10 +56,7 @@ const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ describe('useActivateLastPublishedVersionWorkflowSingleRecordAction', () => { it('should be registered', () => { const { result } = renderHook( - () => - useActivateLastPublishedVersionWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateLastPublishedVersionWorkflowSingleRecordAction(), { wrapper, }, @@ -70,10 +67,7 @@ describe('useActivateLastPublishedVersionWorkflowSingleRecordAction', () => { it('should call activateWorkflowVersion on click', () => { const { result } = renderHook( - () => - useActivateLastPublishedVersionWorkflowSingleRecordAction({ - recordId: workflowMock.id, - }), + () => useActivateLastPublishedVersionWorkflowSingleRecordAction(), { wrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts index 3dd091f46c9f..77bc31ff8103 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts @@ -104,10 +104,7 @@ describe('useDeactivateWorkflowSingleRecordAction', () => { () => deactivatedWorkflowMock, ); const { result } = renderHook( - () => - useDeactivateWorkflowSingleRecordAction({ - recordId: deactivatedWorkflowMock.id, - }), + () => useDeactivateWorkflowSingleRecordAction(), { wrapper: deactivatedWorkflowWrapper, }, @@ -121,10 +118,7 @@ describe('useDeactivateWorkflowSingleRecordAction', () => { () => activeWorkflowMock, ); const { result } = renderHook( - () => - useDeactivateWorkflowSingleRecordAction({ - recordId: activeWorkflowMock.id, - }), + () => useDeactivateWorkflowSingleRecordAction(), { wrapper: activeWorkflowWrapper, }, @@ -138,10 +132,7 @@ describe('useDeactivateWorkflowSingleRecordAction', () => { () => activeWorkflowMock, ); const { result } = renderHook( - () => - useDeactivateWorkflowSingleRecordAction({ - recordId: activeWorkflowMock.id, - }), + () => useDeactivateWorkflowSingleRecordAction(), { wrapper: activeWorkflowWrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts index cf37a4a0fc23..430954ddd932 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts @@ -178,10 +178,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { () => noDraftWorkflowMock, ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: noDraftWorkflowMock.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: noDraftWorkflowWrapper, }, @@ -196,10 +193,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: draftWorkflowMockWithOneVersion.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: draftWorkflowWithOneVersionWrapper, }, @@ -213,10 +207,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { () => draftWorkflowMock, ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: draftWorkflowMock.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: draftWorkflowWrapper, }, @@ -230,10 +221,7 @@ describe('useDiscardDraftWorkflowSingleRecordAction', () => { () => draftWorkflowMock, ); const { result } = renderHook( - () => - useDiscardDraftWorkflowSingleRecordAction({ - recordId: draftWorkflowMock.id, - }), + () => useDiscardDraftWorkflowSingleRecordAction(), { wrapper: draftWorkflowWrapper, }, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts index 280aa8019c04..99f72c0f8bd2 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useActivateDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useActivateDraftWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { activateWorkflowVersion } = useActivateWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts index 0c68e1a629d0..061569ac103a 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useActivateLastPublishedVersionWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useActivateLastPublishedVersionWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { activateWorkflowVersion } = useActivateWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts index dd61f50de5eb..2a51f735b8fb 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useDeactivateWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useDeactivateWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts index a1fe61b51eca..d5db87b19cdf 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useDiscardDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useDiscardDraftWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion(); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts index 678a197f8917..527df28b493e 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts @@ -1,12 +1,15 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useNavigate } from 'react-router-dom'; import { isDefined } from 'twenty-ui'; -export const useSeeActiveVersionWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeActiveVersionWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflow = useWorkflowWithCurrentVersion(recordId); const isDraft = workflow?.statuses?.includes('DRAFT') || false; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts index 08e427e612b2..0b245f7f3e9a 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; @@ -6,8 +7,10 @@ import qs from 'qs'; import { useNavigate } from 'react-router-dom'; import { isDefined } from 'twenty-ui'; -export const useSeeRunsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeRunsWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); const navigate = useNavigate(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts index 4524248048e6..6abcc39932f2 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; @@ -6,8 +7,10 @@ import qs from 'qs'; import { useNavigate } from 'react-router-dom'; import { isDefined } from 'twenty-ui'; -export const useSeeVersionsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeVersionsWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); const navigate = useNavigate(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts index dd1daeba24f4..5a74e7fd4f25 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts @@ -1,10 +1,13 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-ui'; -export const useTestWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useTestWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); const { runWorkflowVersion } = useRunWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts index e8c3975544d0..ab5e2795df1b 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeRunsWorkflowVersionSingleRecordAction.ts @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; @@ -8,8 +9,10 @@ import { useNavigate } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useSeeRunsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeRunsWorkflowVersionSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId)); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts index 738b11fb7d9d..e3d21342d293 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts @@ -1,21 +1,23 @@ +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction'; -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useSeeVersionsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useSeeVersionsWorkflowVersionSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId)); if (!isDefined(workflowVersion)) { throw new Error('Workflow version not found'); } + // TODO: Add recordIds to the hook const { shouldBeRegistered, onClick } = - useSeeVersionsWorkflowSingleRecordAction({ - recordId: workflowVersion.workflow.id, - }); + useSeeVersionsWorkflowSingleRecordAction(); return { shouldBeRegistered, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx index 24e0983069db..437d16e129c0 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx @@ -1,4 +1,5 @@ -import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/SingleRecordActionHook'; +import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; +import { ActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL'; import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal'; @@ -10,8 +11,10 @@ import { useNavigate } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const useUseAsDraftWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = - ({ recordId }) => { +export const useUseAsDraftWorkflowVersionSingleRecordAction: ActionHookWithoutObjectMetadataItem = + () => { + const recordId = useSelectedRecordIdOrThrow(); + const workflowVersion = useWorkflowVersion(recordId); const workflow = useWorkflowWithCurrentVersion( diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts b/packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts new file mode 100644 index 000000000000..2add89e614a7 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/types/ActionHook.ts @@ -0,0 +1,14 @@ +import { ActionHookResult } from '@/action-menu/actions/types/ActionHookResult'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export type ActionHook = + | ActionHookWithoutObjectMetadataItem + | ActionHookWithObjectMetadataItem; + +export type ActionHookWithoutObjectMetadataItem = () => ActionHookResult; + +export type ActionHookWithObjectMetadataItem = ({ + objectMetadataItem, +}: { + objectMetadataItem: ObjectMetadataItem; +}) => ActionHookResult; diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts b/packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts deleted file mode 100644 index 4bf981332905..000000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/types/SingleRecordActionHook.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ActionHookResult } from '@/action-menu/actions/types/ActionHookResult'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export type SingleRecordActionHook = - | SingleRecordActionHookWithoutObjectMetadataItem - | SingleRecordActionHookWithObjectMetadataItem; - -export type SingleRecordActionHookWithoutObjectMetadataItem = ({ - recordId, -}: { - recordId: string; -}) => ActionHookResult; - -export type SingleRecordActionHookWithObjectMetadataItem = ({ - recordId, - objectMetadataItem, -}: { - recordId: string; - objectMetadataItem: ObjectMetadataItem; -}) => ActionHookResult; diff --git a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts new file mode 100644 index 000000000000..9d2cd4b216cf --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts @@ -0,0 +1,25 @@ +import { DEFAULT_ACTIONS_CONFIG_V1 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV1'; +import { DEFAULT_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV2'; +import { WORKFLOW_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowActionsConfig'; +import { WORKFLOW_RUNS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig'; +import { WORKFLOW_VERSIONS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const getActionConfig = ( + objectMetadataItem: ObjectMetadataItem, + isPageHeaderV2Enabled: boolean, +) => { + switch (objectMetadataItem.nameSingular) { + case CoreObjectNameSingular.Workflow: + return WORKFLOW_ACTIONS_CONFIG; + case CoreObjectNameSingular.WorkflowVersion: + return WORKFLOW_VERSIONS_ACTIONS_CONFIG; + case CoreObjectNameSingular.WorkflowRun: + return WORKFLOW_RUNS_ACTIONS_CONFIG; + default: + return isPageHeaderV2Enabled + ? DEFAULT_ACTIONS_CONFIG_V2 + : DEFAULT_ACTIONS_CONFIG_V1; + } +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts new file mode 100644 index 000000000000..435bebe05559 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionViewType.ts @@ -0,0 +1,32 @@ +import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; +import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; + +export const getActionViewType = ( + contextStoreCurrentViewType: ContextStoreViewType | null, + contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule, +) => { + if (contextStoreCurrentViewType === null) { + return null; + } + + if (contextStoreCurrentViewType === ContextStoreViewType.ShowPage) { + return ActionViewType.SHOW_PAGE; + } + + if ( + contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 0 + ) { + return ActionViewType.INDEX_PAGE_NO_SELECTION; + } + + if ( + contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 + ) { + return ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION; + } + + return ActionViewType.INDEX_PAGE_BULK_SELECTION; +}; diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts deleted file mode 100644 index 3bdb784cfab5..000000000000 --- a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig'; -import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; -import { wrapActionInCallbacks } from '@/action-menu/actions/utils/wrapActionInCallbacks'; -import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { useContext } from 'react'; -import { isDefined } from 'twenty-ui'; -import { FeatureFlagKey } from '~/generated/graphql'; - -export const useActionMenuEntriesWithCallbacks = ( - objectMetadataItem: ObjectMetadataItem, - viewType: ActionViewType, -) => { - const isPageHeaderV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IsPageHeaderV2Enabled, - ); - - const actionConfig = getActionConfig( - objectMetadataItem, - isPageHeaderV2Enabled, - ); - - const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( - contextStoreTargetedRecordsRuleComponentState, - ); - - const selectedRecordId = - contextStoreTargetedRecordsRule.mode === 'selection' - ? contextStoreTargetedRecordsRule.selectedRecordIds[0] - : undefined; - - if (!isDefined(selectedRecordId)) { - throw new Error('Selected record ID is required'); - } - - const { onActionStartedCallback, onActionExecutedCallback } = - useContext(ActionMenuContext); - - const actionMenuEntries = Object.values(actionConfig ?? {}) - .filter((action) => action.availableOn?.includes(viewType)) - .map((action) => { - const { shouldBeRegistered, onClick, ConfirmationModal } = - action.actionHook({ - recordId: selectedRecordId, - objectMetadataItem, - }); - - if (!shouldBeRegistered) { - return undefined; - } - - const wrappedAction = wrapActionInCallbacks({ - action: { - ...action, - onClick, - ConfirmationModal, - }, - onActionStartedCallback, - onActionExecutedCallback, - }); - - return wrappedAction; - }) - .filter(isDefined); - - return { actionMenuEntries }; -}; diff --git a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx index 47ac02bcc881..f08377a199ce 100644 --- a/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestContextStoreSetter.tsx @@ -1,6 +1,7 @@ import { ReactNode, useEffect, useState } from 'react'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; +import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { ContextStoreTargetedRecordsRule, contextStoreTargetedRecordsRuleComponentState, @@ -13,10 +14,12 @@ export const JestContextStoreSetter = ({ mode: 'selection', selectedRecordIds: [], }, + contextStoreNumberOfSelectedRecords = 0, contextStoreCurrentObjectMetadataNameSingular = '', children, }: { contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule; + contextStoreNumberOfSelectedRecords?: number; contextStoreCurrentObjectMetadataNameSingular?: string; children: ReactNode; }) => { @@ -28,6 +31,10 @@ export const JestContextStoreSetter = ({ contextStoreCurrentObjectMetadataIdComponentState, ); + const setContextStoreNumberOfSelectedRecords = useSetRecoilComponentStateV2( + contextStoreNumberOfSelectedRecordsComponentState, + ); + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: contextStoreCurrentObjectMetadataNameSingular, }); @@ -38,12 +45,15 @@ export const JestContextStoreSetter = ({ useEffect(() => { setContextStoreTargetedRecordsRule(contextStoreTargetedRecordsRule); setContextStoreCurrentObjectMetadataId(contextStoreCurrentObjectMetadataId); + setContextStoreNumberOfSelectedRecords(contextStoreNumberOfSelectedRecords); setIsLoaded(true); }, [ setContextStoreTargetedRecordsRule, setContextStoreCurrentObjectMetadataId, contextStoreTargetedRecordsRule, contextStoreCurrentObjectMetadataId, + setContextStoreNumberOfSelectedRecords, + contextStoreNumberOfSelectedRecords, ]); return isLoaded ? <>{children} : null; diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx index 882675cd431d..1946010092bd 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx @@ -13,6 +13,7 @@ export type GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = { | undefined; onInitializeRecoilSnapshot?: (snapshot: MutableSnapshot) => void; contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule; + contextStoreNumberOfSelectedRecords?: number; contextStoreCurrentObjectMetadataNameSingular?: string; componentInstanceId: string; }; @@ -21,6 +22,7 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({ apolloMocks, onInitializeRecoilSnapshot, contextStoreTargetedRecordsRule, + contextStoreNumberOfSelectedRecords, contextStoreCurrentObjectMetadataNameSingular, componentInstanceId, }: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps) => { @@ -43,6 +45,9 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({ >