From 8ce6f6daea84071dca3b041c35251dc6ca86402d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:30:18 +0100 Subject: [PATCH] Refactored all single record actions (#9045) ## Context Refactored all single record actions so they can be defined by a config file. This refactoring is made with the idea that later the actions will be stored in the database, so we needed a way to serialize them. For each object we can define a config file, if an object has no config file, it falls back to the default config. I introduced action hooks, which return: - `shouldBeRegistered`: `boolean` Whether the action should be registered. - `onClick`: `() => void` The code that will be executed when we click on an action - `ConfirmationModal`?: `React.ReactNode` (optional) The confirmation modal which will be displayed on click This PR also closes #8973 ## Next steps - Refactor multiple records actions - Refactor no selection actions - Add tests --- .../RecordActionMenuEntriesSetter.tsx | 4 +- .../SingleRecordActionMenuEntrySetter.tsx | 26 -- ...ingleRecordActionMenuEntrySetterEffect.tsx | 64 ++++- .../DefaultSingleRecordActionsConfigV1.ts | 47 ++++ .../DefaultSingleRecordActionsConfigV2.ts | 49 ++++ ...StandardSingleRecordActionsOnAllObjects.ts | 1 - ...eAddToFavoritesSingleRecordAction.test.tsx | 135 ++++++++++ .../useDeleteSingleRecordAction.test.tsx | 133 +++------- ...ManageFavoritesSingleRecordAction.test.tsx | 108 -------- ...veFromFavoritesSingleRecordAction.test.tsx | 132 +++++++++ .../useAddToFavoritesSingleRecordAction.ts | 40 +++ .../hooks/useDeleteSingleRecordAction.tsx | 122 ++++----- .../useManageFavoritesSingleRecordAction.ts | 77 ------ ...seRemoveFromFavoritesSingleRecordAction.ts | 35 +++ .../hooks/useSingleRecordActions.ts | 56 ---- .../single-record/utils/getActionConfig.ts | 23 ++ ...ingleRecordActionMenuEntrySetterEffect.tsx | 21 -- .../WorkflowSingleRecordActionsConfig.ts | 110 ++++++++ ...ateDraftWorkflowSingleRecordAction.test.ts | 90 +++++++ ...dVersionWorkflowSingleRecordAction.test.ts | 91 +++++++ ...owDraftWorkflowSingleRecordAction.test.tsx | 123 --------- ...VersionWorkflowSingleRecordAction.test.tsx | 124 --------- ...activateWorkflowSingleRecordAction.test.ts | 158 +++++++++++ ...orkflowWorkflowSingleRecordAction.test.tsx | 113 -------- ...ardDraftWorkflowSingleRecordAction.test.ts | 250 ++++++++++++++++++ ...rdDraftWorkflowSingleRecordAction.test.tsx | 128 --------- ...ActivateDraftWorkflowSingleRecordAction.ts | 32 +++ ...lishedVersionWorkflowSingleRecordAction.ts | 35 +++ ...WorkflowDraftWorkflowSingleRecordAction.ts | 64 ----- ...lishedVersionWorkflowSingleRecordAction.ts | 61 ----- ...useDeactivateWorkflowSingleRecordAction.ts | 28 ++ ...ivateWorkflowWorkflowSingleRecordAction.ts | 55 ---- ...eDiscardDraftWorkflowSingleRecordAction.ts | 74 ++---- ...ActiveVersionWorkflowSingleRecordAction.ts | 34 +++ .../useSeeRunsWorkflowSingleRecordAction.ts | 41 +++ ...seSeeVersionsWorkflowSingleRecordAction.ts | 41 +++ ...ActiveVersionWorkflowSingleRecordAction.ts | 60 ----- ...eWorkflowRunsWorkflowSingleRecordAction.ts | 66 ----- ...rsionsHistoryWorkflowSingleRecordAction.ts | 66 ----- .../useTestWorkflowSingleRecordAction.ts | 34 +++ ...eTestWorkflowWorkflowSingleRecordAction.ts | 60 ----- .../hooks/useWorkflowSingleRecordActions.ts | 127 --------- ...ingleRecordActionMenuEntrySetterEffect.tsx | 21 -- ...rkflowVersionsSingleRecordActionsConfig.ts | 46 ++++ ...utionsWorkflowVersionSingleRecordAction.ts | 48 ++++ ...rsionsWorkflowVersionSingleRecordAction.ts | 24 ++ ...utionsWorkflowVersionSingleRecordAction.ts | 78 ------ ...istoryWorkflowVersionSingleRecordAction.ts | 27 -- ...DraftWorkflowVersionSingleRecordAction.tsx | 132 ++++----- .../useWorkflowVersionsSingleRecordActions.ts | 70 ----- .../actions/types/actionHookResult.ts | 5 + .../actions/types/singleRecordActionHook.ts | 20 ++ .../action-menu/types/ActionMenuEntry.ts | 4 +- .../command-menu/hooks/useCommandMenu.ts | 40 ++- .../__tests__/useExportFetchRecords.test.ts | 22 +- .../pages/object-record/RecordShowPage.tsx | 46 ++-- ...taAndApolloMocksAndContextStoreWrapper.tsx | 40 +-- 57 files changed, 1868 insertions(+), 1893 deletions(-) delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useManageFavoritesSingleRecordAction.test.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowDraftWorkflowSingleRecordAction.test.tsx delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.test.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowWorkflowSingleRecordAction.test.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useWorkflowSingleRecordActions.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect.tsx create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeExecutionsWorkflowVersionSingleRecordAction.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction.ts delete mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/types/actionHookResult.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/types/singleRecordActionHook.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 a8982d3acf..254a0f20e5 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,6 +1,6 @@ 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 { SingleRecordActionMenuEntrySetter } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter'; +import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect'; import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; @@ -59,7 +59,7 @@ const ActionEffects = ({ {contextStoreTargetedRecordsRule.mode === 'selection' && contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && ( <> - {isWorkflowEnabled && ( diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter.tsx deleted file mode 100644 index acd2c99a80..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect'; -import { WorkflowSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect'; -import { WorkflowVersionsSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const SingleRecordActionMenuEntrySetter = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - return ( - <> - - {objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow && ( - - )} - {objectMetadataItem.nameSingular === - CoreObjectNameSingular.WorkflowVersion && ( - - )} - - ); -}; 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 index ef190808be..f2e803d309 100644 --- 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 @@ -1,24 +1,72 @@ +import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig'; +import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; +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 { useEffect } from 'react'; -import { useSingleRecordActions } from '../hooks/useSingleRecordActions'; +import { isDefined } from 'twenty-ui'; export const SingleRecordActionMenuEntrySetterEffect = ({ objectMetadataItem, }: { objectMetadataItem: ObjectMetadataItem; }) => { - const { registerSingleRecordActions, unregisterSingleRecordActions } = - useSingleRecordActions({ - objectMetadataItem, - }); + const isPageHeaderV2Enabled = useIsFeatureEnabled( + 'IS_PAGE_HEADER_V2_ENABLED', + ); + + const actionConfig = getActionConfig( + objectMetadataItem, + isPageHeaderV2Enabled, + ); + + const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); + + 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 actionMenuEntries = Object.values(actionConfig ?? {}) + .map((action) => { + const { shouldBeRegistered, onClick, ConfirmationModal } = + action.actionHook({ + recordId: selectedRecordId, + objectMetadataItem, + }); + + if (shouldBeRegistered) { + return { + ...action, + onClick, + ConfirmationModal, + }; + } + + return undefined; + }) + .filter(isDefined); useEffect(() => { - registerSingleRecordActions(); + for (const action of actionMenuEntries) { + addActionMenuEntry(action); + } return () => { - unregisterSingleRecordActions(); + for (const action of actionMenuEntries) { + removeActionMenuEntry(action.key); + } }; - }, [registerSingleRecordActions, unregisterSingleRecordActions]); + }, [actionMenuEntries, addActionMenuEntry, removeActionMenuEntry]); 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/single-record/constants/DefaultSingleRecordActionsConfigV1.ts new file mode 100644 index 0000000000..269b5f5850 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts @@ -0,0 +1,47 @@ +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 { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook'; +import { + ActionMenuEntry, + ActionMenuEntryScope, + ActionMenuEntryType, +} from '@/action-menu/types/ActionMenuEntry'; +import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui'; + +export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record< + string, + ActionMenuEntry & { + actionHook: SingleRecordActionHook; + } +> = { + addToFavoritesSingleRecord: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: 'add-to-favorites-single-record', + label: 'Add to favorites', + position: 0, + Icon: IconHeart, + actionHook: useAddToFavoritesSingleRecordAction, + }, + removeFromFavoritesSingleRecord: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: 'remove-from-favorites-single-record', + label: 'Remove from favorites', + position: 1, + Icon: IconHeartOff, + actionHook: useRemoveFromFavoritesSingleRecordAction, + }, + deleteSingleRecord: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: 'delete-single-record', + label: 'Delete', + position: 2, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + actionHook: useDeleteSingleRecordAction, + }, +}; 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/single-record/constants/DefaultSingleRecordActionsConfigV2.ts new file mode 100644 index 0000000000..dbfc40569a --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts @@ -0,0 +1,49 @@ +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 { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook'; +import { + ActionMenuEntry, + ActionMenuEntryScope, + ActionMenuEntryType, +} from '@/action-menu/types/ActionMenuEntry'; +import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui'; + +export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record< + string, + ActionMenuEntry & { + actionHook: SingleRecordActionHook; + } +> = { + addToFavoritesSingleRecord: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: 'add-to-favorites-single-record', + label: 'Add to favorites', + position: 0, + isPinned: true, + Icon: IconHeart, + actionHook: useAddToFavoritesSingleRecordAction, + }, + removeFromFavoritesSingleRecord: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: 'remove-from-favorites-single-record', + label: 'Remove from favorites', + isPinned: true, + position: 1, + Icon: IconHeartOff, + actionHook: useRemoveFromFavoritesSingleRecordAction, + }, + deleteSingleRecord: { + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + key: 'delete-single-record', + label: 'Delete', + position: 2, + Icon: IconTrash, + accent: 'danger', + isPinned: true, + actionHook: useDeleteSingleRecordAction, + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects.ts deleted file mode 100644 index 9bc77b9751..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects.ts +++ /dev/null @@ -1 +0,0 @@ -export const NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS = 2; 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 new file mode 100644 index 0000000000..9137cb8538 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx @@ -0,0 +1,135 @@ +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { + GetJestMetadataAndApolloMocksAndActionMenuWrapperProps, + getJestMetadataAndApolloMocksAndActionMenuWrapper, +} from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getPeopleMock } from '~/testing/mock-data/people'; +import { useAddToFavoritesSingleRecordAction } from '../useAddToFavoritesSingleRecordAction'; + +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +)!; + +const peopleMock = getPeopleMock(); + +const favoritesMock = [ + { + id: '1', + recordId: peopleMock[0].id, + position: 0, + avatarType: 'rounded', + avatarUrl: '', + labelIdentifier: ' ', + link: `/object/${personMockObjectMetadataItem.nameSingular}/${peopleMock[0].id}`, + objectNameSingular: personMockObjectMetadataItem.nameSingular, + workspaceMemberId: '1', + favoriteFolderId: undefined, + }, +]; + +jest.mock('@/favorites/hooks/useFavorites', () => ({ + useFavorites: () => ({ + favorites: favoritesMock, + sortedFavorites: favoritesMock, + }), +})); + +const createFavoriteMock = jest.fn(); + +jest.mock('@/favorites/hooks/useCreateFavorite', () => ({ + useCreateFavorite: () => ({ + createFavorite: createFavoriteMock, + }), +})); + +const wrapperConfigWithSelectedRecordAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = + { + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + personMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[0].id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]); + snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]); + }, + }; + +const wrapperConfigWithSelectedRecordNotAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = + { + ...wrapperConfigWithSelectedRecordAsFavorite, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[1].id], + }, + }; + +const wrapperWithSelectedRecordAsFavorite = + getJestMetadataAndApolloMocksAndActionMenuWrapper( + wrapperConfigWithSelectedRecordAsFavorite, + ); + +const wrapperWithSelectedRecordNotAsFavorite = + getJestMetadataAndApolloMocksAndActionMenuWrapper( + wrapperConfigWithSelectedRecordNotAsFavorite, + ); + +describe('useAddToFavoritesSingleRecordAction', () => { + it('should be registered when the record is not a favorite', () => { + const { result } = renderHook( + () => + useAddToFavoritesSingleRecordAction({ + recordId: peopleMock[1].id, + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper: wrapperWithSelectedRecordNotAsFavorite, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(true); + }); + + it('should not be registered when the record is a favorite', () => { + const { result } = renderHook( + () => + useAddToFavoritesSingleRecordAction({ + recordId: peopleMock[0].id, + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper: wrapperWithSelectedRecordAsFavorite, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(false); + }); + + it('should call createFavorite on click', () => { + const { result } = renderHook( + () => + useAddToFavoritesSingleRecordAction({ + recordId: peopleMock[1].id, + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper: wrapperWithSelectedRecordNotAsFavorite, + }, + ); + + act(() => { + result.current.onClick(); + }); + + expect(createFavoriteMock).toHaveBeenCalledWith( + peopleMock[1], + personMockObjectMetadataItem.nameSingular, + ); + }); +}); 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 b05aa3900e..a650d86e46 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 @@ -1,121 +1,64 @@ -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 { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { renderHook } from '@testing-library/react'; import { act } from 'react'; -import { RecoilRoot } from 'recoil'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getPeopleMock } from '~/testing/mock-data/people'; import { useDeleteSingleRecordAction } from '../useDeleteSingleRecordAction'; +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +)!; + +const peopleMock = getPeopleMock(); + +const deleteOneRecordMock = jest.fn(); + jest.mock('@/object-record/hooks/useDeleteOneRecord', () => ({ useDeleteOneRecord: () => ({ - deleteOneRecord: jest.fn(), - }), -})); -jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({ - useDeleteFavorite: () => ({ - deleteFavorite: jest.fn(), - }), -})); -jest.mock('@/favorites/hooks/useFavorites', () => ({ - useFavorites: () => ({ - sortedFavorites: [], - }), -})); -jest.mock('@/object-record/record-table/hooks/useRecordTable', () => ({ - useRecordTable: () => ({ - resetTableRowSelection: jest.fn(), + deleteOneRecord: deleteOneRecordMock, }), })); -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', -)!; +const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + personMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[0].id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]); + }, +}); describe('useDeleteSingleRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - {children} - - - - ); - - it('should register delete action', () => { + it('should call deleteOneRecord on click', () => { const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeleteSingleRecordAction: useDeleteSingleRecordAction({ - recordId: 'record1', - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; + () => + useDeleteSingleRecordAction({ + recordId: peopleMock[0].id, + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper, }, - { wrapper }, ); - act(() => { - result.current.useDeleteSingleRecordAction.registerDeleteSingleRecordAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('delete-single-record'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('delete-single-record')?.position, - ).toBe(1); - }); - - it('should unregister delete action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeleteSingleRecordAction: useDeleteSingleRecordAction({ - recordId: 'record1', - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); + expect(result.current.ConfirmationModal?.props?.isOpen).toBe(false); act(() => { - result.current.useDeleteSingleRecordAction.registerDeleteSingleRecordAction( - { position: 1 }, - ); + result.current.onClick(); }); - expect(result.current.actionMenuEntries.size).toBe(1); + expect(result.current.ConfirmationModal?.props?.isOpen).toBe(true); act(() => { - result.current.useDeleteSingleRecordAction.unregisterDeleteSingleRecordAction(); + result.current.ConfirmationModal?.props?.onConfirmClick(); }); - expect(result.current.actionMenuEntries.size).toBe(0); + expect(deleteOneRecordMock).toHaveBeenCalledWith(peopleMock[0].id); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useManageFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useManageFavoritesSingleRecordAction.test.tsx deleted file mode 100644 index 86e279ecf4..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useManageFavoritesSingleRecordAction.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 { useManageFavoritesSingleRecordAction } from '../useManageFavoritesSingleRecordAction'; - -const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', -)!; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); - -describe('useManageFavoritesSingleRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register manage favorites action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useManageFavoritesSingleRecordAction: - useManageFavoritesSingleRecordAction({ - recordId: 'record1', - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useManageFavoritesSingleRecordAction.registerManageFavoritesSingleRecordAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('manage-favorites-single-record'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('manage-favorites-single-record') - ?.position, - ).toBe(1); - }); - - it('should unregister manage favorites action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useManageFavoritesSingleRecordAction: - useManageFavoritesSingleRecordAction({ - recordId: 'record1', - objectMetadataItem: companyMockObjectMetadataItem, - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useManageFavoritesSingleRecordAction.registerManageFavoritesSingleRecordAction( - { position: 1 }, - ); - }); - - act(() => { - result.current.useManageFavoritesSingleRecordAction.unregisterManageFavoritesSingleRecordAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); - }); -}); 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 new file mode 100644 index 0000000000..8cf965dd5c --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx @@ -0,0 +1,132 @@ +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { + GetJestMetadataAndApolloMocksAndActionMenuWrapperProps, + getJestMetadataAndApolloMocksAndActionMenuWrapper, +} from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { getPeopleMock } from '~/testing/mock-data/people'; +import { useRemoveFromFavoritesSingleRecordAction } from '../useRemoveFromFavoritesSingleRecordAction'; + +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +)!; + +const peopleMock = getPeopleMock(); + +const favoritesMock = [ + { + id: '1', + recordId: peopleMock[0].id, + position: 0, + avatarType: 'rounded', + avatarUrl: '', + labelIdentifier: ' ', + link: `/object/${personMockObjectMetadataItem.nameSingular}/${peopleMock[0].id}`, + objectNameSingular: personMockObjectMetadataItem.nameSingular, + workspaceMemberId: '1', + favoriteFolderId: undefined, + }, +]; + +jest.mock('@/favorites/hooks/useFavorites', () => ({ + useFavorites: () => ({ + favorites: favoritesMock, + sortedFavorites: favoritesMock, + }), +})); + +const deleteFavoriteMock = jest.fn(); + +jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({ + useDeleteFavorite: () => ({ + deleteFavorite: deleteFavoriteMock, + }), +})); + +const wrapperConfigWithSelectedRecordAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = + { + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + personMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[0].id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]); + snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]); + }, + }; + +const wrapperConfigWithSelectedRecordNotAsFavorite: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = + { + ...wrapperConfigWithSelectedRecordAsFavorite, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [peopleMock[1].id], + }, + }; + +const wrapperWithSelectedRecordAsFavorite = + getJestMetadataAndApolloMocksAndActionMenuWrapper( + wrapperConfigWithSelectedRecordAsFavorite, + ); + +const wrapperWithSelectedRecordNotAsFavorite = + getJestMetadataAndApolloMocksAndActionMenuWrapper( + wrapperConfigWithSelectedRecordNotAsFavorite, + ); + +describe('useRemoveFromFavoritesSingleRecordAction', () => { + it('should be registered when the record is a favorite', () => { + const { result } = renderHook( + () => + useRemoveFromFavoritesSingleRecordAction({ + recordId: peopleMock[0].id, + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper: wrapperWithSelectedRecordAsFavorite, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(true); + }); + + it('should not be registered when the record is not a favorite', () => { + const { result } = renderHook( + () => + useRemoveFromFavoritesSingleRecordAction({ + recordId: peopleMock[1].id, + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper: wrapperWithSelectedRecordNotAsFavorite, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(false); + }); + + it('should call deleteFavorite on click', () => { + const { result } = renderHook( + () => + useRemoveFromFavoritesSingleRecordAction({ + recordId: peopleMock[0].id, + objectMetadataItem: personMockObjectMetadataItem, + }), + { + wrapper: wrapperWithSelectedRecordAsFavorite, + }, + ); + + act(() => { + result.current.onClick(); + }); + + expect(deleteFavoriteMock).toHaveBeenCalledWith(favoritesMock[0].id); + }); +}); 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 new file mode 100644 index 0000000000..da9ee0be9e --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction.ts @@ -0,0 +1,40 @@ +import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite'; +import { useFavorites } from '@/favorites/hooks/useFavorites'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useAddToFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = + ({ recordId, objectMetadataItem }) => { + const { sortedFavorites: favorites } = useFavorites(); + + const { createFavorite } = useCreateFavorite(); + + const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); + + const foundFavorite = favorites?.find( + (favorite) => favorite.recordId === recordId, + ); + + const isFavorite = !!foundFavorite; + + const shouldBeRegistered = + isDefined(objectMetadataItem) && + isDefined(selectedRecord) && + !objectMetadataItem.isRemote && + !isFavorite; + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + createFavorite(selectedRecord, objectMetadataItem.nameSingular); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; 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 90888ccc4a..2a8981ee09 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,90 +1,70 @@ +import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { useFavorites } from '@/favorites/hooks/useFavorites'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; 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 { useCallback, useContext, useState } from 'react'; -import { IconTrash, isDefined } from 'twenty-ui'; +import { isDefined } from 'twenty-ui'; -export const useDeleteSingleRecordAction = ({ - recordId, - objectMetadataItem, -}: { - recordId: string; - objectMetadataItem: ObjectMetadataItem; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); +export const useDeleteSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = + ({ recordId, objectMetadataItem }) => { + const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = + useState(false); - const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = - useState(false); + const { resetTableRowSelection } = useRecordTable({ + recordTableId: objectMetadataItem.namePlural, + }); - const { resetTableRowSelection } = useRecordTable({ - recordTableId: objectMetadataItem.namePlural, - }); + const { deleteOneRecord } = useDeleteOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + }); - const { deleteOneRecord } = useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, - }); + const { sortedFavorites: favorites } = useFavorites(); + const { deleteFavorite } = useDeleteFavorite(); - const { sortedFavorites: favorites } = useFavorites(); - const { deleteFavorite } = useDeleteFavorite(); + const { closeRightDrawer } = useRightDrawer(); - const { closeRightDrawer } = useRightDrawer(); + const handleDeleteClick = useCallback(async () => { + resetTableRowSelection(); - const handleDeleteClick = useCallback(async () => { - resetTableRowSelection(); + const foundFavorite = favorites?.find( + (favorite) => favorite.recordId === recordId, + ); - const foundFavorite = favorites?.find( - (favorite) => favorite.recordId === recordId, - ); + if (isDefined(foundFavorite)) { + deleteFavorite(foundFavorite.id); + } - if (isDefined(foundFavorite)) { - deleteFavorite(foundFavorite.id); - } + await deleteOneRecord(recordId); + }, [ + deleteFavorite, + deleteOneRecord, + favorites, + resetTableRowSelection, + recordId, + ]); - await deleteOneRecord(recordId); - }, [ - deleteFavorite, - deleteOneRecord, - favorites, - resetTableRowSelection, - recordId, - ]); + const isRemoteObject = objectMetadataItem.isRemote; - const isRemoteObject = objectMetadataItem.isRemote; + const { isInRightDrawer, onActionExecutedCallback } = + useContext(ActionMenuContext); - const { isInRightDrawer, onActionExecutedCallback } = - useContext(ActionMenuContext); + const shouldBeRegistered = !isRemoteObject; - const registerDeleteSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if (isRemoteObject) { - return; - } + const onClick = () => { + if (!shouldBeRegistered) { + return; + } - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - key: 'delete-single-record', - label: 'Delete', - position, - Icon: IconTrash, - accent: 'danger', - isPinned: true, - onClick: () => { - setIsDeleteRecordsModalOpen(true); - }, + setIsDeleteRecordsModalOpen(true); + }; + + return { + shouldBeRegistered, + onClick, ConfirmationModal: ( ), - }); + }; }; - - const unregisterDeleteSingleRecordAction = () => { - removeActionMenuEntry('delete-single-record'); - }; - - return { - registerDeleteSingleRecordAction, - unregisterDeleteSingleRecordAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.ts deleted file mode 100644 index 12a81985a9..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite'; -import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; -import { useFavorites } from '@/favorites/hooks/useFavorites'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { useRecoilValue } from 'recoil'; -import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui'; - -export const useManageFavoritesSingleRecordAction = ({ - recordId, - objectMetadataItem, -}: { - recordId: string; - objectMetadataItem: ObjectMetadataItem; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { sortedFavorites: favorites } = useFavorites(); - - const { createFavorite } = useCreateFavorite(); - - const { deleteFavorite } = useDeleteFavorite(); - - const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId)); - - const foundFavorite = favorites?.find( - (favorite) => favorite.recordId === recordId, - ); - - const isFavorite = !!foundFavorite; - - const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', - ); - - const registerManageFavoritesSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if (!isDefined(objectMetadataItem) || objectMetadataItem.isRemote) { - return; - } - - addActionMenuEntry({ - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - key: 'manage-favorites-single-record', - isPinned: isPageHeaderV2Enabled, - label: isFavorite ? 'Remove from favorites' : 'Add to favorites', - position, - Icon: isFavorite ? IconHeartOff : IconHeart, - onClick: () => { - if (isFavorite && isDefined(foundFavorite?.id)) { - deleteFavorite(foundFavorite.id); - } else if (isDefined(selectedRecord)) { - createFavorite(selectedRecord, objectMetadataItem.nameSingular); - } - }, - }); - }; - - const unregisterManageFavoritesSingleRecordAction = () => { - removeActionMenuEntry('manage-favorites-single-record'); - }; - - return { - registerManageFavoritesSingleRecordAction, - unregisterManageFavoritesSingleRecordAction, - }; -}; 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 new file mode 100644 index 0000000000..28382e4252 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction.ts @@ -0,0 +1,35 @@ +import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; +import { useFavorites } from '@/favorites/hooks/useFavorites'; +import { isDefined } from 'twenty-ui'; + +export const useRemoveFromFavoritesSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem = + ({ recordId, objectMetadataItem }) => { + const { sortedFavorites: favorites } = useFavorites(); + + const { deleteFavorite } = useDeleteFavorite(); + + const foundFavorite = favorites?.find( + (favorite) => favorite.recordId === recordId, + ); + + const isFavorite = !!foundFavorite; + + const shouldBeRegistered = + isDefined(objectMetadataItem) && + !objectMetadataItem.isRemote && + isFavorite; + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + deleteFavorite(foundFavorite.id); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.ts deleted file mode 100644 index fb723c46d3..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction'; -import { useManageFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction'; -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 { isDefined } from 'twenty-ui'; - -export const useSingleRecordActions = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - 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 { - registerManageFavoritesSingleRecordAction, - unregisterManageFavoritesSingleRecordAction, - } = useManageFavoritesSingleRecordAction({ - recordId: selectedRecordId, - objectMetadataItem, - }); - - const { - registerDeleteSingleRecordAction, - unregisterDeleteSingleRecordAction, - } = useDeleteSingleRecordAction({ - recordId: selectedRecordId, - objectMetadataItem, - }); - - const registerSingleRecordActions = () => { - registerManageFavoritesSingleRecordAction({ position: 1 }); - registerDeleteSingleRecordAction({ position: 2 }); - }; - - const unregisterSingleRecordActions = () => { - unregisterManageFavoritesSingleRecordAction(); - unregisterDeleteSingleRecordAction(); - }; - - return { - registerSingleRecordActions, - unregisterSingleRecordActions, - }; -}; 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 new file mode 100644 index 0000000000..4fcaf415be --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/utils/getActionConfig.ts @@ -0,0 +1,23 @@ +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_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; + } + 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/components/WorkflowSingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect.tsx deleted file mode 100644 index 4725f00574..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS } from '@/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects'; -import { useEffect } from 'react'; -import { useWorkflowSingleRecordActions } from '../hooks/useWorkflowSingleRecordActions'; - -export const WorkflowSingleRecordActionMenuEntrySetterEffect = () => { - const { registerSingleRecordActions, unregisterSingleRecordActions } = - useWorkflowSingleRecordActions(); - - useEffect(() => { - registerSingleRecordActions({ - startPosition: - NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS + 1, - }); - - return () => { - unregisterSingleRecordActions(); - }; - }, [registerSingleRecordActions, unregisterSingleRecordActions]); - - return null; -}; 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/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts new file mode 100644 index 0000000000..fcad03ea93 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts @@ -0,0 +1,110 @@ +import { useActivateDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction'; +import { useActivateLastPublishedVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction'; +import { useDeactivateWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction'; +import { useDiscardDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction'; +import { useSeeActiveVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction'; +import { useSeeRunsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction'; +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 { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook'; +import { + ActionMenuEntry, + ActionMenuEntryScope, + ActionMenuEntryType, +} from '@/action-menu/types/ActionMenuEntry'; +import { + IconHistory, + IconHistoryToggle, + IconPlayerPause, + IconPlayerPlay, + IconPower, + IconTrash, +} from 'twenty-ui'; + +export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record< + string, + ActionMenuEntry & { + actionHook: SingleRecordActionHook; + } +> = { + activateWorkflowDraftSingleRecord: { + key: 'activate-workflow-draft-single-record', + label: 'Activate Draft', + isPinned: true, + position: 1, + Icon: IconPower, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useActivateDraftWorkflowSingleRecordAction, + }, + activateWorkflowLastPublishedVersionSingleRecord: { + key: 'activate-workflow-last-published-version-single-record', + label: 'Activate last published version', + isPinned: true, + position: 2, + Icon: IconPower, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useActivateLastPublishedVersionWorkflowSingleRecordAction, + }, + deactivateWorkflowSingleRecord: { + key: 'deactivate-workflow-single-record', + label: 'Deactivate Workflow', + isPinned: true, + position: 3, + Icon: IconPlayerPause, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useDeactivateWorkflowSingleRecordAction, + }, + discardWorkflowDraftSingleRecord: { + key: 'discard-workflow-draft-single-record', + label: 'Discard Draft', + isPinned: true, + position: 4, + Icon: IconTrash, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useDiscardDraftWorkflowSingleRecordAction, + }, + seeWorkflowActiveVersionSingleRecord: { + key: 'see-workflow-active-version-single-record', + label: 'See active version', + isPinned: false, + position: 5, + Icon: IconHistory, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useSeeActiveVersionWorkflowSingleRecordAction, + }, + seeWorkflowRunsSingleRecord: { + key: 'see-workflow-runs-single-record', + label: 'See runs', + isPinned: false, + position: 6, + Icon: IconHistoryToggle, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useSeeRunsWorkflowSingleRecordAction, + }, + seeWorkflowVersionsHistorySingleRecord: { + key: 'see-workflow-versions-history-single-record', + label: 'See versions history', + isPinned: false, + position: 7, + Icon: IconHistory, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useSeeVersionsWorkflowSingleRecordAction, + }, + testWorkflowSingleRecord: { + key: 'test-workflow-single-record', + label: 'Test Workflow', + isPinned: true, + position: 8, + Icon: IconPlayerPlay, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + actionHook: useTestWorkflowSingleRecordAction, + }, +}; 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 new file mode 100644 index 0000000000..19e5e64b16 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateDraftWorkflowSingleRecordAction.test.ts @@ -0,0 +1,90 @@ +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { useActivateDraftWorkflowSingleRecordAction } from '../useActivateDraftWorkflowSingleRecordAction'; + +const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'workflow', +)!; + +const workflowMock = { + __typename: 'Workflow', + id: 'workflowId', + currentVersion: { + __typename: 'WorkflowVersion', + id: 'currentVersionId', + trigger: 'trigger', + status: 'DRAFT', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, +}; + +jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ + useWorkflowWithCurrentVersion: () => workflowMock, +})); + +const activateWorkflowVersionMock = jest.fn(); + +jest.mock('@/workflow/hooks/useActivateWorkflowVersion', () => ({ + useActivateWorkflowVersion: () => ({ + activateWorkflowVersion: activateWorkflowVersionMock, + }), +})); + +const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + workflowMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [workflowMock.id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(workflowMock.id), workflowMock); + }, +}); + +describe('useActivateDraftWorkflowSingleRecordAction', () => { + it('should be registered', () => { + const { result } = renderHook( + () => + useActivateDraftWorkflowSingleRecordAction({ + recordId: workflowMock.id, + }), + { + wrapper, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(true); + }); + + it('should call activateWorkflowVersion on click', () => { + const { result } = renderHook( + () => + useActivateDraftWorkflowSingleRecordAction({ + recordId: workflowMock.id, + }), + { + wrapper, + }, + ); + + act(() => { + result.current.onClick(); + }); + + expect(activateWorkflowVersionMock).toHaveBeenCalledWith({ + workflowId: workflowMock.id, + workflowVersionId: workflowMock.currentVersion.id, + }); + }); +}); 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 new file mode 100644 index 0000000000..9cac8dfb5d --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateLastPublishedVersionWorkflowSingleRecordAction.test.ts @@ -0,0 +1,91 @@ +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { useActivateLastPublishedVersionWorkflowSingleRecordAction } from '../useActivateLastPublishedVersionWorkflowSingleRecordAction'; + +const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'workflow', +)!; + +const workflowMock = { + __typename: 'Workflow', + id: 'workflowId', + lastPublishedVersionId: 'lastPublishedVersionId', + currentVersion: { + __typename: 'WorkflowVersion', + id: 'lastPublishedVersionId', + trigger: 'trigger', + status: 'DEACTIVATED', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, +}; + +jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ + useWorkflowWithCurrentVersion: () => workflowMock, +})); + +const activateWorkflowVersionMock = jest.fn(); + +jest.mock('@/workflow/hooks/useActivateWorkflowVersion', () => ({ + useActivateWorkflowVersion: () => ({ + activateWorkflowVersion: activateWorkflowVersionMock, + }), +})); + +const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + workflowMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [workflowMock.id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set(recordStoreFamilyState(workflowMock.id), workflowMock); + }, +}); + +describe('useActivateLastPublishedVersionWorkflowSingleRecordAction', () => { + it('should be registered', () => { + const { result } = renderHook( + () => + useActivateLastPublishedVersionWorkflowSingleRecordAction({ + recordId: workflowMock.id, + }), + { + wrapper, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(true); + }); + + it('should call activateWorkflowVersion on click', () => { + const { result } = renderHook( + () => + useActivateLastPublishedVersionWorkflowSingleRecordAction({ + recordId: workflowMock.id, + }), + { + wrapper, + }, + ); + + act(() => { + result.current.onClick(); + }); + + expect(activateWorkflowVersionMock).toHaveBeenCalledWith({ + workflowId: workflowMock.id, + workflowVersionId: workflowMock.currentVersion.id, + }); + }); +}); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowDraftWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowDraftWorkflowSingleRecordAction.test.tsx deleted file mode 100644 index a468db65a5..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowDraftWorkflowSingleRecordAction.test.tsx +++ /dev/null @@ -1,123 +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 { useActivateWorkflowDraftWorkflowSingleRecordAction } from '../useActivateWorkflowDraftWorkflowSingleRecordAction'; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); - -jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ - useWorkflowWithCurrentVersion: () => ({ - id: 'workflowId', - currentVersion: { - id: 'currentVersionId', - trigger: 'trigger', - status: 'DRAFT', - steps: [ - { - id: 'stepId1', - }, - { - id: 'stepId2', - }, - ], - }, - }), -})); - -describe('useActivateWorkflowDraftWorkflowSingleRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register activate workflow draft workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useActivateWorkflowDraftWorkflowSingleRecordAction: - useActivateWorkflowDraftWorkflowSingleRecordAction({ - workflowId: 'workflowId', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.registerActivateWorkflowDraftWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get( - 'activate-workflow-draft-single-record', - ), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get( - 'activate-workflow-draft-single-record', - )?.position, - ).toBe(1); - }); - - it('should unregister activate workflow draft workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useActivateWorkflowDraftWorkflowSingleRecordAction: - useActivateWorkflowDraftWorkflowSingleRecordAction({ - workflowId: 'workflow1', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.registerActivateWorkflowDraftWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - act(() => { - result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.unregisterActivateWorkflowDraftWorkflowSingleRecordAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); - }); -}); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.test.tsx deleted file mode 100644 index e81a4e419d..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.test.tsx +++ /dev/null @@ -1,124 +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 { useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction } from '../useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction'; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); - -jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ - useWorkflowWithCurrentVersion: () => ({ - id: 'workflowId', - currentVersion: { - id: 'currentVersionId', - trigger: 'trigger', - status: 'DEACTIVATED', - steps: [ - { - id: 'stepId1', - }, - { - id: 'stepId2', - }, - ], - }, - lastPublishedVersionId: 'lastPublishedVersionId', - }), -})); - -describe('useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register activate workflow last published version workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction: - useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({ - workflowId: 'workflowId', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get( - 'activate-workflow-last-published-version-single-record', - ), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get( - 'activate-workflow-last-published-version-single-record', - )?.position, - ).toBe(1); - }); - - it('should unregister activate workflow last published version workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction: - useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({ - workflowId: 'workflow1', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - act(() => { - result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); - }); -}); 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 new file mode 100644 index 0000000000..3dd091f46c --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts @@ -0,0 +1,158 @@ +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { useDeactivateWorkflowSingleRecordAction } from '../useDeactivateWorkflowSingleRecordAction'; +const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'workflow', +)!; + +const activeWorkflowMock = { + __typename: 'Workflow', + id: 'workflowId', + lastPublishedVersionId: 'lastPublishedVersionId', + currentVersion: { + __typename: 'WorkflowVersion', + id: 'currentVersionId', + trigger: 'trigger', + status: 'ACTIVE', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, +}; + +const deactivatedWorkflowMock = { + __typename: 'Workflow', + id: 'workflowId', + lastPublishedVersionId: 'lastPublishedVersionId', + currentVersion: { + __typename: 'WorkflowVersion', + id: 'currentVersionId', + trigger: 'trigger', + status: 'DEACTIVATED', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, +}; + +jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ + useWorkflowWithCurrentVersion: jest.fn(), +})); + +const deactivateWorkflowVersionMock = jest.fn(); + +jest.mock('@/workflow/hooks/useDeactivateWorkflowVersion', () => ({ + useDeactivateWorkflowVersion: () => ({ + deactivateWorkflowVersion: deactivateWorkflowVersionMock, + }), +})); + +const activeWorkflowWrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper( + { + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + workflowMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [activeWorkflowMock.id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set( + recordStoreFamilyState(activeWorkflowMock.id), + activeWorkflowMock, + ); + }, + }, +); + +const deactivatedWorkflowWrapper = + getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + workflowMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [deactivatedWorkflowMock.id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set( + recordStoreFamilyState(deactivatedWorkflowMock.id), + deactivatedWorkflowMock, + ); + }, + }); + +describe('useDeactivateWorkflowSingleRecordAction', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should not be registered when the workflow is deactivated', () => { + (useWorkflowWithCurrentVersion as jest.Mock).mockImplementation( + () => deactivatedWorkflowMock, + ); + const { result } = renderHook( + () => + useDeactivateWorkflowSingleRecordAction({ + recordId: deactivatedWorkflowMock.id, + }), + { + wrapper: deactivatedWorkflowWrapper, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(false); + }); + + it('should be registered when the workflow is active', () => { + (useWorkflowWithCurrentVersion as jest.Mock).mockImplementation( + () => activeWorkflowMock, + ); + const { result } = renderHook( + () => + useDeactivateWorkflowSingleRecordAction({ + recordId: activeWorkflowMock.id, + }), + { + wrapper: activeWorkflowWrapper, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(true); + }); + + it('should call deactivateWorkflowVersion on click', () => { + (useWorkflowWithCurrentVersion as jest.Mock).mockImplementation( + () => activeWorkflowMock, + ); + const { result } = renderHook( + () => + useDeactivateWorkflowSingleRecordAction({ + recordId: activeWorkflowMock.id, + }), + { + wrapper: activeWorkflowWrapper, + }, + ); + + act(() => { + result.current.onClick(); + }); + + expect(deactivateWorkflowVersionMock).toHaveBeenCalledWith( + activeWorkflowMock.currentVersion.id, + ); + }); +}); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowWorkflowSingleRecordAction.test.tsx deleted file mode 100644 index 3b56488b46..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowWorkflowSingleRecordAction.test.tsx +++ /dev/null @@ -1,113 +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 { useDeactivateWorkflowWorkflowSingleRecordAction } from '../useDeactivateWorkflowWorkflowSingleRecordAction'; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); - -jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ - useWorkflowWithCurrentVersion: () => ({ - id: 'workflowId', - currentVersion: { - id: 'currentVersionId', - trigger: 'trigger', - status: 'ACTIVE', - }, - lastPublishedVersionId: 'lastPublishedVersionId', - }), -})); - -describe('useDeactivateWorkflowWorkflowSingleRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register activate workflow last published version workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeactivateWorkflowWorkflowSingleRecordAction: - useDeactivateWorkflowWorkflowSingleRecordAction({ - workflowId: 'workflowId', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useDeactivateWorkflowWorkflowSingleRecordAction.registerDeactivateWorkflowWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get('deactivate-workflow-single-record'), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get('deactivate-workflow-single-record') - ?.position, - ).toBe(1); - }); - - it('should unregister deactivate workflow workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDeactivateWorkflowWorkflowSingleRecordAction: - useDeactivateWorkflowWorkflowSingleRecordAction({ - workflowId: 'workflow1', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useDeactivateWorkflowWorkflowSingleRecordAction.registerDeactivateWorkflowWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - act(() => { - result.current.useDeactivateWorkflowWorkflowSingleRecordAction.unregisterDeactivateWorkflowWorkflowSingleRecordAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); - }); -}); 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 new file mode 100644 index 0000000000..cf37a4a0fc --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.ts @@ -0,0 +1,250 @@ +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { useDiscardDraftWorkflowSingleRecordAction } from '../useDiscardDraftWorkflowSingleRecordAction'; + +const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'workflow', +)!; + +const noDraftWorkflowMock = { + __typename: 'Workflow', + id: 'workflowId', + lastPublishedVersionId: 'lastPublishedVersionId', + currentVersion: { + __typename: 'WorkflowVersion', + id: 'currentVersionId', + trigger: 'trigger', + status: 'ACTIVE', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, + versions: [ + { + __typename: 'WorkflowVersion', + id: 'currentVersionId', + trigger: 'trigger', + status: 'ACTIVE', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, + { + __typename: 'WorkflowVersion', + id: 'versionId2', + trigger: 'trigger', + status: 'ACTIVE', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId2', + }, + ], + }, + ], +}; + +const draftWorkflowMock = { + __typename: 'Workflow', + id: 'workflowId', + lastPublishedVersionId: 'lastPublishedVersionId', + currentVersion: { + __typename: 'WorkflowVersion', + id: 'currentVersionId', + trigger: 'trigger', + status: 'DRAFT', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, + versions: [ + { + __typename: 'WorkflowVersion', + id: 'currentVersionId', + trigger: 'trigger', + status: 'DRAFT', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId1', + }, + ], + }, + { + __typename: 'WorkflowVersion', + id: 'versionId2', + trigger: 'trigger', + status: 'ACTIVE', + steps: [ + { + __typename: 'WorkflowStep', + id: 'stepId2', + }, + ], + }, + ], +}; + +const draftWorkflowMockWithOneVersion = { + ...draftWorkflowMock, + versions: [draftWorkflowMock.currentVersion], +}; + +jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ + useWorkflowWithCurrentVersion: jest.fn(), +})); + +const deleteOneWorkflowVersionMock = jest.fn(); + +jest.mock('@/workflow/hooks/useDeleteOneWorkflowVersion', () => ({ + useDeleteOneWorkflowVersion: () => ({ + deleteOneWorkflowVersion: deleteOneWorkflowVersionMock, + }), +})); + +const noDraftWorkflowWrapper = + getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + workflowMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [noDraftWorkflowMock.id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set( + recordStoreFamilyState(noDraftWorkflowMock.id), + noDraftWorkflowMock, + ); + }, + }); + +const draftWorkflowWrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + workflowMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [draftWorkflowMock.id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set( + recordStoreFamilyState(draftWorkflowMock.id), + draftWorkflowMock, + ); + }, +}); + +const draftWorkflowWithOneVersionWrapper = + getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: '1', + contextStoreCurrentObjectMetadataNameSingular: + workflowMockObjectMetadataItem.nameSingular, + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [draftWorkflowMockWithOneVersion.id], + }, + onInitializeRecoilSnapshot: (snapshot) => { + snapshot.set( + recordStoreFamilyState(draftWorkflowMockWithOneVersion.id), + draftWorkflowMockWithOneVersion, + ); + }, + }); + +describe('useDiscardDraftWorkflowSingleRecordAction', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should not be registered when there is no draft', () => { + (useWorkflowWithCurrentVersion as jest.Mock).mockImplementation( + () => noDraftWorkflowMock, + ); + const { result } = renderHook( + () => + useDiscardDraftWorkflowSingleRecordAction({ + recordId: noDraftWorkflowMock.id, + }), + { + wrapper: noDraftWorkflowWrapper, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(false); + }); + + it('should not be registered when there is only one version', () => { + (useWorkflowWithCurrentVersion as jest.Mock).mockImplementation( + () => draftWorkflowMockWithOneVersion, + ); + + const { result } = renderHook( + () => + useDiscardDraftWorkflowSingleRecordAction({ + recordId: draftWorkflowMockWithOneVersion.id, + }), + { + wrapper: draftWorkflowWithOneVersionWrapper, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(false); + }); + + it('should be registered when the workflow is draft', () => { + (useWorkflowWithCurrentVersion as jest.Mock).mockImplementation( + () => draftWorkflowMock, + ); + const { result } = renderHook( + () => + useDiscardDraftWorkflowSingleRecordAction({ + recordId: draftWorkflowMock.id, + }), + { + wrapper: draftWorkflowWrapper, + }, + ); + + expect(result.current.shouldBeRegistered).toBe(true); + }); + + it('should call deactivateWorkflowVersion on click', () => { + (useWorkflowWithCurrentVersion as jest.Mock).mockImplementation( + () => draftWorkflowMock, + ); + const { result } = renderHook( + () => + useDiscardDraftWorkflowSingleRecordAction({ + recordId: draftWorkflowMock.id, + }), + { + wrapper: draftWorkflowWrapper, + }, + ); + + act(() => { + result.current.onClick(); + }); + + expect(deleteOneWorkflowVersionMock).toHaveBeenCalledWith({ + workflowVersionId: draftWorkflowMock.currentVersion.id, + }); + }); +}); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.tsx deleted file mode 100644 index 428d21704f..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.tsx +++ /dev/null @@ -1,128 +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 { useDiscardDraftWorkflowSingleRecordAction } from '../useDiscardDraftWorkflowSingleRecordAction'; - -const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: [], -}); - -jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({ - useWorkflowWithCurrentVersion: () => ({ - id: 'workflowId', - currentVersion: { - id: 'currentVersionId', - trigger: 'trigger', - status: 'DRAFT', - }, - lastPublishedVersionId: 'lastPublishedVersionId', - versions: [ - { - id: 'currentVersionId', - trigger: 'trigger', - status: 'DRAFT', - }, - { - id: 'lastPublishedVersionId', - trigger: 'trigger', - status: 'ACTIVE', - }, - ], - }), -})); - -describe('useDiscardDraftWorkflowSingleRecordAction', () => { - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - - - {children} - - - - - ); - - it('should register discard workflow draft workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDiscardDraftWorkflowSingleRecordAction: - useDiscardDraftWorkflowSingleRecordAction({ - workflowId: 'workflowId', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useDiscardDraftWorkflowSingleRecordAction.registerDiscardDraftWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - expect(result.current.actionMenuEntries.size).toBe(1); - expect( - result.current.actionMenuEntries.get( - 'discard-workflow-draft-single-record', - ), - ).toBeDefined(); - expect( - result.current.actionMenuEntries.get( - 'discard-workflow-draft-single-record', - )?.position, - ).toBe(1); - }); - - it('should unregister deactivate workflow workflow action', () => { - const { result } = renderHook( - () => { - const actionMenuEntries = useRecoilComponentValueV2( - actionMenuEntriesComponentState, - ); - - return { - actionMenuEntries, - useDiscardDraftWorkflowSingleRecordAction: - useDiscardDraftWorkflowSingleRecordAction({ - workflowId: 'workflow1', - }), - }; - }, - { wrapper }, - ); - - act(() => { - result.current.useDiscardDraftWorkflowSingleRecordAction.registerDiscardDraftWorkflowSingleRecordAction( - { position: 1 }, - ); - }); - - act(() => { - result.current.useDiscardDraftWorkflowSingleRecordAction.unregisterDiscardDraftWorkflowSingleRecordAction(); - }); - - expect(result.current.actionMenuEntries.size).toBe(0); - }); -}); 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 new file mode 100644 index 0000000000..79bf61a3c0 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateDraftWorkflowSingleRecordAction.ts @@ -0,0 +1,32 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { isDefined } from 'twenty-ui'; + +export const useActivateDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const { activateWorkflowVersion } = useActivateWorkflowVersion(); + + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); + + const shouldBeRegistered = + isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) && + isDefined(workflowWithCurrentVersion.currentVersion?.steps) && + workflowWithCurrentVersion.currentVersion.status === 'DRAFT'; + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + activateWorkflowVersion({ + workflowVersionId: workflowWithCurrentVersion.currentVersion.id, + workflowId: workflowWithCurrentVersion.id, + }); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; 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 new file mode 100644 index 0000000000..c107d3d51f --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateLastPublishedVersionWorkflowSingleRecordAction.ts @@ -0,0 +1,35 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { isDefined } from 'twenty-ui'; + +export const useActivateLastPublishedVersionWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const { activateWorkflowVersion } = useActivateWorkflowVersion(); + + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); + + const shouldBeRegistered = + isDefined(workflowWithCurrentVersion) && + isDefined(workflowWithCurrentVersion.currentVersion.trigger) && + isDefined(workflowWithCurrentVersion.lastPublishedVersionId) && + workflowWithCurrentVersion.currentVersion.status !== 'ACTIVE' && + isDefined(workflowWithCurrentVersion.currentVersion?.steps) && + workflowWithCurrentVersion.currentVersion?.steps.length !== 0; + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + activateWorkflowVersion({ + workflowVersionId: workflowWithCurrentVersion.lastPublishedVersionId, + workflowId: workflowWithCurrentVersion.id, + }); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction.ts deleted file mode 100644 index abaf0ba08f..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { IconPower, isDefined } from 'twenty-ui'; - -export const useActivateWorkflowDraftWorkflowSingleRecordAction = ({ - workflowId, -}: { - workflowId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { activateWorkflowVersion } = useActivateWorkflowVersion(); - - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); - - const registerActivateWorkflowDraftWorkflowSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if ( - !isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) || - !isDefined(workflowWithCurrentVersion.currentVersion?.steps) - ) { - return; - } - - const isDraft = - workflowWithCurrentVersion.currentVersion.status === 'DRAFT'; - - if (!isDraft) { - return; - } - - addActionMenuEntry({ - key: 'activate-workflow-draft-single-record', - label: 'Activate Draft', - position, - Icon: IconPower, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - onClick: () => { - activateWorkflowVersion({ - workflowVersionId: workflowWithCurrentVersion.currentVersion.id, - workflowId: workflowWithCurrentVersion.id, - }); - }, - }); - }; - - const unregisterActivateWorkflowDraftWorkflowSingleRecordAction = () => { - removeActionMenuEntry('activate-workflow-draft-single-record'); - }; - - return { - registerActivateWorkflowDraftWorkflowSingleRecordAction, - unregisterActivateWorkflowDraftWorkflowSingleRecordAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.ts deleted file mode 100644 index ee6d6463cd..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { IconPower, isDefined } from 'twenty-ui'; - -export const useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction = - ({ workflowId }: { workflowId: string }) => { - const { addActionMenuEntry, removeActionMenuEntry } = - useActionMenuEntries(); - - const { activateWorkflowVersion } = useActivateWorkflowVersion(); - - const workflowWithCurrentVersion = - useWorkflowWithCurrentVersion(workflowId); - - const registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction = - ({ position }: { position: number }) => { - if ( - !isDefined(workflowWithCurrentVersion) || - !isDefined(workflowWithCurrentVersion.currentVersion.trigger) || - !isDefined(workflowWithCurrentVersion.lastPublishedVersionId) || - workflowWithCurrentVersion.currentVersion.status === 'ACTIVE' || - !isDefined(workflowWithCurrentVersion.currentVersion?.steps) || - workflowWithCurrentVersion.currentVersion?.steps.length === 0 - ) { - return; - } - - addActionMenuEntry({ - key: 'activate-workflow-last-published-version-single-record', - label: 'Activate last published version', - position, - Icon: IconPower, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - onClick: () => { - activateWorkflowVersion({ - workflowVersionId: - workflowWithCurrentVersion.lastPublishedVersionId, - workflowId: workflowWithCurrentVersion.id, - }); - }, - }); - }; - - const unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction = - () => { - removeActionMenuEntry( - 'activate-workflow-last-published-version-single-record', - ); - }; - - return { - registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction, - unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction, - }; - }; 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 new file mode 100644 index 0000000000..731bd6a4f4 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts @@ -0,0 +1,28 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { isDefined } from 'twenty-ui'; + +export const useDeactivateWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion(); + + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); + + const shouldBeRegistered = + isDefined(workflowWithCurrentVersion) && + workflowWithCurrentVersion.currentVersion.status === 'ACTIVE'; + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + deactivateWorkflowVersion(workflowWithCurrentVersion.currentVersion.id); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction.ts deleted file mode 100644 index ab132227f3..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { IconPlayerPause, isDefined } from 'twenty-ui'; - -export const useDeactivateWorkflowWorkflowSingleRecordAction = ({ - workflowId, -}: { - workflowId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion(); - - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); - - const isWorkflowActive = - isDefined(workflowWithCurrentVersion) && - workflowWithCurrentVersion.currentVersion.status === 'ACTIVE'; - - const registerDeactivateWorkflowWorkflowSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if (!isDefined(workflowWithCurrentVersion) || !isWorkflowActive) { - return; - } - - addActionMenuEntry({ - key: 'deactivate-workflow-single-record', - label: 'Deactivate Workflow', - position, - Icon: IconPlayerPause, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - onClick: () => { - deactivateWorkflowVersion(workflowWithCurrentVersion.currentVersion.id); - }, - }); - }; - - const unregisterDeactivateWorkflowWorkflowSingleRecordAction = () => { - removeActionMenuEntry('deactivate-workflow-single-record'); - }; - - return { - registerDeactivateWorkflowWorkflowSingleRecordAction, - unregisterDeactivateWorkflowWorkflowSingleRecordAction, - }; -}; 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 22a7cb519f..d91532c3ba 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,63 +1,31 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { IconTrash, isDefined } from 'twenty-ui'; +import { isDefined } from 'twenty-ui'; -export const useDiscardDraftWorkflowSingleRecordAction = ({ - workflowId, -}: { - workflowId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); +export const useDiscardDraftWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion(); - const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion(); + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); - - const registerDiscardDraftWorkflowSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if ( - !isDefined(workflowWithCurrentVersion) || - workflowWithCurrentVersion.versions.length < 2 - ) { - return; - } - - const isDraft = + const shouldBeRegistered = + isDefined(workflowWithCurrentVersion) && + workflowWithCurrentVersion.versions.length > 1 && workflowWithCurrentVersion.currentVersion.status === 'DRAFT'; - if (!isDraft) { - return; - } + const onClick = () => { + if (!shouldBeRegistered) { + return; + } - addActionMenuEntry({ - key: 'discard-workflow-draft-single-record', - label: 'Discard Draft', - position, - Icon: IconTrash, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - onClick: () => { - deleteOneWorkflowVersion({ - workflowVersionId: workflowWithCurrentVersion.currentVersion.id, - }); - }, - }); - }; + deleteOneWorkflowVersion({ + workflowVersionId: workflowWithCurrentVersion.currentVersion.id, + }); + }; - const unregisterDiscardDraftWorkflowSingleRecordAction = () => { - removeActionMenuEntry('discard-workflow-draft-single-record'); + return { + shouldBeRegistered, + onClick, + }; }; - - return { - registerDiscardDraftWorkflowSingleRecordAction, - unregisterDiscardDraftWorkflowSingleRecordAction, - }; -}; 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 new file mode 100644 index 0000000000..95264cf29d --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeActiveVersionWorkflowSingleRecordAction.ts @@ -0,0 +1,34 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +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 }) => { + const workflow = useWorkflowWithCurrentVersion(recordId); + + const isDraft = workflow?.statuses?.includes('DRAFT') || false; + + const workflowActiveVersion = useActiveWorkflowVersion(recordId); + + const navigate = useNavigate(); + + const shouldBeRegistered = isDefined(workflowActiveVersion) && isDraft; + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + navigate( + `/object/${CoreObjectNameSingular.WorkflowVersion}/${workflowActiveVersion.id}`, + ); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; 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 new file mode 100644 index 0000000000..4c7da08932 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeRunsWorkflowSingleRecordAction.ts @@ -0,0 +1,41 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import qs from 'qs'; +import { useNavigate } from 'react-router-dom'; +import { isDefined } from 'twenty-ui'; + +export const useSeeRunsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); + + const navigate = useNavigate(); + + const shouldBeRegistered = isDefined(workflowWithCurrentVersion); + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + const filterQueryParams: FilterQueryParams = { + filter: { + workflow: { + [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id], + }, + }, + }; + const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify( + filterQueryParams, + )}`; + + navigate(filterLinkHref); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; 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 new file mode 100644 index 0000000000..4ff30bc435 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction.ts @@ -0,0 +1,41 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import qs from 'qs'; +import { useNavigate } from 'react-router-dom'; +import { isDefined } from 'twenty-ui'; + +export const useSeeVersionsWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); + + const navigate = useNavigate(); + + const shouldBeRegistered = isDefined(workflowWithCurrentVersion); + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + const filterQueryParams: FilterQueryParams = { + filter: { + workflow: { + [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id], + }, + }, + }; + const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowVersion}?${qs.stringify( + filterQueryParams, + )}`; + + navigate(filterLinkHref); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction.ts deleted file mode 100644 index e40794f4c5..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion'; -import { useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; -import { IconHistory, isDefined } from 'twenty-ui'; - -export const useSeeWorkflowActiveVersionWorkflowSingleRecordAction = ({ - workflowId, -}: { - workflowId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const workflow = useRecoilValue(recordStoreFamilyState(workflowId)); - - const isDraft = workflow?.statuses?.includes('DRAFT'); - - const workflowActiveVersion = useActiveWorkflowVersion(workflowId); - - const navigate = useNavigate(); - - const registerSeeWorkflowActiveVersionWorkflowSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if (!isDefined(workflowActiveVersion) || !isDraft) { - return; - } - - addActionMenuEntry({ - key: 'see-workflow-active-version-single-record', - label: 'See active version', - position, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - Icon: IconHistory, - onClick: () => { - navigate( - `/object/${CoreObjectNameSingular.WorkflowVersion}/${workflowActiveVersion.id}`, - ); - }, - }); - }; - - const unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction = () => { - removeActionMenuEntry('see-workflow-active-version-single-record'); - }; - - return { - registerSeeWorkflowActiveVersionWorkflowSingleRecordAction, - unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction.ts deleted file mode 100644 index 2a4cd8de1d..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import qs from 'qs'; -import { useNavigate } from 'react-router-dom'; -import { IconHistoryToggle, isDefined } from 'twenty-ui'; - -export const useSeeWorkflowRunsWorkflowSingleRecordAction = ({ - workflowId, -}: { - workflowId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); - - const navigate = useNavigate(); - - const registerSeeWorkflowRunsWorkflowSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if (!isDefined(workflowWithCurrentVersion)) { - return; - } - - const filterQueryParams: FilterQueryParams = { - filter: { - workflow: { - [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id], - }, - }, - }; - const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify( - filterQueryParams, - )}`; - - addActionMenuEntry({ - key: 'see-workflow-runs-single-record', - label: 'See runs', - position, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - Icon: IconHistoryToggle, - onClick: () => { - navigate(filterLinkHref); - }, - }); - }; - - const unregisterSeeWorkflowRunsWorkflowSingleRecordAction = () => { - removeActionMenuEntry('see-workflow-runs-single-record'); - }; - - return { - registerSeeWorkflowRunsWorkflowSingleRecordAction, - unregisterSeeWorkflowRunsWorkflowSingleRecordAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction.ts deleted file mode 100644 index 2204c1af1e..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import qs from 'qs'; -import { useNavigate } from 'react-router-dom'; -import { IconHistory, isDefined } from 'twenty-ui'; - -export const useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = ({ - workflowId, -}: { - workflowId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); - - const navigate = useNavigate(); - - const registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if (!isDefined(workflowWithCurrentVersion)) { - return; - } - - const filterQueryParams: FilterQueryParams = { - filter: { - workflow: { - [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id], - }, - }, - }; - const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowVersion}?${qs.stringify( - filterQueryParams, - )}`; - - addActionMenuEntry({ - key: 'see-workflow-versions-history-single-record', - label: 'See versions history', - position, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - Icon: IconHistory, - onClick: () => { - navigate(filterLinkHref); - }, - }); - }; - - const unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = () => { - removeActionMenuEntry('see-workflow-versions-history-single-record'); - }; - - return { - registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction, - unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction, - }; -}; 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 new file mode 100644 index 0000000000..4df8e533bb --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction.ts @@ -0,0 +1,34 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { isDefined } from 'twenty-ui'; + +export const useTestWorkflowSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId); + + const { runWorkflowVersion } = useRunWorkflowVersion(); + + const shouldBeRegistered = + isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) && + workflowWithCurrentVersion.currentVersion.trigger.type === 'MANUAL' && + !isDefined( + workflowWithCurrentVersion.currentVersion.trigger.settings.objectType, + ); + + const onClick = () => { + if (!shouldBeRegistered) { + return; + } + + runWorkflowVersion({ + workflowVersionId: workflowWithCurrentVersion.currentVersion.id, + workflowName: workflowWithCurrentVersion.name, + }); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction.ts deleted file mode 100644 index d99f35784c..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { IconPlayerPlay, isDefined } from 'twenty-ui'; - -export const useTestWorkflowWorkflowSingleRecordAction = ({ - workflowId, -}: { - workflowId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); - - const { runWorkflowVersion } = useRunWorkflowVersion(); - - const registerTestWorkflowWorkflowSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if ( - !isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) || - workflowWithCurrentVersion.currentVersion.trigger.type !== 'MANUAL' || - isDefined( - workflowWithCurrentVersion.currentVersion.trigger.settings.objectType, - ) - ) { - return; - } - - addActionMenuEntry({ - key: 'test-workflow-single-record', - label: 'Test workflow', - position, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - Icon: IconPlayerPlay, - onClick: () => { - runWorkflowVersion({ - workflowVersionId: workflowWithCurrentVersion.currentVersion.id, - workflowName: workflowWithCurrentVersion.name, - }); - }, - }); - }; - - const unregisterTestWorkflowWorkflowSingleRecordAction = () => { - removeActionMenuEntry('test-workflow-single-record'); - }; - - return { - registerTestWorkflowWorkflowSingleRecordAction, - unregisterTestWorkflowWorkflowSingleRecordAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useWorkflowSingleRecordActions.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useWorkflowSingleRecordActions.ts deleted file mode 100644 index a35ccc4868..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useWorkflowSingleRecordActions.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { useActivateWorkflowDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction'; -import { useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction'; -import { useDeactivateWorkflowWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction'; -import { useDiscardDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction'; -import { useSeeWorkflowActiveVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction'; -import { useSeeWorkflowRunsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction'; -import { useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction'; -import { useTestWorkflowWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { isDefined } from 'twenty-ui'; - -export const useWorkflowSingleRecordActions = () => { - 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 { - registerTestWorkflowWorkflowSingleRecordAction, - unregisterTestWorkflowWorkflowSingleRecordAction, - } = useTestWorkflowWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const { - registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction, - unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction, - } = useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const { - registerDeactivateWorkflowWorkflowSingleRecordAction, - unregisterDeactivateWorkflowWorkflowSingleRecordAction, - } = useDeactivateWorkflowWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const { - registerSeeWorkflowRunsWorkflowSingleRecordAction, - unregisterSeeWorkflowRunsWorkflowSingleRecordAction, - } = useSeeWorkflowRunsWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const { - registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction, - unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction, - } = useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const { - registerSeeWorkflowActiveVersionWorkflowSingleRecordAction, - unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction, - } = useSeeWorkflowActiveVersionWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const { - registerActivateWorkflowDraftWorkflowSingleRecordAction, - unregisterActivateWorkflowDraftWorkflowSingleRecordAction, - } = useActivateWorkflowDraftWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const { - registerDiscardDraftWorkflowSingleRecordAction, - unregisterDiscardDraftWorkflowSingleRecordAction, - } = useDiscardDraftWorkflowSingleRecordAction({ - workflowId: selectedRecordId, - }); - - const registerSingleRecordActions = ({ - startPosition, - }: { - startPosition: number; - }) => { - registerTestWorkflowWorkflowSingleRecordAction({ position: startPosition }); - registerDiscardDraftWorkflowSingleRecordAction({ - position: startPosition + 1, - }); - registerActivateWorkflowDraftWorkflowSingleRecordAction({ - position: startPosition + 2, - }); - registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({ - position: startPosition + 3, - }); - registerDeactivateWorkflowWorkflowSingleRecordAction({ - position: startPosition + 4, - }); - registerSeeWorkflowRunsWorkflowSingleRecordAction({ - position: startPosition + 5, - }); - registerSeeWorkflowActiveVersionWorkflowSingleRecordAction({ - position: startPosition + 6, - }); - registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({ - position: startPosition + 7, - }); - }; - - const unregisterSingleRecordActions = () => { - unregisterTestWorkflowWorkflowSingleRecordAction(); - unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction(); - unregisterDiscardDraftWorkflowSingleRecordAction(); - unregisterActivateWorkflowDraftWorkflowSingleRecordAction(); - unregisterDeactivateWorkflowWorkflowSingleRecordAction(); - unregisterSeeWorkflowRunsWorkflowSingleRecordAction(); - unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction(); - unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction(); - }; - - return { - registerSingleRecordActions, - unregisterSingleRecordActions, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect.tsx deleted file mode 100644 index edc710b83f..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS } from '@/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects'; -import { useWorkflowVersionsSingleRecordActions } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions'; -import { useEffect } from 'react'; - -export const WorkflowVersionsSingleRecordActionMenuEntrySetterEffect = () => { - const { registerSingleRecordActions, unregisterSingleRecordActions } = - useWorkflowVersionsSingleRecordActions(); - - useEffect(() => { - registerSingleRecordActions({ - startPosition: - NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS + 1, - }); - - return () => { - unregisterSingleRecordActions(); - }; - }, [registerSingleRecordActions, unregisterSingleRecordActions]); - - return null; -}; 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/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts new file mode 100644 index 0000000000..7e9480914e --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts @@ -0,0 +1,46 @@ +import { useSeeExecutionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeExecutionsWorkflowVersionSingleRecordAction'; +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 { SingleRecordActionHook } from '@/action-menu/actions/types/singleRecordActionHook'; +import { + ActionMenuEntry, + ActionMenuEntryScope, + ActionMenuEntryType, +} from '@/action-menu/types/ActionMenuEntry'; +import { IconHistory, IconHistoryToggle, IconPencil } from 'twenty-ui'; + +export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record< + string, + ActionMenuEntry & { + actionHook: SingleRecordActionHook; + } +> = { + useAsDraftWorkflowVersionSingleRecord: { + key: 'use-as-draft-workflow-version-single-record', + label: 'Use as draft', + position: 1, + isPinned: true, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + Icon: IconPencil, + actionHook: useUseAsDraftWorkflowVersionSingleRecordAction, + }, + seeWorkflowExecutionsSingleRecord: { + key: 'see-workflow-executions-single-record', + label: 'See executions', + position: 2, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + Icon: IconHistoryToggle, + actionHook: useSeeExecutionsWorkflowVersionSingleRecordAction, + }, + seeWorkflowVersionsHistorySingleRecord: { + key: 'see-workflow-versions-history-single-record', + label: 'See versions history', + position: 3, + type: ActionMenuEntryType.Standard, + scope: ActionMenuEntryScope.RecordSelection, + Icon: IconHistory, + actionHook: useSeeVersionsWorkflowVersionSingleRecordAction, + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeExecutionsWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeExecutionsWorkflowVersionSingleRecordAction.ts new file mode 100644 index 0000000000..241a1e9fe1 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeExecutionsWorkflowVersionSingleRecordAction.ts @@ -0,0 +1,48 @@ +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import qs from 'qs'; +import { useNavigate } from 'react-router-dom'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useSeeExecutionsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId)); + + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( + workflowVersion?.workflow.id, + ); + + const navigate = useNavigate(); + + const shouldBeRegistered = isDefined(workflowWithCurrentVersion); + + const onClick = () => { + if (!shouldBeRegistered) return; + + const filterQueryParams: FilterQueryParams = { + filter: { + workflow: { + [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id], + }, + workflowVersion: { + [ViewFilterOperand.Is]: [recordId], + }, + }, + }; + const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify( + filterQueryParams, + )}`; + + navigate(filterLinkHref); + }; + + return { + shouldBeRegistered, + onClick, + }; + }; 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 new file mode 100644 index 0000000000..8b90991c80 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction.ts @@ -0,0 +1,24 @@ +import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction'; +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useSeeVersionsWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const workflowVersion = useRecoilValue(recordStoreFamilyState(recordId)); + + if (!isDefined(workflowVersion)) { + throw new Error('Workflow version not found'); + } + + const { shouldBeRegistered, onClick } = + useSeeVersionsWorkflowSingleRecordAction({ + recordId: workflowVersion.workflow.id, + }); + + return { + shouldBeRegistered, + onClick, + }; + }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction.ts deleted file mode 100644 index f0452d4f32..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import qs from 'qs'; -import { useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; -import { IconHistoryToggle, isDefined } from 'twenty-ui'; - -export const useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction = ({ - workflowVersionId, -}: { - workflowVersionId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); - - const workflowVersion = useRecoilValue( - recordStoreFamilyState(workflowVersionId), - ); - - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( - workflowVersion?.workflow.id, - ); - - const navigate = useNavigate(); - - const registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if (!isDefined(workflowWithCurrentVersion)) { - return; - } - - const filterQueryParams: FilterQueryParams = { - filter: { - workflow: { - [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id], - }, - workflowVersion: { - [ViewFilterOperand.Is]: [workflowVersionId], - }, - }, - }; - const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify( - filterQueryParams, - )}`; - - addActionMenuEntry({ - key: 'see-workflow-executions-single-record', - label: 'See executions', - position, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - Icon: IconHistoryToggle, - onClick: () => { - navigate(filterLinkHref); - }, - }); - }; - - const unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction = - () => { - removeActionMenuEntry('see-workflow-executions-single-record'); - }; - - return { - registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction, - unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction.ts deleted file mode 100644 index ea607ff96a..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { useRecoilValue } from 'recoil'; - -export const useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction = ({ - workflowVersionId, -}: { - workflowVersionId: string; -}) => { - const workflowVersion = useRecoilValue( - recordStoreFamilyState(workflowVersionId), - ); - - const { - registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction: - registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction, - unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction: - unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction, - } = useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({ - workflowId: workflowVersion?.workflow.id, - }); - - return { - registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction, - unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction, - }; -}; 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 3fc9906da2..c4c09530be 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,92 +1,66 @@ -import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; -import { - ActionMenuEntryScope, - ActionMenuEntryType, -} from '@/action-menu/types/ActionMenuEntry'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { SingleRecordActionHookWithoutObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook'; import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal'; import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion'; +import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { openOverrideWorkflowDraftConfirmationModalState } from '@/workflow/states/openOverrideWorkflowDraftConfirmationModalState'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { IconPencil, isDefined } from 'twenty-ui'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-ui'; -export const useUseAsDraftWorkflowVersionSingleRecordAction = ({ - workflowVersionId, -}: { - workflowVersionId: string; -}) => { - const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); +export const useUseAsDraftWorkflowVersionSingleRecordAction: SingleRecordActionHookWithoutObjectMetadataItem = + ({ recordId }) => { + const workflowVersion = useWorkflowVersion(recordId); - const workflowVersion = useRecoilValue( - recordStoreFamilyState(workflowVersionId), - ); + const workflow = useWorkflowWithCurrentVersion( + workflowVersion?.workflow?.id ?? '', + ); - const workflow = useWorkflowWithCurrentVersion( - workflowVersion?.workflow?.id ?? '', - ); + const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); - const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); + const setOpenOverrideWorkflowDraftConfirmationModal = useSetRecoilState( + openOverrideWorkflowDraftConfirmationModalState, + ); - const setOpenOverrideWorkflowDraftConfirmationModal = useSetRecoilState( - openOverrideWorkflowDraftConfirmationModalState, - ); + const workflowStatuses = workflow?.statuses; - const registerUseAsDraftWorkflowVersionSingleRecordAction = ({ - position, - }: { - position: number; - }) => { - if ( - !isDefined(workflowVersion) || - !isDefined(workflow) || - !isDefined(workflow.statuses) || - workflowVersion.status === 'DRAFT' - ) { - return; - } + const shouldBeRegistered = + isDefined(workflowVersion) && + isDefined(workflow) && + isDefined(workflowStatuses) && + workflowVersion.status !== 'DRAFT'; - const hasAlreadyDraftVersion = workflow.statuses.includes('DRAFT'); + const onClick = async () => { + if (!shouldBeRegistered) return; - addActionMenuEntry({ - key: 'use-workflow-version-as-draft-single-record', - label: 'Use as draft', - position, - Icon: IconPencil, - type: ActionMenuEntryType.Standard, - scope: ActionMenuEntryScope.RecordSelection, - onClick: async () => { - if (hasAlreadyDraftVersion) { - setOpenOverrideWorkflowDraftConfirmationModal(true); - } else { - await createNewWorkflowVersion({ - workflowId: workflowVersion.workflow.id, - name: `v${workflow.versions.length + 1}`, - status: 'DRAFT', - trigger: workflowVersion.trigger, - steps: workflowVersion.steps, - }); - } - }, - ConfirmationModal: ( - - ), - }); + const hasAlreadyDraftVersion = workflowStatuses.includes('DRAFT'); + + if (hasAlreadyDraftVersion) { + setOpenOverrideWorkflowDraftConfirmationModal(true); + } else { + await createNewWorkflowVersion({ + workflowId: workflowVersion.workflow.id, + name: `v${workflow.versions.length + 1}`, + status: 'DRAFT', + trigger: workflowVersion.trigger, + steps: workflowVersion.steps, + }); + } + }; + + const ConfirmationModal = shouldBeRegistered ? ( + + ) : undefined; + + return { + shouldBeRegistered, + onClick, + ConfirmationModal, + }; }; - - const unregisterUseAsDraftWorkflowVersionSingleRecordAction = () => { - removeActionMenuEntry('use-workflow-version-as-draft-single-record'); - }; - - return { - registerUseAsDraftWorkflowVersionSingleRecordAction, - unregisterUseAsDraftWorkflowVersionSingleRecordAction, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions.ts deleted file mode 100644 index 258c06777f..0000000000 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction'; -import { useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction'; -import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { isDefined } from 'twenty-ui'; - -export const useWorkflowVersionsSingleRecordActions = () => { - 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 { - registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction, - unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction, - } = useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction({ - workflowVersionId: selectedRecordId, - }); - - const { - registerUseAsDraftWorkflowVersionSingleRecordAction, - unregisterUseAsDraftWorkflowVersionSingleRecordAction, - } = useUseAsDraftWorkflowVersionSingleRecordAction({ - workflowVersionId: selectedRecordId, - }); - - const { - registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction, - unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction, - } = useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction({ - workflowVersionId: selectedRecordId, - }); - - const registerSingleRecordActions = ({ - startPosition, - }: { - startPosition: number; - }) => { - registerUseAsDraftWorkflowVersionSingleRecordAction({ - position: startPosition, - }); - registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction({ - position: startPosition + 1, - }); - - registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction({ - position: startPosition + 2, - }); - }; - - const unregisterSingleRecordActions = () => { - unregisterUseAsDraftWorkflowVersionSingleRecordAction(); - unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction(); - unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction(); - }; - - return { - registerSingleRecordActions, - unregisterSingleRecordActions, - }; -}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/actionHookResult.ts b/packages/twenty-front/src/modules/action-menu/actions/types/actionHookResult.ts new file mode 100644 index 0000000000..aa72e88791 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/types/actionHookResult.ts @@ -0,0 +1,5 @@ +export type ActionHookResult = { + shouldBeRegistered: boolean; + onClick: () => void; + ConfirmationModal?: React.ReactElement; +}; 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 new file mode 100644 index 0000000000..9446939883 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/types/singleRecordActionHook.ts @@ -0,0 +1,20 @@ +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/types/ActionMenuEntry.ts b/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts index d5a6cc175f..8d63ac3252 100644 --- a/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts +++ b/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts @@ -1,4 +1,4 @@ -import { MouseEvent, ReactNode } from 'react'; +import { MouseEvent, ReactElement } from 'react'; import { IconComponent, MenuItemAccent } from 'twenty-ui'; export enum ActionMenuEntryType { @@ -21,5 +21,5 @@ export type ActionMenuEntry = { isPinned?: boolean; accent?: MenuItemAccent; onClick?: (event?: MouseEvent) => void; - ConfirmationModal?: ReactNode; + ConfirmationModal?: ReactElement; }; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts index 6967fdbba2..2cdf04b59f 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -121,12 +121,50 @@ export const useCommandMenu = () => { ); const closeCommandMenu = useRecoilCallback( - ({ snapshot }) => + ({ snapshot, set }) => () => { const isCommandMenuOpened = snapshot .getLoadable(isCommandMenuOpenedState) .getValue(); + set( + contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ + instanceId: 'command-menu', + }), + null, + ); + + set( + contextStoreTargetedRecordsRuleComponentState.atomFamily({ + instanceId: 'command-menu', + }), + { + mode: 'selection', + selectedRecordIds: [], + }, + ); + + set( + contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ + instanceId: 'command-menu', + }), + 0, + ); + + set( + contextStoreFiltersComponentState.atomFamily({ + instanceId: 'command-menu', + }), + [], + ); + + set( + contextStoreCurrentViewIdComponentState.atomFamily({ + instanceId: 'command-menu', + }), + null, + ); + if (isCommandMenuOpened) { setIsCommandMenuOpened(false); resetSelectedItem(); diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts index 94042f4e02..6621a234be 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts @@ -14,7 +14,7 @@ import { ViewType } from '@/views/types/ViewType'; import { MockedResponse } from '@apollo/client/testing'; import { expect } from '@storybook/test'; import gql from 'graphql-tag'; -import { getJestMetadataAndApolloMocksAndContextStoreWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const defaultResponseData = { @@ -130,17 +130,15 @@ const mocks: MockedResponse[] = [ }, ]; -const WrapperWithResponse = getJestMetadataAndApolloMocksAndContextStoreWrapper( - { - apolloMocks: mocks, - componentInstanceId: 'recordIndexId', - contextStoreTargetedRecordsRule: { - mode: 'selection', - selectedRecordIds: [], - }, - contextStoreCurrentObjectMetadataNameSingular: 'person', +const WrapperWithResponse = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: mocks, + componentInstanceId: 'recordIndexId', + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [], }, -); + contextStoreCurrentObjectMetadataNameSingular: 'person', +}); const graphqlEmptyResponse = [ { @@ -157,7 +155,7 @@ const graphqlEmptyResponse = [ ]; const WrapperWithEmptyResponse = - getJestMetadataAndApolloMocksAndContextStoreWrapper({ + getJestMetadataAndApolloMocksAndActionMenuWrapper({ apolloMocks: graphqlEmptyResponse, componentInstanceId: 'recordIndexId', contextStoreTargetedRecordsRule: { diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx index 37bf95a6de..9a0748147a 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx @@ -14,6 +14,7 @@ import { PageContainer } from '@/ui/layout/page/components/PageContainer'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; import { RecordShowPageWorkflowHeader } from '@/workflow/components/RecordShowPageWorkflowHeader'; import { RecordShowPageWorkflowVersionHeader } from '@/workflow/components/RecordShowPageWorkflowVersionHeader'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader'; export const RecordShowPage = () => { @@ -38,6 +39,10 @@ export const RecordShowPage = () => { parameters.objectRecordId ?? '', ); + const isPageHeaderV2Enabled = useIsFeatureEnabled( + 'IS_PAGE_HEADER_V2_ENABLED', + ); + return ( { headerIcon={headerIcon} > <> - {objectNameSingular === CoreObjectNameSingular.Workflow ? ( - - ) : objectNameSingular === - CoreObjectNameSingular.WorkflowVersion ? ( - - ) : ( - <> - + )} + {!isPageHeaderV2Enabled && + objectNameSingular === + CoreObjectNameSingular.WorkflowVersion && ( + - + )} + {(isPageHeaderV2Enabled || + (objectNameSingular !== CoreObjectNameSingular.Workflow && + objectNameSingular !== + CoreObjectNameSingular.WorkflowVersion)) && ( + )} diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx index fba19c23ba..882675cd43 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper.tsx @@ -1,3 +1,4 @@ +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { MockedResponse } from '@apollo/client/testing'; @@ -6,13 +7,7 @@ import { MutableSnapshot } from 'recoil'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter'; -export const getJestMetadataAndApolloMocksAndContextStoreWrapper = ({ - apolloMocks, - onInitializeRecoilSnapshot, - contextStoreTargetedRecordsRule, - contextStoreCurrentObjectMetadataNameSingular, - componentInstanceId, -}: { +export type GetJestMetadataAndApolloMocksAndActionMenuWrapperProps = { apolloMocks: | readonly MockedResponse, Record>[] | undefined; @@ -20,11 +15,20 @@ export const getJestMetadataAndApolloMocksAndContextStoreWrapper = ({ contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule; contextStoreCurrentObjectMetadataNameSingular?: string; componentInstanceId: string; -}) => { +}; + +export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({ + apolloMocks, + onInitializeRecoilSnapshot, + contextStoreTargetedRecordsRule, + contextStoreCurrentObjectMetadataNameSingular, + componentInstanceId, +}: GetJestMetadataAndApolloMocksAndActionMenuWrapperProps) => { const Wrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks, onInitializeRecoilSnapshot, }); + return ({ children }: { children: ReactNode }) => ( - - {children} - + + {children} + + );