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}
+
+
);