diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
index d38c62963e..c4670bb991 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
@@ -1720,12 +1720,12 @@ enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED
IS_JSON_FILTER_ENABLED
IS_AI_ENABLED
+ IS_COMMAND_MENU_ITEM_ENABLED
IS_MARKETPLACE_ENABLED
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
IS_PUBLIC_DOMAIN_ENABLED
IS_EMAILING_DOMAIN_ENABLED
IS_JUNCTION_RELATIONS_ENABLED
- IS_COMMAND_MENU_ITEM_ENABLED
IS_DRAFT_EMAIL_ENABLED
IS_USAGE_ANALYTICS_ENABLED
IS_RICH_TEXT_V1_MIGRATED
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
index 3939afc760..2fec5003e0 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
@@ -1422,7 +1422,7 @@ export interface PublicFeatureFlag {
__typename: 'PublicFeatureFlag'
}
-export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_GRAPHQL_QUERY_TIMING_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
+export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_GRAPHQL_QUERY_TIMING_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
export interface ClientConfig {
appVersion?: Scalars['String']
@@ -8949,12 +8949,12 @@ export const enumFeatureFlagKey = {
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
IS_AI_ENABLED: 'IS_AI_ENABLED' as const,
+ IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
IS_MARKETPLACE_ENABLED: 'IS_MARKETPLACE_ENABLED' as const,
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
- IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
IS_DRAFT_EMAIL_ENABLED: 'IS_DRAFT_EMAIL_ENABLED' as const,
IS_USAGE_ANALYTICS_ENABLED: 'IS_USAGE_ANALYTICS_ENABLED' as const,
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
diff --git a/packages/twenty-front/src/modules/command-menu-item/components/PageHeaderCommandMenuButtons.tsx b/packages/twenty-front/src/modules/command-menu-item/components/PageHeaderCommandMenuButtons.tsx
deleted file mode 100644
index d621bf4539..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/components/PageHeaderCommandMenuButtons.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
-import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
-import { styled } from '@linaria/react';
-import { motion } from 'framer-motion';
-import { useContext } from 'react';
-import { ThemeContext } from 'twenty-ui/theme-constants';
-
-const StyledActionContainer = styled(motion.div)`
- align-items: center;
- display: flex;
- justify-content: center;
-`;
-
-export const PageHeaderCommandMenuButtons = () => {
- const { theme } = useContext(ThemeContext);
- const { commandMenuItems } = useContext(CommandMenuContext);
- const pinnedActions = commandMenuItems
- .filter((entry) => entry.isPinned)
- .toReversed();
-
- const actionsWithPositionForAnimation = pinnedActions.map(
- (action, index) => ({
- action,
- position: pinnedActions.length - index - 1,
- }),
- );
-
- return (
- <>
- {actionsWithPositionForAnimation.map(({ action, position }) => (
-
-
-
- ))}
- >
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx
index 10adee45ad..51f6007335 100644
--- a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx
@@ -1,4 +1,3 @@
-import { PageHeaderCommandMenuButtons } from '@/command-menu-item/components/PageHeaderCommandMenuButtons';
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons';
import { RecordIndexCommandMenuDropdown } from '@/command-menu-item/components/RecordIndexCommandMenuDropdown';
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
@@ -9,8 +8,6 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { useIsMobile } from 'twenty-ui/utilities';
export const RecordIndexCommandMenu = () => {
@@ -20,15 +17,11 @@ export const RecordIndexCommandMenu = () => {
);
const isMobile = useIsMobile();
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
const isLayoutCustomizationModeEnabled = useAtomStateValue(
isLayoutCustomizationModeEnabledState,
);
- const showEditModePinnedButtons =
- isCommandMenuItemEnabled && isLayoutCustomizationModeEnabled;
+ const showEditModePinnedButtons = isLayoutCustomizationModeEnabled;
return (
<>
@@ -42,12 +35,7 @@ export const RecordIndexCommandMenu = () => {
displayType="button"
containerType="index-page-header"
>
- {!isMobile &&
- (isCommandMenuItemEnabled ? (
-
- ) : (
-
- ))}
+ {!isMobile && }
)}
{
@@ -26,9 +23,6 @@ export const RecordShowCommandMenu = () => {
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1;
const isMobile = useIsMobile();
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
return (
<>
@@ -39,12 +33,7 @@ export const RecordShowCommandMenu = () => {
displayType="button"
containerType="show-page-header"
>
- {!isMobile &&
- (isCommandMenuItemEnabled ? (
-
- ) : (
-
- ))}
+ {!isMobile && }
>
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx
index ef0306d59f..fc9f8727a7 100644
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx
@@ -1,44 +1,21 @@
import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext';
-import { CommandMenuContextProviderLegacy } from '@/command-menu-item/contexts/CommandMenuContextProviderLegacy';
import { CommandMenuContextProviderServerItems } from '@/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItems';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const CommandMenuContextProvider = ({
children,
isInSidePanel,
displayType,
containerType,
- objectMetadataItemOverride,
}: Omit & {
children: React.ReactNode;
- objectMetadataItemOverride?: EnrichedObjectMetadataItem;
}) => {
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
-
- if (isCommandMenuItemEnabled) {
- return (
-
- {children}
-
- );
- }
-
return (
-
{children}
-
+
);
};
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderDefault.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderDefault.tsx
deleted file mode 100644
index 5d295be5ce..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderDefault.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
- CommandMenuContext,
- type CommandMenuContextType,
-} from '@/command-menu-item/contexts/CommandMenuContext';
-import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
-import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
-import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
-import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-
-export const CommandMenuContextProviderDefault = ({
- objectMetadataItem,
- isInSidePanel,
- displayType,
- containerType,
- children,
-}: {
- objectMetadataItem: EnrichedObjectMetadataItem;
- isInSidePanel: CommandMenuContextType['isInSidePanel'];
- displayType: CommandMenuContextType['displayType'];
- containerType: CommandMenuContextType['containerType'];
- children: React.ReactNode;
-}) => {
- const shouldBeRegisteredParams = useShouldCommandMenuItemBeRegisteredParams({
- objectMetadataItem,
- });
-
- const commandMenuItems = useRegisteredCommandMenuItems(
- shouldBeRegisteredParams,
- );
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const isRecordSelection =
- contextStoreTargetedRecordsRule.mode === 'selection' &&
- contextStoreTargetedRecordsRule.selectedRecordIds.length > 0;
-
- const runWorkflowRecordCommands = useRunWorkflowRecordCommands({
- objectMetadataItem,
- skip: !isRecordSelection,
- });
-
- const runWorkflowRecordAgnosticCommands =
- useRunWorkflowRecordAgnosticCommands();
-
- return (
-
- {children}
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderLegacy.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderLegacy.tsx
deleted file mode 100644
index 9cf9805d99..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderLegacy.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext';
-import { CommandMenuContextProviderDefault } from '@/command-menu-item/contexts/CommandMenuContextProviderDefault';
-import { CommandMenuContextProviderWorkflowObjects } from '@/command-menu-item/contexts/CommandMenuContextProviderWorkflowObjects';
-import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
-import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
-import { CoreObjectNameSingular } from 'twenty-shared/types';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
-import { isDefined } from 'twenty-shared/utils';
-
-export const CommandMenuContextProviderLegacy = ({
- children,
- isInSidePanel,
- displayType,
- containerType,
- objectMetadataItemOverride,
-}: Omit & {
- children: React.ReactNode;
- objectMetadataItemOverride?: EnrichedObjectMetadataItem;
-}) => {
- const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
- contextStoreCurrentObjectMetadataItemIdComponentState,
- );
-
- const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
-
- const objectMetadataItem =
- objectMetadataItemOverride ??
- objectMetadataItems.find(
- (objectMetadataItem) =>
- objectMetadataItem.id === contextStoreCurrentObjectMetadataItemId,
- );
-
- if (!isDefined(objectMetadataItem)) {
- return null;
- }
-
- const isWorkflowObject =
- objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow;
-
- if (isWorkflowObject) {
- return (
-
- {children}
-
- );
- }
-
- return (
-
- {children}
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWorkflowObjects.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWorkflowObjects.tsx
deleted file mode 100644
index a97ca36654..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWorkflowObjects.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import {
- CommandMenuContext,
- type CommandMenuContextType,
-} from '@/command-menu-item/contexts/CommandMenuContext';
-import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
-import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
-import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { type WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
-import { isDefined } from 'twenty-shared/utils';
-
-type CommandMenuContextProviderWorkflowObjectsProps = {
- objectMetadataItem: EnrichedObjectMetadataItem;
- isInSidePanel: CommandMenuContextType['isInSidePanel'];
- displayType: CommandMenuContextType['displayType'];
- containerType: CommandMenuContextType['containerType'];
- children: React.ReactNode;
-};
-
-const CommandMenuContextProviderWorkflowObjectsContent = ({
- objectMetadataItem,
- isInSidePanel,
- displayType,
- containerType,
- children,
- selectedRecordId,
-}: CommandMenuContextProviderWorkflowObjectsProps & {
- selectedRecordId: string;
-}) => {
- const params = useShouldCommandMenuItemBeRegisteredParams({
- objectMetadataItem,
- });
-
- const workflowWithCurrentVersion =
- useWorkflowWithCurrentVersion(selectedRecordId);
-
- const shouldBeRegisteredParams = {
- ...params,
- workflowWithCurrentVersion,
- };
-
- const commandMenuItems = useRegisteredCommandMenuItems(
- shouldBeRegisteredParams,
- );
-
- const runWorkflowRecordAgnosticCommands =
- useRunWorkflowRecordAgnosticCommands();
-
- return (
-
- {children}
-
- );
-};
-
-const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
- objectMetadataItem,
- isInSidePanel,
- displayType,
- containerType,
- children,
-}: CommandMenuContextProviderWorkflowObjectsProps & {
- workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined;
-}) => {
- const params = useShouldCommandMenuItemBeRegisteredParams({
- objectMetadataItem,
- });
-
- const shouldBeRegisteredParams = {
- ...params,
- workflowWithCurrentVersion: undefined,
- };
-
- const commandMenuItems = useRegisteredCommandMenuItems(
- shouldBeRegisteredParams,
- );
-
- const runWorkflowRecordAgnosticCommands =
- useRunWorkflowRecordAgnosticCommands();
-
- return (
-
- {children}
-
- );
-};
-
-export const CommandMenuContextProviderWorkflowObjects = ({
- objectMetadataItem,
- isInSidePanel,
- displayType,
- containerType,
- children,
-}: CommandMenuContextProviderWorkflowObjectsProps) => {
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const recordId =
- contextStoreTargetedRecordsRule.mode === 'selection' &&
- contextStoreTargetedRecordsRule.selectedRecordIds.length === 1
- ? contextStoreTargetedRecordsRule.selectedRecordIds[0]
- : undefined;
-
- const selectedRecord =
- useAtomFamilyStateValue(recordStoreFamilyState, recordId ?? '') ||
- undefined;
-
- if (isDefined(selectedRecord?.id)) {
- return (
-
- {children}
-
- );
- }
-
- return (
-
- {children}
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemOpenSidePanelPage.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemOpenSidePanelPage.tsx
deleted file mode 100644
index 6aa4232788..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemOpenSidePanelPage.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
-import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
-import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
-import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
-import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
-import { type MessageDescriptor } from '@lingui/core';
-import { t } from '@lingui/core/macro';
-import { useContext } from 'react';
-import { type SidePanelPages } from 'twenty-shared/types';
-import { type IconComponent } from 'twenty-ui/display';
-
-export const CommandMenuItemOpenSidePanelPage = ({
- page,
- pageTitle,
- pageIcon,
- onClick,
- shouldResetSearchState = false,
-}: {
- page: SidePanelPages;
- pageTitle: MessageDescriptor;
- pageIcon: IconComponent;
- onClick?: () => void;
- shouldResetSearchState?: boolean;
-}) => {
- const actionConfig = useContext(CommandConfigContext);
-
- const { navigateSidePanel } = useNavigateSidePanel();
-
- const setSidePanelSearch = useSetAtomState(sidePanelSearchState);
-
- if (!actionConfig) {
- return null;
- }
-
- const handleClick = () => {
- onClick?.();
-
- navigateSidePanel({
- page,
- pageTitle: t(pageTitle),
- pageIcon,
- });
-
- if (shouldResetSearchState) {
- setSidePanelSearch('');
- }
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx
index e7c32ef9b3..82f2063c02 100644
--- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx
@@ -1,12 +1,12 @@
-import { CommandMenuItemButton } from '@/command-menu-item/display/components/CommandMenuItemButton';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
+import { CommandMenuItemButton } from '@/command-menu-item/display/components/CommandMenuItemButton';
+import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
+import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
const meta: Meta = {
title: 'Modules/CommandMenuItem/Display/CommandMenuItemButton',
component: CommandMenuItemButton,
@@ -26,11 +26,11 @@ const mockActions = createMockCommandMenuItems({
});
const addToFavoritesCommandMenuItem = mockActions.find(
- (action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
+ (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
);
const goToPeopleCommandMenuItem = mockActions.find(
- (action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
+ (action) => action.key === EngineComponentKey.GO_TO_PEOPLE,
);
export const Default: Story = {
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx
index 33ddca96e8..81d39cb370 100644
--- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx
@@ -1,18 +1,18 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, within } from 'storybook/test';
-
-import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
-import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
-import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { ComponentDecorator } from 'twenty-ui/testing';
+import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
+import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
+import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
+import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
+import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
const mockActions = createMockCommandMenuItems({});
const addToFavoritesCommandMenuItem = mockActions.find(
- (action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
+ (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
);
if (!addToFavoritesCommandMenuItem) {
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx
index 53575ed802..cb0973d104 100644
--- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx
@@ -1,14 +1,15 @@
-import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
-import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
-import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
-import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
+import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
+import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
+import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
+import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
+import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
+import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
type Story = StoryObj;
const deleteMock = fn();
@@ -20,7 +21,7 @@ const mockActions = createMockCommandMenuItems({
});
const addToFavoritesCommandMenuItem = mockActions.find(
- (action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
+ (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
);
if (!addToFavoritesCommandMenuItem) {
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx
index 78d3ea7c9e..23d5711daf 100644
--- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx
@@ -1,13 +1,13 @@
-import { CommandDropdownItem } from '@/command-menu-item/display/components/CommandDropdownItem';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
-import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
+import { CommandDropdownItem } from '@/command-menu-item/display/components/CommandDropdownItem';
+import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
+import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
+import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
const meta: Meta = {
title: 'Modules/CommandMenuItem/Display/CommandDropdownItem',
component: CommandDropdownItem,
@@ -37,11 +37,11 @@ const mockActions = createMockCommandMenuItems({
});
const addToFavoritesCommandMenuItem = mockActions.find(
- (action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
+ (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
);
const goToPeopleCommandMenuItem = mockActions.find(
- (action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
+ (action) => action.key === EngineComponentKey.GO_TO_PEOPLE,
);
export const Default: Story = {
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx
index 66b465bbd4..cd55087b5b 100644
--- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx
@@ -1,13 +1,13 @@
-import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem';
-import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
-import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
+import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem';
+import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
+import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
+import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
type Story = StoryObj;
const deleteMock = fn();
@@ -19,11 +19,11 @@ const mockActions = createMockCommandMenuItems({
});
const addToFavoritesCommandMenuItem = mockActions.find(
- (action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
+ (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES,
);
const goToPeopleCommandMenuItem = mockActions.find(
- (action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
+ (action) => action.key === EngineComponentKey.GO_TO_PEOPLE,
);
const meta: Meta = {
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.test.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.test.tsx
new file mode 100644
index 0000000000..d701386ca2
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.test.tsx
@@ -0,0 +1,231 @@
+import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
+import { useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation } from '@/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation';
+import { renderHook, act } from '@testing-library/react';
+import { createStore, Provider as JotaiProvider } from 'jotai';
+import { type ReactNode } from 'react';
+import {
+ CommandMenuItemAvailabilityType,
+ EngineComponentKey,
+} from '~/generated-metadata/graphql';
+
+const mockFindOneWorkflowVersion = jest.fn();
+const mockEnqueueWarningSnackBar = jest.fn();
+const mockBuildTriggerWorkflowVersionPayloads = jest.fn();
+
+jest.mock('@/object-record/hooks/useLazyFindOneRecord', () => ({
+ useLazyFindOneRecord: () => ({
+ findOneRecord: mockFindOneWorkflowVersion,
+ }),
+}));
+
+jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({
+ useSnackBar: () => ({
+ enqueueWarningSnackBar: mockEnqueueWarningSnackBar,
+ }),
+}));
+
+jest.mock(
+ '@/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads',
+ () => ({
+ buildTriggerWorkflowVersionPayloads: (...args: unknown[]) =>
+ mockBuildTriggerWorkflowVersionPayloads(...args),
+ }),
+);
+
+const getWrapper =
+ (store = createStore()) =>
+ ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+
+const buildBaseContextApi = (
+ overrides: Partial = {},
+): HeadlessEngineCommandContextApi => ({
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ contextStoreInstanceId: 'ctx-1',
+ objectMetadataItem: null,
+ currentViewId: null,
+ recordIndexId: null,
+ targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
+ selectedRecords: [],
+ graphqlFilter: null,
+ ...overrides,
+});
+
+describe('useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return enriched context API with workflow info and payloads', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const workflowVersionRecord = {
+ id: 'wf-version-1',
+ workflowId: 'workflow-1',
+ trigger: { type: 'MANUAL' },
+ __typename: 'WorkflowVersion' as const,
+ };
+
+ mockFindOneWorkflowVersion.mockImplementation(
+ async ({ onCompleted }: { onCompleted: (data: unknown) => void }) => {
+ onCompleted(workflowVersionRecord);
+ },
+ );
+
+ const expectedPayloads = [{ recordId: 'rec-1' }];
+ mockBuildTriggerWorkflowVersionPayloads.mockReturnValue(expectedPayloads);
+
+ const { result } = renderHook(
+ () =>
+ useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
+ { wrapper },
+ );
+
+ const headlessEngineCommandContextApi = buildBaseContextApi();
+
+ let enrichedResult: unknown;
+
+ await act(async () => {
+ enrichedResult =
+ await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
+ {
+ headlessEngineCommandContextApi,
+ workflowVersionId: 'wf-version-1',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ },
+ );
+ });
+
+ expect(enrichedResult).toEqual({
+ ...headlessEngineCommandContextApi,
+ workflowId: 'workflow-1',
+ workflowVersionId: 'wf-version-1',
+ payloads: expectedPayloads,
+ });
+ });
+
+ it('should return undefined when workflow version is not found', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ mockFindOneWorkflowVersion.mockImplementation(async () => {});
+
+ const { result } = renderHook(
+ () =>
+ useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
+ { wrapper },
+ );
+
+ let enrichedResult: unknown;
+
+ await act(async () => {
+ enrichedResult =
+ await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
+ {
+ headlessEngineCommandContextApi: buildBaseContextApi(),
+ workflowVersionId: 'nonexistent',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ },
+ );
+ });
+
+ expect(enrichedResult).toBeUndefined();
+ });
+
+ it('should return undefined for RECORD_SELECTION type when payloads are empty', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const workflowVersionRecord = {
+ id: 'wf-version-1',
+ workflowId: 'workflow-1',
+ trigger: { type: 'MANUAL' },
+ __typename: 'WorkflowVersion' as const,
+ };
+
+ mockFindOneWorkflowVersion.mockImplementation(
+ async ({ onCompleted }: { onCompleted: (data: unknown) => void }) => {
+ onCompleted(workflowVersionRecord);
+ },
+ );
+
+ mockBuildTriggerWorkflowVersionPayloads.mockReturnValue([]);
+
+ const { result } = renderHook(
+ () =>
+ useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
+ { wrapper },
+ );
+
+ let enrichedResult: unknown;
+
+ await act(async () => {
+ enrichedResult =
+ await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
+ {
+ headlessEngineCommandContextApi: buildBaseContextApi(),
+ workflowVersionId: 'wf-version-1',
+ availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
+ },
+ );
+ });
+
+ expect(enrichedResult).toBeUndefined();
+ });
+
+ it('should show warning snackbar when selected records exceed QUERY_MAX_RECORDS', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const workflowVersionRecord = {
+ id: 'wf-version-1',
+ workflowId: 'workflow-1',
+ trigger: { type: 'MANUAL' },
+ __typename: 'WorkflowVersion' as const,
+ };
+
+ mockFindOneWorkflowVersion.mockImplementation(
+ async ({ onCompleted }: { onCompleted: (data: unknown) => void }) => {
+ onCompleted(workflowVersionRecord);
+ },
+ );
+
+ mockBuildTriggerWorkflowVersionPayloads.mockReturnValue([
+ { recordId: 'rec-1' },
+ ]);
+
+ const selectedRecordIds = Array.from({ length: 201 }, (_, index) =>
+ String(index),
+ );
+
+ const headlessEngineCommandContextApi = buildBaseContextApi({
+ targetedRecordsRule: { mode: 'selection', selectedRecordIds },
+ });
+
+ const { result } = renderHook(
+ () =>
+ useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(),
+ { wrapper },
+ );
+
+ await act(async () => {
+ await result.current.enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(
+ {
+ headlessEngineCommandContextApi,
+ workflowVersionId: 'wf-version-1',
+ availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
+ },
+ );
+ });
+
+ expect(mockEnqueueWarningSnackBar).toHaveBeenCalledWith(
+ expect.objectContaining({
+ options: {
+ dedupeKey: 'workflow-manual-trigger-selection-limit',
+ },
+ }),
+ );
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useHeadlessCommandContextApi.test.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useHeadlessCommandContextApi.test.tsx
new file mode 100644
index 0000000000..baf6e77337
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useHeadlessCommandContextApi.test.tsx
@@ -0,0 +1,68 @@
+import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState';
+import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
+import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
+import { renderHook } from '@testing-library/react';
+import { createStore, Provider as JotaiProvider } from 'jotai';
+import { type ReactNode } from 'react';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
+const TEST_ENGINE_COMMAND_ID = 'test-engine-cmd-1';
+
+jest.mock(
+ '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow',
+ () => ({
+ useAvailableComponentInstanceIdOrThrow: () => TEST_ENGINE_COMMAND_ID,
+ }),
+);
+
+const getWrapper =
+ (store = createStore()) =>
+ ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+
+const buildHeadlessContextApi = (
+ overrides: Partial = {},
+): HeadlessEngineCommandContextApi => ({
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ contextStoreInstanceId: 'ctx-1',
+ objectMetadataItem: null,
+ currentViewId: null,
+ recordIndexId: null,
+ targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
+ selectedRecords: [],
+ graphqlFilter: null,
+ ...overrides,
+});
+
+describe('useHeadlessCommandContextApi', () => {
+ it('should return the HeadlessCommandContextApi for the current instance id', () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const contextApi = buildHeadlessContextApi();
+ store.set(
+ headlessCommandContextApisState.atom,
+ new Map([[TEST_ENGINE_COMMAND_ID, contextApi]]),
+ );
+
+ const { result } = renderHook(() => useHeadlessCommandContextApi(), {
+ wrapper,
+ });
+
+ expect(result.current).toEqual(contextApi);
+ });
+
+ it('should throw when no entry exists for the instance id', () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ store.set(headlessCommandContextApisState.atom, new Map());
+
+ expect(() =>
+ renderHook(() => useHeadlessCommandContextApi(), { wrapper }),
+ ).toThrow(
+ 'Headless command context API not found. Make sure the command was mounted via the command mount flow.',
+ );
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useIsHeadlessEngineCommandEffectInitialized.test.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useIsHeadlessEngineCommandEffectInitialized.test.ts
new file mode 100644
index 0000000000..b60cc74360
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useIsHeadlessEngineCommandEffectInitialized.test.ts
@@ -0,0 +1,42 @@
+import { useIsHeadlessEngineCommandEffectInitialized } from '@/command-menu-item/engine-command/hooks/useIsHeadlessEngineCommandEffectInitialized';
+import { renderHook, act } from '@testing-library/react';
+
+describe('useIsHeadlessEngineCommandEffectInitialized', () => {
+ it('should return isInitializedRef as false initially', () => {
+ const { result } = renderHook(() =>
+ useIsHeadlessEngineCommandEffectInitialized(),
+ );
+
+ expect(result.current.isInitializedRef.current).toBe(false);
+ });
+
+ it('should update isInitializedRef to true after calling setIsInitialized(true)', () => {
+ const { result } = renderHook(() =>
+ useIsHeadlessEngineCommandEffectInitialized(),
+ );
+
+ act(() => {
+ result.current.setIsInitialized(true);
+ });
+
+ expect(result.current.isInitializedRef.current).toBe(true);
+ });
+
+ it('should toggle back to false after calling setIsInitialized(false)', () => {
+ const { result } = renderHook(() =>
+ useIsHeadlessEngineCommandEffectInitialized(),
+ );
+
+ act(() => {
+ result.current.setIsInitialized(true);
+ });
+
+ expect(result.current.isInitializedRef.current).toBe(true);
+
+ act(() => {
+ result.current.setIsInitialized(false);
+ });
+
+ expect(result.current.isInitializedRef.current).toBe(false);
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useMountCommand.test.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useMountCommand.test.tsx
new file mode 100644
index 0000000000..89917641c5
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useMountCommand.test.tsx
@@ -0,0 +1,155 @@
+import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState';
+import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
+import { useMountCommand } from '@/command-menu-item/engine-command/hooks/useMountCommand';
+import { renderHook, act } from '@testing-library/react';
+import { createStore, Provider as JotaiProvider } from 'jotai';
+import { type ReactNode } from 'react';
+import {
+ CommandMenuItemAvailabilityType,
+ EngineComponentKey,
+} from '~/generated-metadata/graphql';
+
+const mockEnrichFn = jest.fn();
+
+jest.mock(
+ '@/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation',
+ () => ({
+ useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation:
+ () => ({
+ enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation:
+ mockEnrichFn,
+ }),
+ }),
+);
+
+const baseContextApi: HeadlessEngineCommandContextApi = {
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ contextStoreInstanceId: 'ctx-1',
+ objectMetadataItem: null,
+ currentViewId: null,
+ recordIndexId: null,
+ targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
+ selectedRecords: [],
+ graphqlFilter: null,
+};
+
+jest.mock(
+ '@/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi',
+ () => ({
+ buildHeadlessCommandContextApi: () => baseContextApi,
+ }),
+);
+
+const getWrapper =
+ (store = createStore()) =>
+ ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+
+describe('useMountCommand', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should mount with frontComponentId when provided', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const { result } = renderHook(() => useMountCommand(), { wrapper });
+
+ await act(async () => {
+ await result.current({
+ engineCommandId: 'cmd-1',
+ contextStoreInstanceId: 'ctx-1',
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ frontComponentId: 'front-comp-1',
+ });
+ });
+
+ const map = store.get(headlessCommandContextApisState.atom);
+
+ expect(map.get('cmd-1')).toEqual({
+ ...baseContextApi,
+ frontComponentId: 'front-comp-1',
+ });
+ expect(mockEnrichFn).not.toHaveBeenCalled();
+ });
+
+ it('should mount with workflow enrichment when workflowVersionId and availabilityType are provided', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const enrichedState = {
+ ...baseContextApi,
+ workflowId: 'workflow-1',
+ workflowVersionId: 'wf-version-1',
+ payloads: [{ recordId: 'rec-1' }],
+ };
+ mockEnrichFn.mockResolvedValue(enrichedState);
+
+ const { result } = renderHook(() => useMountCommand(), { wrapper });
+
+ await act(async () => {
+ await result.current({
+ engineCommandId: 'cmd-1',
+ contextStoreInstanceId: 'ctx-1',
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ workflowVersionId: 'wf-version-1',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ });
+ });
+
+ const map = store.get(headlessCommandContextApisState.atom);
+
+ expect(map.get('cmd-1')).toEqual(enrichedState);
+ expect(mockEnrichFn).toHaveBeenCalledWith({
+ headlessEngineCommandContextApi: baseContextApi,
+ workflowVersionId: 'wf-version-1',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ availabilityObjectMetadataId: undefined,
+ });
+ });
+
+ it('should mount with base headless context API when neither frontComponentId nor workflow params are provided', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const { result } = renderHook(() => useMountCommand(), { wrapper });
+
+ await act(async () => {
+ await result.current({
+ engineCommandId: 'cmd-1',
+ contextStoreInstanceId: 'ctx-1',
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ });
+ });
+
+ const map = store.get(headlessCommandContextApisState.atom);
+
+ expect(map.get('cmd-1')).toEqual(baseContextApi);
+ expect(mockEnrichFn).not.toHaveBeenCalled();
+ });
+
+ it('should not set state when workflow enrichment returns undefined', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ mockEnrichFn.mockResolvedValue(undefined);
+
+ const { result } = renderHook(() => useMountCommand(), { wrapper });
+
+ await act(async () => {
+ await result.current({
+ engineCommandId: 'cmd-1',
+ contextStoreInstanceId: 'ctx-1',
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ workflowVersionId: 'wf-version-1',
+ availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
+ });
+ });
+
+ const map = store.get(headlessCommandContextApisState.atom);
+
+ expect(map.has('cmd-1')).toBe(false);
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useUnmountEngineCommand.test.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useUnmountEngineCommand.test.tsx
new file mode 100644
index 0000000000..831317b47b
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useUnmountEngineCommand.test.tsx
@@ -0,0 +1,103 @@
+import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState';
+import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
+import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand';
+import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState';
+import { renderHook, act } from '@testing-library/react';
+import { createStore, Provider as JotaiProvider } from 'jotai';
+import { type ReactNode } from 'react';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
+const getWrapper =
+ (store = createStore()) =>
+ ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+
+const buildHeadlessContextApi = (
+ overrides: Partial = {},
+): HeadlessEngineCommandContextApi => ({
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ contextStoreInstanceId: 'ctx-1',
+ objectMetadataItem: null,
+ currentViewId: null,
+ recordIndexId: null,
+ targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
+ selectedRecords: [],
+ graphqlFilter: null,
+ ...overrides,
+});
+
+describe('useUnmountCommand', () => {
+ it('should remove entry from headlessCommandContextApisState map', () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const contextApi = buildHeadlessContextApi();
+ store.set(
+ headlessCommandContextApisState.atom,
+ new Map([['cmd-1', contextApi]]),
+ );
+
+ const { result } = renderHook(() => useUnmountCommand(), { wrapper });
+
+ act(() => {
+ result.current('cmd-1');
+ });
+
+ const map = store.get(headlessCommandContextApisState.atom);
+
+ expect(map.has('cmd-1')).toBe(false);
+ expect(map.size).toBe(0);
+ });
+
+ it('should reset commandMenuItemProgressFamilyState for the given id to undefined', () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ store.set(commandMenuItemProgressFamilyState.atomFamily('cmd-1'), 50);
+
+ const { result } = renderHook(() => useUnmountCommand(), { wrapper });
+
+ act(() => {
+ result.current('cmd-1');
+ });
+
+ const progress = store.get(
+ commandMenuItemProgressFamilyState.atomFamily('cmd-1'),
+ );
+
+ expect(progress).toBeUndefined();
+ });
+
+ it('should not affect other entries in the map', () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const contextApi1 = buildHeadlessContextApi({
+ contextStoreInstanceId: 'ctx-1',
+ });
+ const contextApi2 = buildHeadlessContextApi({
+ contextStoreInstanceId: 'ctx-2',
+ });
+
+ store.set(
+ headlessCommandContextApisState.atom,
+ new Map([
+ ['cmd-1', contextApi1],
+ ['cmd-2', contextApi2],
+ ]),
+ );
+
+ const { result } = renderHook(() => useUnmountCommand(), { wrapper });
+
+ act(() => {
+ result.current('cmd-1');
+ });
+
+ const map = store.get(headlessCommandContextApisState.atom);
+
+ expect(map.has('cmd-1')).toBe(false);
+ expect(map.has('cmd-2')).toBe(true);
+ expect(map.get('cmd-2')).toEqual(contextApi2);
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx
deleted file mode 100644
index 52fb1a0d0d..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
-import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow';
-import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout';
-import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
-import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
-import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
-import { isDefined } from 'twenty-shared/utils';
-import { PageLayoutType } from '~/generated-metadata/graphql';
-
-export const CancelRecordPageLayoutSingleRecordCommand = () => {
- const { objectMetadataItem } = useHeadlessCommandContextApi();
-
- if (!isDefined(objectMetadataItem)) {
- throw new Error(
- 'Object metadata item is required to cancel record page layout',
- );
- }
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const { pageLayoutId } = useRecordPageLayoutIdFromRecordStoreOrThrow({
- targetObjectNameSingular: objectMetadataItem.nameSingular,
- });
-
- const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({
- pageLayoutId,
- layoutType: PageLayoutType.RECORD_PAGE,
- targetRecordIdentifier: {
- id: recordId,
- targetObjectNameSingular: objectMetadataItem.nameSingular,
- },
- });
-
- const { closeSidePanelMenu } = useSidePanelMenu();
-
- const { setIsPageLayoutInEditMode } =
- useSetIsPageLayoutInEditMode(pageLayoutId);
-
- const { resetDraftPageLayoutToPersistedPageLayout } =
- useResetDraftPageLayoutToPersistedPageLayout({
- pageLayoutId,
- tabListInstanceId,
- });
-
- const handleExecute = () => {
- closeSidePanelMenu();
-
- resetDraftPageLayoutToPersistedPageLayout();
- setIsPageLayoutInEditMode(false);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/SaveRecordPageLayoutSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/SaveRecordPageLayoutSingleRecordCommand.tsx
deleted file mode 100644
index 77330eeba7..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/SaveRecordPageLayoutSingleRecordCommand.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
-import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
-import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow';
-import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout';
-import { useSavePageLayoutWidgetsData } from '@/page-layout/hooks/useSavePageLayoutWidgetsData';
-import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
-import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
-import { isDefined } from 'twenty-shared/utils';
-
-export const SaveRecordPageLayoutSingleRecordCommand = () => {
- const { objectMetadataItem } = useHeadlessCommandContextApi();
-
- if (!isDefined(objectMetadataItem)) {
- throw new Error(
- 'Object metadata item is required to save record page layout',
- );
- }
-
- const { pageLayoutId } = useRecordPageLayoutIdFromRecordStoreOrThrow({
- targetObjectNameSingular: objectMetadataItem.nameSingular,
- });
-
- const { savePageLayout } = useSavePageLayout(pageLayoutId);
-
- const { savePageLayoutWidgetsData } = useSavePageLayoutWidgetsData();
-
- const { setIsPageLayoutInEditMode } =
- useSetIsPageLayoutInEditMode(pageLayoutId);
-
- const { closeSidePanelMenu } = useSidePanelMenu();
-
- const handleExecute = async () => {
- const result = await savePageLayout();
-
- if (result.status === 'successful') {
- await savePageLayoutWidgetsData(pageLayoutId);
-
- closeSidePanelMenu();
- setIsPageLayoutInEditMode(false);
- }
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/__tests__/isHeadlessTriggerWorkflowVersionCommandContextApi.test.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/__tests__/isHeadlessTriggerWorkflowVersionCommandContextApi.test.ts
new file mode 100644
index 0000000000..6a46d04149
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/__tests__/isHeadlessTriggerWorkflowVersionCommandContextApi.test.ts
@@ -0,0 +1,46 @@
+import { type HeadlessCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi';
+import { isHeadlessTriggerWorkflowVersionCommandContextApi } from '@/command-menu-item/engine-command/utils/isHeadlessTriggerWorkflowVersionCommandContextApi';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
+const baseContextApi: HeadlessCommandContextApi = {
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ contextStoreInstanceId: 'ctx-1',
+ objectMetadataItem: null,
+ currentViewId: null,
+ recordIndexId: null,
+ targetedRecordsRule: { mode: 'selection', selectedRecordIds: [] },
+ selectedRecords: [],
+ graphqlFilter: null,
+};
+
+describe('isHeadlessTriggerWorkflowVersionCommandContextApi', () => {
+ it('should return true when state has workflowId', () => {
+ const triggerWorkflowContext: HeadlessCommandContextApi = {
+ ...baseContextApi,
+ workflowId: 'wf-1',
+ workflowVersionId: 'wfv-1',
+ payloads: [],
+ };
+
+ expect(
+ isHeadlessTriggerWorkflowVersionCommandContextApi(triggerWorkflowContext),
+ ).toBe(true);
+ });
+
+ it('should return false for plain HeadlessEngineCommandContextApi', () => {
+ expect(
+ isHeadlessTriggerWorkflowVersionCommandContextApi(baseContextApi),
+ ).toBe(false);
+ });
+
+ it('should return false for HeadlessFrontComponentCommandContextApi', () => {
+ const frontComponentContext: HeadlessCommandContextApi = {
+ ...baseContextApi,
+ frontComponentId: 'fc-1',
+ };
+
+ expect(
+ isHeadlessTriggerWorkflowVersionCommandContextApi(frontComponentContext),
+ ).toBe(false);
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
new file mode 100644
index 0000000000..d488230699
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
@@ -0,0 +1,201 @@
+import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
+import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
+import { type CommandMenuItemContainerType } from '@/command-menu-item/types/CommandMenuItemContainerType';
+import { act, renderHook } from '@testing-library/react';
+import { type ReactNode } from 'react';
+
+const TEST_COMMAND_MENU_ID = 'test-cmd-menu-1';
+
+const mockCloseSidePanelMenu = jest.fn();
+const mockCloseDropdown = jest.fn();
+
+jest.mock(
+ '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow',
+ () => ({
+ useAvailableComponentInstanceIdOrThrow: () => TEST_COMMAND_MENU_ID,
+ }),
+);
+
+jest.mock('@/side-panel/hooks/useSidePanelMenu', () => ({
+ useSidePanelMenu: () => ({
+ closeSidePanelMenu: mockCloseSidePanelMenu,
+ }),
+}));
+
+jest.mock('@/ui/layout/dropdown/hooks/useCloseDropdown', () => ({
+ useCloseDropdown: () => ({
+ closeDropdown: mockCloseDropdown,
+ }),
+}));
+
+const getWrapper =
+ ({
+ containerType,
+ isInSidePanel = false,
+ }: {
+ containerType: CommandMenuItemContainerType;
+ isInSidePanel?: boolean;
+ }) =>
+ ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ );
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('useCloseCommandMenu', () => {
+ describe('when containerType is command-menu-list', () => {
+ it('should call closeSidePanelMenu by default', () => {
+ const wrapper = getWrapper({ containerType: 'command-menu-list' });
+
+ const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseSidePanelMenu).toHaveBeenCalledTimes(1);
+ expect(mockCloseDropdown).not.toHaveBeenCalled();
+ });
+
+ it('should not call closeSidePanelMenu when closeSidePanelOnCommandMenuListExecution is false', () => {
+ const wrapper = getWrapper({ containerType: 'command-menu-list' });
+
+ const { result } = renderHook(
+ () =>
+ useCloseCommandMenu({
+ closeSidePanelOnCommandMenuListExecution: false,
+ }),
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseSidePanelMenu).not.toHaveBeenCalled();
+ expect(mockCloseDropdown).not.toHaveBeenCalled();
+ });
+
+ it('should not call closeDropdown', () => {
+ const wrapper = getWrapper({ containerType: 'command-menu-list' });
+
+ const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseDropdown).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when containerType is index-page-dropdown', () => {
+ it('should call closeDropdown with the correct dropdown id', () => {
+ const wrapper = getWrapper({ containerType: 'index-page-dropdown' });
+
+ const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseDropdown).toHaveBeenCalledWith(
+ `command-menu-dropdown-${TEST_COMMAND_MENU_ID}`,
+ );
+ });
+
+ it('should not call closeSidePanelMenu', () => {
+ const wrapper = getWrapper({ containerType: 'index-page-dropdown' });
+
+ const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseSidePanelMenu).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when containerType is command-menu-show-page-dropdown', () => {
+ it('should call closeDropdown with the correct dropdown id', () => {
+ const wrapper = getWrapper({
+ containerType: 'command-menu-show-page-dropdown',
+ });
+
+ const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseDropdown).toHaveBeenCalledWith(
+ `command-menu-dropdown-${TEST_COMMAND_MENU_ID}`,
+ );
+ });
+
+ it('should not call closeSidePanelMenu by default', () => {
+ const wrapper = getWrapper({
+ containerType: 'command-menu-show-page-dropdown',
+ });
+
+ const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseSidePanelMenu).not.toHaveBeenCalled();
+ });
+
+ it('should call closeSidePanelMenu when closeSidePanelOnShowPageOptionsExecution is true', () => {
+ const wrapper = getWrapper({
+ containerType: 'command-menu-show-page-dropdown',
+ });
+
+ const { result } = renderHook(
+ () =>
+ useCloseCommandMenu({
+ closeSidePanelOnShowPageOptionsExecution: true,
+ }),
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseSidePanelMenu).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when isInSidePanel is true', () => {
+ it('should use side panel dropdown id for closeDropdown', () => {
+ const wrapper = getWrapper({
+ containerType: 'index-page-dropdown',
+ isInSidePanel: true,
+ });
+
+ const { result } = renderHook(() => useCloseCommandMenu(), { wrapper });
+
+ act(() => {
+ result.current.closeCommandMenu();
+ });
+
+ expect(mockCloseDropdown).toHaveBeenCalledWith(
+ `side-panel-command-menu-dropdown-${TEST_COMMAND_MENU_ID}`,
+ );
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useRegisteredCommandMenuItems.test.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useRegisteredCommandMenuItems.test.tsx
deleted file mode 100644
index 7c677c859b..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useRegisteredCommandMenuItems.test.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
-import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
-import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
-import { act, renderHook } from '@testing-library/react';
-import { createStore, Provider as JotaiProvider } from 'jotai';
-import { type ReactNode } from 'react';
-import { CommandMenuItemViewType } from 'twenty-shared/types';
-import { Icon123 } from 'twenty-ui/display';
-
-jest.mock('@/command-menu-item/utils/getCommandMenuItemConfig', () => ({
- getCommandMenuItemConfig: () => ({}),
-}));
-
-jest.mock(
- '@/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands',
- () => ({
- useRelatedRecordCommands: () => ({}),
- }),
-);
-
-jest.mock('@/settings/roles/hooks/usePermissionFlagMap', () => ({
- usePermissionFlagMap: () => ({}),
-}));
-
-jest.mock(
- '@/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands',
- () => ({
- useRecordAgnosticCommands: () => ({
- pageEditItem: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Global,
- key: 'page-edit-item',
- label: 'Page Edit Item',
- position: 0,
- Icon: Icon123,
- availableOn: [CommandMenuItemViewType.PAGE_EDIT_MODE],
- shouldBeRegistered: () => true,
- component: null,
- },
- showItem: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Global,
- key: 'show-item',
- label: 'Show Item',
- position: 1,
- Icon: Icon123,
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- shouldBeRegistered: () => true,
- component: null,
- },
- globalItem: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Global,
- key: 'global-item',
- label: 'Global Item',
- position: 2,
- Icon: Icon123,
- availableOn: [CommandMenuItemViewType.GLOBAL],
- shouldBeRegistered: () => true,
- component: null,
- },
- }),
- }),
-);
-
-const CONTEXT_STORE_INSTANCE_ID = 'test-context-store-instance-id';
-
-const getWrapper = (store = createStore()) => {
- return ({ children }: { children: ReactNode }) => (
-
-
- {children}
-
-
- );
-};
-
-const shouldBeRegisteredParams = {
- objectPermissions: {
- canReadObjectRecords: true,
- canUpdateObjectRecords: true,
- canSoftDeleteObjectRecords: true,
- canDestroyObjectRecords: true,
- restrictedFields: {},
- objectMetadataId: '',
- rowLevelPermissionPredicates: [],
- rowLevelPermissionPredicateGroups: [],
- },
- getTargetObjectReadPermission: () => true,
- getTargetObjectWritePermission: () => true,
- isFeatureFlagEnabled: () => true,
-};
-
-describe('useRegisteredCommandMenuItems', () => {
- it('should register SHOW_PAGE and GLOBAL commands when page is not in edit mode', () => {
- const store = createStore();
- const wrapper = getWrapper(store);
-
- store.set(
- contextStoreCurrentViewTypeComponentState.atomFamily({
- instanceId: CONTEXT_STORE_INSTANCE_ID,
- }),
- ContextStoreViewType.ShowPage,
- );
- store.set(
- contextStoreTargetedRecordsRuleComponentState.atomFamily({
- instanceId: CONTEXT_STORE_INSTANCE_ID,
- }),
- { mode: 'selection', selectedRecordIds: [] },
- );
-
- act(() => {
- store.set(
- contextStoreIsPageInEditModeComponentState.atomFamily({
- instanceId: CONTEXT_STORE_INSTANCE_ID,
- }),
- false,
- );
- });
-
- const { result } = renderHook(
- () =>
- useRegisteredCommandMenuItems(
- shouldBeRegisteredParams as Parameters<
- typeof useRegisteredCommandMenuItems
- >[0],
- ),
- {
- wrapper,
- },
- );
-
- expect(result.current.map((item) => item.key)).toEqual([
- 'show-item',
- 'global-item',
- ]);
- });
-
- it('should register PAGE_EDIT_MODE commands when page is in edit mode', () => {
- const store = createStore();
- const wrapper = getWrapper(store);
-
- store.set(
- contextStoreCurrentViewTypeComponentState.atomFamily({
- instanceId: CONTEXT_STORE_INSTANCE_ID,
- }),
- ContextStoreViewType.ShowPage,
- );
- store.set(
- contextStoreTargetedRecordsRuleComponentState.atomFamily({
- instanceId: CONTEXT_STORE_INSTANCE_ID,
- }),
- { mode: 'selection', selectedRecordIds: [] },
- );
-
- act(() => {
- store.set(
- contextStoreIsPageInEditModeComponentState.atomFamily({
- instanceId: CONTEXT_STORE_INSTANCE_ID,
- }),
- true,
- );
- });
-
- const { result } = renderHook(
- () =>
- useRegisteredCommandMenuItems(
- shouldBeRegisteredParams as Parameters<
- typeof useRegisteredCommandMenuItems
- >[0],
- ),
- {
- wrapper,
- },
- );
-
- expect(result.current.map((item) => item.key)).toEqual(['page-edit-item']);
- });
-});
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useRegisteredCommandMenuItems.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useRegisteredCommandMenuItems.ts
deleted file mode 100644
index 5308bbd3be..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/hooks/useRegisteredCommandMenuItems.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands';
-import { useRelatedRecordCommands } from '@/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands';
-import { CommandMenuItemViewType } from 'twenty-shared/types';
-import { type ShouldBeRegisteredFunctionParams } from '@/command-menu-item/types/ShouldBeRegisteredFunctionParams';
-import { getCommandMenuItemConfig } from '@/command-menu-item/utils/getCommandMenuItemConfig';
-import { getCommandMenuItemViewType } from '@/command-menu-item/utils/getCommandMenuItemViewType';
-import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
-import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { isDefined } from 'twenty-shared/utils';
-import { useIcons } from 'twenty-ui/display';
-
-export const useRegisteredCommandMenuItems = (
- shouldBeRegisteredParams: ShouldBeRegisteredFunctionParams,
-) => {
- const { objectMetadataItem } = shouldBeRegisteredParams;
-
- const { getIcon } = useIcons();
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const contextStoreCurrentViewType = useAtomComponentStateValue(
- contextStoreCurrentViewTypeComponentState,
- );
-
- const contextStoreIsPageInEditMode = useAtomComponentStateValue(
- contextStoreIsPageInEditModeComponentState,
- );
-
- const viewType = getCommandMenuItemViewType(
- contextStoreCurrentViewType,
- contextStoreTargetedRecordsRule,
- );
-
- const recordCommandMenuItemsConfig = getCommandMenuItemConfig({
- objectMetadataItem,
- });
-
- const relatedRecordCommandMenuItemsConfig = useRelatedRecordCommands({
- sourceObjectMetadataItem: objectMetadataItem,
- getIcon,
- startPosition: Object.keys(recordCommandMenuItemsConfig).length + 1,
- });
-
- const recordAgnosticCommandMenuItemsConfig = useRecordAgnosticCommands();
-
- const commandMenuItemsConfig = {
- ...recordCommandMenuItemsConfig,
- ...relatedRecordCommandMenuItemsConfig,
- ...recordAgnosticCommandMenuItemsConfig,
- };
-
- const permissionMap = usePermissionFlagMap();
-
- const commandMenuItemsToRegister = Object.values(
- commandMenuItemsConfig,
- ).filter((commandMenuItem) => {
- if (contextStoreIsPageInEditMode) {
- return (
- isDefined(commandMenuItem.availableOn) &&
- commandMenuItem.availableOn.includes(
- CommandMenuItemViewType.PAGE_EDIT_MODE,
- )
- );
- }
-
- if (isDefined(viewType)) {
- return (
- commandMenuItem.availableOn?.includes(viewType) ||
- commandMenuItem.availableOn?.includes(CommandMenuItemViewType.GLOBAL)
- );
- }
-
- return commandMenuItem.availableOn?.includes(
- CommandMenuItemViewType.GLOBAL,
- );
- });
-
- const commandMenuItems = commandMenuItemsToRegister
- .filter((commandMenuItem) => {
- if (
- isDefined(commandMenuItem.requiredPermissionFlag) &&
- !permissionMap[commandMenuItem.requiredPermissionFlag]
- ) {
- return false;
- }
- return commandMenuItem.shouldBeRegistered(shouldBeRegisteredParams);
- })
- .sort((a, b) => a.position - b.position);
-
- return commandMenuItems;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams.ts
deleted file mode 100644
index 8a71f74054..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import { type ShouldBeRegisteredFunctionParams } from '@/command-menu-item/types/ShouldBeRegisteredFunctionParams';
-import { getCommandMenuItemViewType } from '@/command-menu-item/utils/getCommandMenuItemViewType';
-import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
-import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
-import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector';
-import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
-import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
-import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
-import { CoreObjectNameSingular } from 'twenty-shared/types';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
-import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
-import { useStore } from 'jotai';
-import { useCallback, useContext, useMemo } from 'react';
-import { isDefined } from 'twenty-shared/utils';
-import type { FeatureFlagKey } from '~/generated-metadata/graphql';
-
-export const useShouldCommandMenuItemBeRegisteredParams = ({
- objectMetadataItem,
-}: {
- objectMetadataItem?: EnrichedObjectMetadataItem;
-}): ShouldBeRegisteredFunctionParams => {
- const store = useStore();
- const { navigationMenuItems } = useNavigationMenuItemsData();
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const recordId =
- contextStoreTargetedRecordsRule.mode === 'selection'
- ? contextStoreTargetedRecordsRule.selectedRecordIds[0]
- : undefined;
-
- const isFavorite = useMemo(() => {
- if (!isDefined(recordId) || !isDefined(objectMetadataItem)) {
- return false;
- }
-
- const foundNavigationMenuItem = navigationMenuItems?.find(
- (item) =>
- item.targetRecordId === recordId &&
- item.targetObjectMetadataId === objectMetadataItem.id,
- );
- return !!foundNavigationMenuItem;
- }, [recordId, objectMetadataItem, navigationMenuItems]);
-
- const selectedRecord =
- useAtomFamilyStateValue(recordStoreFamilyState, recordId ?? '') ||
- undefined;
-
- const objectPermissions = useObjectPermissionsForObject(
- objectMetadataItem?.id ?? '',
- );
-
- const isNoteOrTask =
- objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note ||
- objectMetadataItem?.nameSingular === CoreObjectNameSingular.Task;
-
- const { isInSidePanel } = useContext(CommandMenuContext);
-
- const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
-
- const hasAnySoftDeleteFilterOnView = useAtomComponentSelectorValue(
- hasAnySoftDeleteFilterOnViewComponentSelector,
- recordIndexId,
- );
-
- const isShowPage =
- useAtomComponentStateValue(contextStoreCurrentViewTypeComponentState) ===
- ContextStoreViewType.ShowPage;
-
- const contextStoreNumberOfSelectedRecords = useAtomComponentStateValue(
- contextStoreNumberOfSelectedRecordsComponentState,
- );
-
- const contextStoreCurrentViewType = useAtomComponentStateValue(
- contextStoreCurrentViewTypeComponentState,
- );
-
- const viewType = getCommandMenuItemViewType(
- contextStoreCurrentViewType,
- contextStoreTargetedRecordsRule,
- );
-
- const isSelectAll = contextStoreTargetedRecordsRule.mode === 'exclusion';
-
- const getObjectReadPermission = useCallback(
- (objectMetadataNameSingular: string) => {
- return store.get(
- objectPermissionsFamilySelector.selectorFamily({
- objectNameSingular: objectMetadataNameSingular,
- }),
- ).canRead;
- },
- [store],
- );
-
- const getObjectWritePermission = useCallback(
- (objectMetadataNameSingular: string) => {
- return store.get(
- objectPermissionsFamilySelector.selectorFamily({
- objectNameSingular: objectMetadataNameSingular,
- }),
- ).canUpdate;
- },
- [store],
- );
-
- const currentWorkspace = useAtomStateValue(currentWorkspaceState);
-
- const isFeatureFlagEnabled = (featureFlagKey: FeatureFlagKey) => {
- const featureFlag = currentWorkspace?.featureFlags?.find(
- (flag) => flag.key === featureFlagKey,
- );
-
- return featureFlag?.value === true;
- };
-
- return {
- objectMetadataItem,
- isFavorite,
- objectPermissions,
- isNoteOrTask,
- isInSidePanel,
- hasAnySoftDeleteFilterOnView,
- isShowPage,
- isSelectAll,
- selectedRecord,
- numberOfSelectedRecords: contextStoreNumberOfSelectedRecords,
- viewType: viewType ?? undefined,
- getTargetObjectReadPermission: getObjectReadPermission,
- getTargetObjectWritePermission: getObjectWritePermission,
- isFeatureFlagEnabled,
- };
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx b/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx
index 0eae11ebc2..12fb70b970 100644
--- a/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx
@@ -1,17 +1,5 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import {
- CommandMenuItemViewType,
- CoreObjectNameSingular,
- AppPath,
-} from 'twenty-shared/types';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { msg } from '@lingui/core/macro';
+import { CommandMenuItemViewType, AppPath } from 'twenty-shared/types';
import {
IconFileExport,
IconHeart,
@@ -19,6 +7,14 @@ import {
IconUser,
} from 'twenty-ui/display';
+import { Command } from '@/command-menu-item/display/components/Command';
+import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
+import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
+import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
+import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
+import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
+import { EngineComponentKey } from '~/generated-metadata/graphql';
+
export const createMockCommandMenuItems = ({
deleteMock = () => {},
addToFavoritesMock = () => {},
@@ -31,7 +27,7 @@ export const createMockCommandMenuItems = ({
{
type: CommandMenuItemType.Standard,
scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.ADD_TO_FAVORITES,
+ key: EngineComponentKey.ADD_TO_FAVORITES,
label: msg`Add to favorites`,
shortLabel: msg`Add to favorites`,
position: 2,
@@ -47,7 +43,7 @@ export const createMockCommandMenuItems = ({
{
type: CommandMenuItemType.Standard,
scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
+ key: EngineComponentKey.EXPORT_FROM_RECORD_INDEX,
label: msg`Export`,
shortLabel: msg`Export`,
position: 4,
@@ -61,7 +57,7 @@ export const createMockCommandMenuItems = ({
{
type: CommandMenuItemType.Standard,
scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.DELETE,
+ key: EngineComponentKey.DELETE_SINGLE_RECORD,
label: msg`Delete`,
shortLabel: msg`Delete`,
position: 7,
@@ -78,7 +74,7 @@ export const createMockCommandMenuItems = ({
{
type: CommandMenuItemType.Navigation,
scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
+ key: EngineComponentKey.GO_TO_PEOPLE,
label: msg`Go to People`,
shortLabel: msg`People`,
position: 19,
@@ -90,9 +86,7 @@ export const createMockCommandMenuItems = ({
CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
CommandMenuItemViewType.SHOW_PAGE,
],
- shouldBeRegistered: ({ objectMetadataItem, viewType }) =>
- objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Person ||
- viewType === CommandMenuItemViewType.SHOW_PAGE,
+ shouldBeRegistered: () => true,
component: (
= {
- [RecordAgnosticCommandKeys.SEARCH_RECORDS]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Global,
- key: RecordAgnosticCommandKeys.SEARCH_RECORDS,
- label: msg`Search records`,
- shortLabel: msg`Search`,
- position: 0,
- isPinned: false,
- Icon: IconSearch,
- availableOn: [CommandMenuItemViewType.GLOBAL],
- component: (
-
- ),
- hotKeys: ['/'],
- shouldBeRegistered: () => true,
- },
- [RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK]: {
- type: CommandMenuItemType.Fallback,
- scope: CommandMenuItemScope.Global,
- key: RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK,
- label: msg`Search records`,
- shortLabel: msg`Search`,
- position: 1,
- isPinned: false,
- Icon: IconSearch,
- availableOn: [CommandMenuItemViewType.GLOBAL],
- component: (
-
- ),
- hotKeys: ['/'],
- shouldBeRegistered: () => true,
- },
- [RecordAgnosticCommandKeys.ASK_AI]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Global,
- key: RecordAgnosticCommandKeys.ASK_AI,
- label: msg`Ask AI`,
- shortLabel: msg`Ask AI`,
- position: 2,
- isPinned: false,
- Icon: IconSparkles,
- availableOn: [CommandMenuItemViewType.GLOBAL],
- component: (
-
- ),
- hotKeys: ['@'],
- shouldBeRegistered: () => true,
- },
- [RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Global,
- key: RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS,
- label: msg`View Previous AI Chats`,
- shortLabel: msg`Previous AI Chats`,
- position: 3,
- isPinned: false,
- Icon: IconHistory,
- availableOn: [CommandMenuItemViewType.GLOBAL],
- component: (
-
- ),
- shouldBeRegistered: () => true,
- },
- [RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR,
- label: msg`Edit navigation sidebar`,
- shortLabel: msg`Edit sidebar`,
- position: 4,
- Icon: IconLayout,
- isPinned: false,
- availableOn: [CommandMenuItemViewType.GLOBAL],
- shouldBeRegistered: () => true,
- component: ,
- },
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/__tests__/useRelatedRecordCommands.test.tsx b/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/__tests__/useRelatedRecordCommands.test.tsx
deleted file mode 100644
index d4a9434551..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/__tests__/useRelatedRecordCommands.test.tsx
+++ /dev/null
@@ -1,252 +0,0 @@
-import { CoreObjectNameSingular } from 'twenty-shared/types';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { renderHook } from '@testing-library/react';
-import { useRelatedRecordCommands } from '@/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands';
-
-jest.mock('@/object-metadata/hooks/useObjectMetadataItems', () => ({
- useObjectMetadataItems: () => ({
- objectMetadataItems: [
- {
- id: 'person-id',
- nameSingular: CoreObjectNameSingular.Person,
- namePlural: 'People',
- labelSingular: 'Person',
- },
- {
- id: 'company-id',
- nameSingular: CoreObjectNameSingular.Company,
- namePlural: 'Companies',
- labelSingular: 'Company',
- },
- ],
- }),
-}));
-
-describe('useRelatedRecordCommands', () => {
- const mockGetIcon = jest.fn();
-
- beforeEach(() => {
- mockGetIcon.mockClear();
- });
-
- it('should return empty object when objectMetadataItem has no fields', () => {
- const objectMetadataItem = {
- fields: [],
- readableFields: [],
- updatableFields: [],
- } as unknown as EnrichedObjectMetadataItem;
-
- const { result } = renderHook(() =>
- useRelatedRecordCommands({
- sourceObjectMetadataItem: objectMetadataItem,
- getIcon: mockGetIcon,
- startPosition: 18,
- }),
- );
-
- expect(result.current).toEqual({});
- });
-
- it('should return empty object when objectMetadataItem is undefined', () => {
- const objectMetadataItem =
- undefined as unknown as EnrichedObjectMetadataItem;
-
- const { result } = renderHook(() =>
- useRelatedRecordCommands({
- sourceObjectMetadataItem: objectMetadataItem,
- getIcon: mockGetIcon,
- startPosition: 18,
- }),
- );
-
- expect(result.current).toEqual({});
- });
-
- it('should generate actions for one-to-many relations', () => {
- const fields = [
- {
- type: 'RELATION',
- relation: {
- type: 'ONE_TO_MANY',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Person,
- namePlural: 'People',
- },
- },
- label: 'person',
- isSystem: false,
- },
- {
- type: 'RELATION',
- relation: {
- type: 'ONE_TO_MANY',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Company,
- namePlural: 'Companies',
- },
- },
- label: 'company',
- isSystem: false,
- },
- ];
- const objectMetadataItem = {
- fields,
- readableFields: fields,
- updatableFields: fields,
- } as unknown as EnrichedObjectMetadataItem;
-
- const { result } = renderHook(() =>
- useRelatedRecordCommands({
- sourceObjectMetadataItem: objectMetadataItem,
- getIcon: mockGetIcon,
- startPosition: 18,
- }),
- );
-
- expect(Object.keys(result.current)).toHaveLength(2);
- expect(result.current['create-related-person']).toBeDefined();
- expect(result.current['create-related-company']).toBeDefined();
- });
-
- it('should filter out non-one-to-many relations', () => {
- const fields = [
- {
- type: 'RELATION',
- relation: {
- type: 'MANY_TO_ONE',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Person,
- namePlural: 'People',
- },
- },
- label: 'person',
- isSystem: false,
- },
- {
- type: 'TEXT',
- },
- {
- type: 'RELATION',
- relation: {
- type: 'ONE_TO_MANY',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Company,
- namePlural: 'Companies',
- },
- },
- label: 'company',
- isSystem: false,
- },
- ];
-
- const objectMetadataItem = {
- fields,
- readableFields: fields,
- updatableFields: fields,
- } as unknown as EnrichedObjectMetadataItem;
-
- const { result } = renderHook(() =>
- useRelatedRecordCommands({
- sourceObjectMetadataItem: objectMetadataItem,
- getIcon: mockGetIcon,
- startPosition: 18,
- }),
- );
-
- expect(Object.keys(result.current)).toHaveLength(1);
- expect(result.current['create-related-company']).toBeDefined();
- expect(result.current['create-related-person']).toBeUndefined();
- });
-
- it('should assign correct positions to each action', () => {
- const fields = [
- {
- type: 'RELATION',
- relation: {
- type: 'ONE_TO_MANY',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Person,
- namePlural: 'People',
- },
- },
- label: 'person',
- isSystem: false,
- },
- {
- type: 'RELATION',
- relation: {
- type: 'ONE_TO_MANY',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Company,
- namePlural: 'Companies',
- },
- },
- label: 'company',
- isSystem: false,
- },
- ];
- const objectMetadataItem = {
- fields,
- readableFields: fields,
- updatableFields: fields,
- } as unknown as EnrichedObjectMetadataItem;
-
- const { result } = renderHook(() =>
- useRelatedRecordCommands({
- sourceObjectMetadataItem: objectMetadataItem,
- getIcon: mockGetIcon,
- startPosition: 18,
- }),
- );
-
- expect(result.current['create-related-person'].position).toBe(18);
- expect(result.current['create-related-company'].position).toBe(19);
- });
-
- it('should filter out hidden system fields', () => {
- const fields = [
- {
- type: 'RELATION',
- name: 'position',
- relation: {
- type: 'ONE_TO_MANY',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Person,
- namePlural: 'People',
- },
- },
- label: 'person',
- isSystem: true,
- },
- {
- type: 'RELATION',
- relation: {
- type: 'ONE_TO_MANY',
- targetObjectMetadata: {
- nameSingular: CoreObjectNameSingular.Company,
- namePlural: 'Companies',
- },
- },
- label: 'company',
- isSystem: false,
- },
- ];
- const objectMetadataItem = {
- fields,
- readableFields: fields,
- updatableFields: fields,
- } as unknown as EnrichedObjectMetadataItem;
-
- const { result } = renderHook(() =>
- useRelatedRecordCommands({
- sourceObjectMetadataItem: objectMetadataItem,
- getIcon: mockGetIcon,
- startPosition: 18,
- }),
- );
-
- expect(Object.keys(result.current)).toHaveLength(1);
- expect(result.current['create-related-company']).toBeDefined();
- expect(result.current['create-related-person']).toBeUndefined();
- });
-});
diff --git a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands.ts b/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands.ts
deleted file mode 100644
index ddfbec5b76..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record-agnostic/constants/RecordAgnosticCommandMenuItemsConfig';
-import { RecordAgnosticCommandKeys } from '@/command-menu-item/record-agnostic/types/RecordAgnosticCommandKeys';
-import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
-
-export const useRecordAgnosticCommands = () => {
- const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
-
- const commandMenuItems: Record = {
- [RecordAgnosticCommandKeys.SEARCH_RECORDS]:
- RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
- RecordAgnosticCommandKeys.SEARCH_RECORDS
- ],
- [RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK]:
- RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
- RecordAgnosticCommandKeys.SEARCH_RECORDS_FALLBACK
- ],
- [RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR]:
- RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
- RecordAgnosticCommandKeys.EDIT_NAVIGATION_SIDEBAR
- ],
- };
-
- if (isAiEnabled) {
- commandMenuItems[RecordAgnosticCommandKeys.ASK_AI] =
- RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
- RecordAgnosticCommandKeys.ASK_AI
- ];
- commandMenuItems[RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS] =
- RECORD_AGNOSTIC_COMMAND_MENU_ITEMS_CONFIG[
- RecordAgnosticCommandKeys.VIEW_PREVIOUS_AI_CHATS
- ];
- }
-
- return commandMenuItems;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands.ts b/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands.ts
deleted file mode 100644
index 63176b6f1c..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { CreateRelatedRecordCommand } from '@/command-menu-item/record/single-record/components/CreateRelatedRecordCommand';
-import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import {
- CommandMenuItemViewType,
- CoreObjectNameSingular,
-} from 'twenty-shared/types';
-import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
-import { isRecordReadOnly } from '@/object-record/read-only/utils/isRecordReadOnly';
-import { msg } from '@lingui/core/macro';
-import React from 'react';
-import { isDefined } from 'twenty-shared/utils';
-import { type IconComponent, IconPlus } from 'twenty-ui/display';
-
-interface GenerateRelatedRecordCommandsParams {
- sourceObjectMetadataItem?: EnrichedObjectMetadataItem;
- getIcon: (iconKey: string) => IconComponent;
- startPosition: number;
-}
-
-export const useRelatedRecordCommands = ({
- sourceObjectMetadataItem,
- getIcon,
- startPosition,
-}: GenerateRelatedRecordCommandsParams): Record<
- string,
- CommandMenuItemConfig
-> => {
- const relatedCommands: Record = {};
-
- const { objectMetadataItems } = useObjectMetadataItems();
-
- if (!sourceObjectMetadataItem?.fields) {
- return relatedCommands;
- }
-
- const oneToManyFields = sourceObjectMetadataItem.readableFields.filter(
- (field) =>
- field.type === 'RELATION' &&
- field.relation?.type === 'ONE_TO_MANY' &&
- !isHiddenSystemField(field),
- );
-
- let currentPosition = startPosition;
-
- for (const field of oneToManyFields) {
- if (!isDefined(field.relation)) {
- throw new Error(`Field relation is undefined for field: ${field.id}`);
- }
-
- const targetObjectName = field.relation.targetObjectMetadata.nameSingular;
-
- const targetObjectMetadataItem = objectMetadataItems.find(
- (item) => item.nameSingular === targetObjectName,
- );
-
- if (!isDefined(targetObjectMetadataItem)) {
- throw new Error(
- `Target object metadata item is undefined for field: ${field.id}`,
- );
- }
-
- const targetObjectNameSingular = targetObjectMetadataItem.nameSingular;
- const targetObjectLabelSingular =
- targetObjectNameSingular === CoreObjectNameSingular.TaskTarget
- ? 'task'
- : targetObjectNameSingular === CoreObjectNameSingular.NoteTarget
- ? 'note'
- : targetObjectMetadataItem.labelSingular.toLowerCase();
-
- const actionKey = `create-related-${targetObjectLabelSingular}`;
-
- relatedCommands[actionKey] = {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.CreateRelatedRecord,
- key: actionKey,
- label: msg`Create ${targetObjectLabelSingular}`,
- shortLabel: msg`Create ${targetObjectLabelSingular}`,
- position: currentPosition,
- Icon: field.icon ? getIcon(field.icon) : IconPlus,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({
- selectedRecord,
- objectPermissions,
- getTargetObjectWritePermission,
- objectMetadataItem,
- }) =>
- (isDefined(selectedRecord) &&
- isDefined(objectMetadataItem) &&
- isRecordReadOnly({
- objectPermissions: {
- canUpdateObjectRecords: objectPermissions.canUpdateObjectRecords,
- objectMetadataId: objectMetadataItem.id,
- },
- objectMetadataItem,
- isRecordDeleted: isDefined(selectedRecord.deletedAt),
- }) &&
- objectPermissions.canUpdateObjectRecords &&
- getTargetObjectWritePermission(
- targetObjectNameSingular === CoreObjectNameSingular.TaskTarget
- ? CoreObjectNameSingular.Task
- : targetObjectNameSingular === CoreObjectNameSingular.NoteTarget
- ? CoreObjectNameSingular.Note
- : targetObjectNameSingular,
- )) ??
- false,
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: React.createElement(CreateRelatedRecordCommand, {
- targetFieldMetadataItemRelation: field.relation,
- }),
- };
-
- currentPosition++;
- }
-
- return relatedCommands;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/types/RecordAgnosticCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record-agnostic/types/RecordAgnosticCommandKeys.ts
deleted file mode 100644
index 188fd96709..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/types/RecordAgnosticCommandKeys.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export enum RecordAgnosticCommandKeys {
- SEARCH_RECORDS = 'search-records',
- SEARCH_RECORDS_FALLBACK = 'search-records-fallback',
- ASK_AI = 'ask-ai',
- VIEW_PREVIOUS_AI_CHATS = 'view-previous-ai-chats',
- EDIT_NAVIGATION_SIDEBAR = 'edit-navigation-sidebar',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands.tsx b/packages/twenty-front/src/modules/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands.tsx
deleted file mode 100644
index 99e9fd753b..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
-import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
-import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
-import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
-import { useContext } from 'react';
-import { capitalize, isDefined } from 'twenty-shared/utils';
-import { useIcons } from 'twenty-ui/display';
-
-export const useRunWorkflowRecordAgnosticCommands = () => {
- const { getIcon } = useIcons();
-
- const contextStoreIsPageInEditMode = useAtomComponentStateValue(
- contextStoreIsPageInEditModeComponentState,
- );
-
- const { containerType } = useContext(CommandMenuContext);
-
- const { records: activeWorkflowVersions } =
- useActiveWorkflowVersionsWithManualTrigger({
- skip:
- containerType !== 'command-menu-list' &&
- containerType !== 'command-menu-show-page-dropdown',
- });
-
- const { runWorkflowVersion } = useRunWorkflowVersion();
-
- return activeWorkflowVersions
- .map((activeWorkflowVersion, index) => {
- if (!isDefined(activeWorkflowVersion.workflow)) {
- return undefined;
- }
-
- const name = capitalize(activeWorkflowVersion.workflow.name);
-
- const Icon = getIcon(
- activeWorkflowVersion.trigger?.settings.icon,
- COMMAND_MENU_DEFAULT_ICON,
- );
-
- return {
- type: CommandMenuItemType.WorkflowRun,
- key: `workflow-run-${activeWorkflowVersion.id}`,
- scope: CommandMenuItemScope.Global,
- label: name,
- shortLabel: name,
- position: index,
- isPinned:
- !contextStoreIsPageInEditMode &&
- activeWorkflowVersion.trigger?.settings?.isPinned,
- Icon,
- shouldBeRegistered: () => true,
- component: (
- {
- runWorkflowVersion({
- workflowVersionId: activeWorkflowVersion.id,
- workflowId: activeWorkflowVersion.workflowId,
- });
- }}
- closeSidePanelOnCommandMenuListExecution={false}
- />
- ),
- };
- })
- .filter(isDefined);
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/constants/DashboardCommandMenuItemsConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/record/constants/DashboardCommandMenuItemsConfig.tsx
deleted file mode 100644
index a1a30f716f..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/constants/DashboardCommandMenuItemsConfig.tsx
+++ /dev/null
@@ -1,204 +0,0 @@
-import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { CancelDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand';
-import { DuplicateDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand';
-import { EditDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/EditDashboardSingleRecordCommand';
-import { SaveDashboardSingleRecordCommand } from '@/command-menu-item/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand';
-import { DashboardSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/dashboard/types/DashboardSingleRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { CommandMenuItemViewType } from 'twenty-shared/types';
-import { PageLayoutSingleRecordActionKeys } from '@/page-layout/actions/PageLayoutSingleRecordActionKeys';
-import { msg } from '@lingui/core/macro';
-import { isDefined } from 'twenty-shared/utils';
-import {
- IconCancel,
- IconCopyPlus,
- IconDeviceFloppy,
- IconPencil,
-} from 'twenty-ui/display';
-
-export const DASHBOARD_COMMAND_MENU_ITEMS_CONFIG =
- inheritCommandMenuItemsFromDefaultConfig({
- config: {
- [PageLayoutSingleRecordActionKeys.EDIT_LAYOUT]: {
- key: PageLayoutSingleRecordActionKeys.EDIT_LAYOUT,
- label: msg`Edit Dashboard`,
- shortLabel: msg`Edit`,
- isPinned: true,
- position: 3,
- Icon: IconPencil,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
- isDefined(selectedRecord) &&
- !selectedRecord?.isRemote &&
- !isDefined(selectedRecord?.deletedAt) &&
- isDefined(selectedRecord?.pageLayoutId) &&
- objectPermissions.canUpdateObjectRecords,
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- },
- [PageLayoutSingleRecordActionKeys.SAVE_LAYOUT]: {
- key: PageLayoutSingleRecordActionKeys.SAVE_LAYOUT,
- label: msg`Save Dashboard`,
- shortLabel: msg`Save`,
- isPinned: true,
- isPrimaryCTA: true,
- position: 4,
- Icon: IconDeviceFloppy,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
- isDefined(selectedRecord) &&
- !selectedRecord?.isRemote &&
- !isDefined(selectedRecord?.deletedAt) &&
- isDefined(selectedRecord?.pageLayoutId) &&
- objectPermissions.canUpdateObjectRecords,
- availableOn: [CommandMenuItemViewType.PAGE_EDIT_MODE],
- component: ,
- },
- [PageLayoutSingleRecordActionKeys.CANCEL_LAYOUT_EDITION]: {
- key: PageLayoutSingleRecordActionKeys.CANCEL_LAYOUT_EDITION,
- label: msg`Cancel Edition`,
- shortLabel: msg`Cancel`,
- isPinned: true,
- position: 5,
- Icon: IconCancel,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
- isDefined(selectedRecord) &&
- !selectedRecord?.isRemote &&
- !isDefined(selectedRecord?.deletedAt) &&
- isDefined(selectedRecord?.pageLayoutId) &&
- objectPermissions.canUpdateObjectRecords,
- availableOn: [CommandMenuItemViewType.PAGE_EDIT_MODE],
- component: ,
- },
- [DashboardSingleRecordCommandKeys.DUPLICATE_DASHBOARD]: {
- key: DashboardSingleRecordCommandKeys.DUPLICATE_DASHBOARD,
- label: msg`Duplicate Dashboard`,
- shortLabel: msg`Duplicate`,
- isPinned: false,
- position: 6,
- Icon: IconCopyPlus,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
- isDefined(selectedRecord) &&
- !selectedRecord?.isRemote &&
- !isDefined(selectedRecord?.deletedAt) &&
- objectPermissions.canUpdateObjectRecords,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- component: ,
- },
- },
- commandKeys: [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
- SingleRecordCommandKeys.DELETE,
- SingleRecordCommandKeys.DESTROY,
- SingleRecordCommandKeys.RESTORE,
- MultipleRecordsCommandKeys.DELETE,
- MultipleRecordsCommandKeys.DESTROY,
- MultipleRecordsCommandKeys.RESTORE,
- NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
- SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
- SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
- NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
- NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
- NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
- NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
- NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
- NoSelectionRecordCommandKeys.GO_TO_TASKS,
- NoSelectionRecordCommandKeys.GO_TO_NOTES,
- ],
- propertiesToOverwrite: {
- [SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
- position: 0,
- label: msg`Navigate to next dashboard`,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
- position: 1,
- label: msg`Navigate to previous dashboard`,
- },
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- position: 2,
- label: msg`Create new dashboard`,
- },
- [SingleRecordCommandKeys.DELETE]: {
- position: 7,
- label: msg`Delete dashboard`,
- },
- [MultipleRecordsCommandKeys.DELETE]: {
- position: 12,
- label: msg`Delete dashboards`,
- },
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- position: 8,
- isPinned: true,
- },
- [SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
- position: 9,
- isPinned: true,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
- position: 10,
- label: msg`Export dashboard`,
- },
- [SingleRecordCommandKeys.DESTROY]: {
- position: 11,
- label: msg`Permanently destroy dashboard`,
- },
- [MultipleRecordsCommandKeys.DESTROY]: {
- position: 13,
- label: msg`Permanently destroy dashboards`,
- },
- [SingleRecordCommandKeys.RESTORE]: {
- position: 14,
- label: msg`Restore dashboard`,
- },
- [MultipleRecordsCommandKeys.RESTORE]: {
- position: 15,
- label: msg`Restore dashboards`,
- },
- [NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
- position: 22,
- label: msg`See deleted dashboards`,
- },
- [NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
- position: 23,
- label: msg`Hide deleted dashboards`,
- },
- [NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
- position: 24,
- },
- [NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
- position: 25,
- },
- [NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
- position: 26,
- },
- [NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
- position: 27,
- },
- [NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
- position: 28,
- },
- [NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
- position: 29,
- },
- [NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
- position: 30,
- },
- },
- });
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
deleted file mode 100644
index ea14c16f6c..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx
+++ /dev/null
@@ -1,813 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { DeleteMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/DeleteMultipleRecordsCommand';
-import { DestroyMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/DestroyMultipleRecordsCommand';
-import { ExportMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/ExportMultipleRecordsCommand';
-import { MergeMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/MergeMultipleRecordsCommand';
-import { RestoreMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/RestoreMultipleRecordsCommand';
-import { UpdateMultipleRecordsCommand } from '@/command-menu-item/record/multiple-records/components/UpdateMultipleRecordsCommand';
-import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { CreateNewIndexRecordNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand';
-import { CreateNewViewNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/CreateNewViewNoSelectionRecordCommand';
-import { HideDeletedRecordsNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand';
-import { ImportRecordsNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/ImportRecordsNoSelectionRecordCommand';
-import { SeeDeletedRecordsNoSelectionRecordCommand } from '@/command-menu-item/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { AddToFavoritesSingleRecordCommand } from '@/command-menu-item/record/single-record/components/AddToFavoritesSingleRecordCommand';
-import { DeleteSingleRecordCommand } from '@/command-menu-item/record/single-record/components/DeleteSingleRecordCommand';
-import { DestroySingleRecordCommand } from '@/command-menu-item/record/single-record/components/DestroySingleRecordCommand';
-import { ExportNoteSingleRecordCommand } from '@/command-menu-item/record/single-record/components/ExportNoteSingleRecordCommand';
-import { ExportSingleRecordCommand } from '@/command-menu-item/record/single-record/components/ExportSingleRecordCommand';
-import { NavigateToNextRecordSingleRecordCommand } from '@/command-menu-item/record/single-record/components/NavigateToNextRecordSingleRecordCommand';
-import { NavigateToPreviousRecordSingleRecordCommand } from '@/command-menu-item/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand';
-import { RemoveFromFavoritesSingleRecordCommand } from '@/command-menu-item/record/single-record/components/RemoveFromFavoritesSingleRecordCommand';
-import { RestoreSingleRecordCommand } from '@/command-menu-item/record/single-record/components/RestoreSingleRecordCommand';
-import { EditRecordPageLayoutSingleRecordCommand } from '@/command-menu-item/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand';
-import { RecordPageLayoutSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/record-page-layout/types/RecordPageLayoutSingleRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import { msg } from '@lingui/core/macro';
-import { isNonEmptyString } from '@sniptt/guards';
-import {
- BACKEND_BATCH_REQUEST_MAX_COUNT,
- MUTATION_MAX_MERGE_RECORDS,
-} from 'twenty-shared/constants';
-import {
- AppPath,
- CommandMenuItemViewType,
- CoreObjectNameSingular,
- SettingsPath,
-} from 'twenty-shared/types';
-import {
- IconArrowMerge,
- IconBuildingSkyscraper,
- IconCheckbox,
- IconChevronDown,
- IconChevronUp,
- IconEdit,
- IconEyeOff,
- IconFileExport,
- IconFileImport,
- IconHeart,
- IconHeartOff,
- IconLayout,
- IconLayoutDashboard,
- IconPencil,
- IconPlus,
- IconRefresh,
- IconRotate2,
- IconSettings,
- IconSettingsAutomation,
- IconTargetArrow,
- IconTrash,
- IconTrashX,
- IconUser,
-} from 'twenty-ui/display';
-
-import { isDefined } from 'twenty-shared/utils';
-import {
- FeatureFlagKey,
- PermissionFlagType,
-} from '~/generated-metadata/graphql';
-
-export const DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG: Record<
- | NoSelectionRecordCommandKeys
- | SingleRecordCommandKeys
- | MultipleRecordsCommandKeys
- | RecordPageLayoutSingleRecordCommandKeys,
- CommandMenuItemConfig
-> = {
- [SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
- label: msg`Navigate to next record`,
- position: 0,
- isPinned: true,
- Icon: IconChevronDown,
- shouldBeRegistered: ({ isInSidePanel }) => !isInSidePanel,
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
- label: msg`Navigate to previous record`,
- position: 1,
- isPinned: true,
- Icon: IconChevronUp,
- shouldBeRegistered: ({ isInSidePanel }) => !isInSidePanel,
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- },
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- label: msg`Create new record`,
- shortLabel: msg`New record`,
- position: 2,
- isPinned: true,
- Icon: IconPlus,
- shouldBeRegistered: ({
- objectMetadataItem,
- objectPermissions,
- hasAnySoftDeleteFilterOnView,
- }) =>
- (!objectMetadataItem?.isSystem &&
- objectPermissions.canUpdateObjectRecords &&
- !hasAnySoftDeleteFilterOnView) ??
- false,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: ,
- },
- [SingleRecordCommandKeys.DELETE]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.DELETE,
- label: msg`Delete`,
- shortLabel: msg`Delete`,
- position: 3,
- Icon: IconTrash,
- accent: 'default',
- isPinned: true,
- shouldBeRegistered: ({
- selectedRecord,
- hasAnySoftDeleteFilterOnView,
- objectPermissions,
- objectMetadataItem,
- }) =>
- (!objectMetadataItem?.isSystem &&
- isDefined(selectedRecord) &&
- !selectedRecord.isRemote &&
- !hasAnySoftDeleteFilterOnView &&
- objectPermissions.canSoftDeleteObjectRecords &&
- !isDefined(selectedRecord?.deletedAt)) ??
- false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- component: ,
- },
- [MultipleRecordsCommandKeys.DELETE]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: MultipleRecordsCommandKeys.DELETE,
- label: msg`Delete records`,
- shortLabel: msg`Delete`,
- position: 4,
- Icon: IconTrash,
- accent: 'default',
- isPinned: true,
- shouldBeRegistered: ({
- objectPermissions,
- isRemote,
- hasAnySoftDeleteFilterOnView,
- numberOfSelectedRecords,
- objectMetadataItem,
- }) =>
- (!objectMetadataItem?.isSystem &&
- objectPermissions.canSoftDeleteObjectRecords &&
- !isRemote &&
- !hasAnySoftDeleteFilterOnView &&
- isDefined(numberOfSelectedRecords) &&
- numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
- false,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
- component: ,
- },
- [SingleRecordCommandKeys.RESTORE]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.RESTORE,
- label: msg`Restore record`,
- shortLabel: msg`Restore`,
- position: 5,
- Icon: IconRefresh,
- accent: 'default',
- isPinned: true,
- shouldBeRegistered: ({
- selectedRecord,
- objectPermissions,
- isRemote,
- isShowPage,
- hasAnySoftDeleteFilterOnView,
- objectMetadataItem,
- }) =>
- (!objectMetadataItem?.isSystem &&
- !isRemote &&
- isDefined(selectedRecord?.deletedAt) &&
- objectPermissions.canSoftDeleteObjectRecords &&
- ((isDefined(isShowPage) && isShowPage) ||
- (isDefined(hasAnySoftDeleteFilterOnView) &&
- hasAnySoftDeleteFilterOnView))) ??
- false,
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [MultipleRecordsCommandKeys.RESTORE]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: MultipleRecordsCommandKeys.RESTORE,
- label: msg`Restore records`,
- shortLabel: msg`Restore`,
- position: 6,
- Icon: IconRefresh,
- accent: 'default',
- isPinned: true,
- shouldBeRegistered: ({
- objectPermissions,
- isRemote,
- hasAnySoftDeleteFilterOnView,
- numberOfSelectedRecords,
- objectMetadataItem,
- }) =>
- (!objectMetadataItem?.isSystem &&
- objectPermissions.canSoftDeleteObjectRecords &&
- !isRemote &&
- isDefined(hasAnySoftDeleteFilterOnView) &&
- hasAnySoftDeleteFilterOnView &&
- isDefined(numberOfSelectedRecords) &&
- numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
- false,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
- component: ,
- },
- [SingleRecordCommandKeys.DESTROY]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.DESTROY,
- label: msg`Permanently destroy record`,
- shortLabel: msg`Destroy`,
- position: 7,
- Icon: IconTrashX,
- accent: 'danger',
- isPinned: true,
- shouldBeRegistered: ({
- selectedRecord,
- objectPermissions,
- isRemote,
- objectMetadataItem,
- }) =>
- (!objectMetadataItem?.isSystem &&
- objectPermissions.canDestroyObjectRecords &&
- !isRemote &&
- isDefined(selectedRecord?.deletedAt)) ??
- false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- component: ,
- },
- [MultipleRecordsCommandKeys.DESTROY]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: MultipleRecordsCommandKeys.DESTROY,
- label: msg`Permanently destroy records`,
- shortLabel: msg`Destroy`,
- position: 8,
- Icon: IconTrashX,
- accent: 'danger',
- isPinned: true,
- shouldBeRegistered: ({
- objectPermissions,
- isRemote,
- hasAnySoftDeleteFilterOnView,
- numberOfSelectedRecords,
- objectMetadataItem,
- }) =>
- (!objectMetadataItem?.isSystem &&
- objectPermissions.canDestroyObjectRecords &&
- !isRemote &&
- isDefined(hasAnySoftDeleteFilterOnView) &&
- hasAnySoftDeleteFilterOnView &&
- isDefined(numberOfSelectedRecords) &&
- numberOfSelectedRecords < BACKEND_BATCH_REQUEST_MAX_COUNT) ??
- false,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
- component: ,
- },
-
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.ADD_TO_FAVORITES,
- label: msg`Add to favorites`,
- shortLabel: msg`Add to favorites`,
- position: 9,
- isPinned: true,
- Icon: IconHeart,
- shouldBeRegistered: ({
- selectedRecord,
- isFavorite,
- hasAnySoftDeleteFilterOnView,
- objectMetadataItem,
- }) =>
- !objectMetadataItem?.isSystem &&
- !selectedRecord?.isRemote &&
- !isFavorite &&
- !isDefined(selectedRecord?.deletedAt) &&
- !hasAnySoftDeleteFilterOnView,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- component: ,
- },
- [SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
- label: msg`Remove from favorites`,
- shortLabel: msg`Remove from favorites`,
- isPinned: true,
- position: 10,
- Icon: IconHeartOff,
- shouldBeRegistered: ({
- selectedRecord,
- isFavorite,
- hasAnySoftDeleteFilterOnView,
- objectMetadataItem,
- }) =>
- !objectMetadataItem?.isSystem &&
- isDefined(selectedRecord) &&
- !selectedRecord?.isRemote &&
- isDefined(isFavorite) &&
- isFavorite &&
- !isDefined(selectedRecord?.deletedAt) &&
- !hasAnySoftDeleteFilterOnView,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- component: ,
- },
- [SingleRecordCommandKeys.EXPORT_NOTE_TO_PDF]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.EXPORT_NOTE_TO_PDF,
- label: msg`Export to PDF`,
- shortLabel: msg`Export`,
- position: 11,
- isPinned: false,
- Icon: IconFileExport,
- shouldBeRegistered: ({ selectedRecord, isNoteOrTask }) =>
- isDefined(isNoteOrTask) &&
- isNoteOrTask &&
- isNonEmptyString(selectedRecord?.bodyV2?.blocknote),
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
- label: msg`Export`,
- shortLabel: msg`Export`,
- position: 12,
- Icon: IconFileExport,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({ selectedRecord }) =>
- isDefined(selectedRecord) && !selectedRecord.isRemote,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION],
- component: ,
- requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
- label: msg`Export`,
- shortLabel: msg`Export`,
- position: 13,
- Icon: IconFileExport,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({ selectedRecord }) =>
- isDefined(selectedRecord) && !selectedRecord.isRemote,
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
- },
- [MultipleRecordsCommandKeys.UPDATE]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: MultipleRecordsCommandKeys.UPDATE,
- label: msg`Update records`,
- shortLabel: msg`Update`,
- position: 14,
- Icon: IconEdit,
- accent: 'default',
- isPinned: true,
- shouldBeRegistered: ({ objectPermissions, isRemote, objectMetadataItem }) =>
- !objectMetadataItem?.isSystem &&
- objectPermissions.canUpdateObjectRecords &&
- !isRemote,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
- component: ,
- },
- [MultipleRecordsCommandKeys.MERGE]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: MultipleRecordsCommandKeys.MERGE,
- label: msg`Merge records`,
- shortLabel: msg`Merge`,
- position: 15,
- Icon: IconArrowMerge,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({
- objectMetadataItem,
- numberOfSelectedRecords,
- objectPermissions,
- }) =>
- isDefined(objectMetadataItem?.duplicateCriteria) &&
- isDefined(numberOfSelectedRecords) &&
- Boolean(objectPermissions.canUpdateObjectRecords) &&
- Boolean(objectPermissions.canDestroyObjectRecords) &&
- numberOfSelectedRecords <= MUTATION_MAX_MERGE_RECORDS,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
- component: ,
- },
- [MultipleRecordsCommandKeys.EXPORT]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: MultipleRecordsCommandKeys.EXPORT,
- label: msg`Export records`,
- shortLabel: msg`Export`,
- position: 16,
- Icon: IconFileExport,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: () => true,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION],
- component: ,
- requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
- },
- [NoSelectionRecordCommandKeys.IMPORT_RECORDS]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: NoSelectionRecordCommandKeys.IMPORT_RECORDS,
- label: msg`Import records`,
- shortLabel: msg`Import`,
- position: 17,
- Icon: IconFileImport,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({
- objectMetadataItem,
- hasAnySoftDeleteFilterOnView,
- }) => !objectMetadataItem?.isSystem && !hasAnySoftDeleteFilterOnView,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: ,
- requiredPermissionFlag: PermissionFlagType.IMPORT_CSV,
- },
- [NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: NoSelectionRecordCommandKeys.EXPORT_VIEW,
- label: msg`Export view`,
- shortLabel: msg`Export`,
- position: 18,
- Icon: IconFileExport,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: () => true,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: ,
- requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
- },
- [NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
- label: msg`See deleted records`,
- shortLabel: msg`Deleted records`,
- position: 19,
- Icon: IconRotate2,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
- !hasAnySoftDeleteFilterOnView,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: ,
- },
- [NoSelectionRecordCommandKeys.CREATE_NEW_VIEW]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: NoSelectionRecordCommandKeys.CREATE_NEW_VIEW,
- label: msg`Create View`,
- shortLabel: msg`Create View`,
- position: 20,
- Icon: IconLayout,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
- !hasAnySoftDeleteFilterOnView,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: ,
- },
- [NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
- label: msg`Hide deleted records`,
- shortLabel: msg`Hide deleted`,
- position: 21,
- Icon: IconEyeOff,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
- isDefined(hasAnySoftDeleteFilterOnView) && hasAnySoftDeleteFilterOnView,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: ,
- },
- [NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
- label: msg`Go to Workflows`,
- shortLabel: msg`See Workflows`,
- position: 22,
- Icon: IconSettingsAutomation,
- accent: 'default',
- isPinned: false,
- shouldBeRegistered: ({
- objectMetadataItem,
- viewType,
- getTargetObjectReadPermission,
- }) =>
- getTargetObjectReadPermission(CoreObjectNameSingular.Workflow) &&
- (objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Workflow ||
- viewType === CommandMenuItemViewType.SHOW_PAGE),
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.PAGE_EDIT_MODE,
- ],
- component: (
-
- ),
- hotKeys: ['G', 'W'],
- },
- [NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
- label: msg`Go to People`,
- shortLabel: msg`People`,
- position: 23,
- Icon: IconUser,
- isPinned: false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.PAGE_EDIT_MODE,
- ],
- shouldBeRegistered: ({
- objectMetadataItem,
- viewType,
- getTargetObjectReadPermission,
- }) =>
- getTargetObjectReadPermission(CoreObjectNameSingular.Person) &&
- (objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Person ||
- viewType === CommandMenuItemViewType.SHOW_PAGE),
- component: (
-
- ),
- hotKeys: ['G', 'P'],
- },
- [NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
- label: msg`Go to Companies`,
- shortLabel: msg`Companies`,
- position: 24,
- Icon: IconBuildingSkyscraper,
- isPinned: false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.PAGE_EDIT_MODE,
- ],
- shouldBeRegistered: ({
- objectMetadataItem,
- viewType,
- getTargetObjectReadPermission,
- }) =>
- getTargetObjectReadPermission(CoreObjectNameSingular.Company) &&
- (objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Company ||
- viewType === CommandMenuItemViewType.SHOW_PAGE),
- component: (
-
- ),
- hotKeys: ['G', 'C'],
- },
- [NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
- label: msg`Go to Dashboards`,
- shortLabel: msg`Dashboards`,
- position: 25,
- Icon: IconLayoutDashboard,
- isPinned: false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- shouldBeRegistered: ({
- objectMetadataItem,
- viewType,
- getTargetObjectReadPermission,
- }) =>
- getTargetObjectReadPermission(CoreObjectNameSingular.Dashboard) &&
- (objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Dashboard ||
- viewType === CommandMenuItemViewType.SHOW_PAGE),
- component: (
-
- ),
- hotKeys: ['G', 'D'],
- },
- [NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
- label: msg`Go to Opportunities`,
- shortLabel: msg`Opportunities`,
- position: 26,
- Icon: IconTargetArrow,
- isPinned: false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.PAGE_EDIT_MODE,
- ],
- shouldBeRegistered: ({
- objectMetadataItem,
- viewType,
- getTargetObjectReadPermission,
- }) =>
- getTargetObjectReadPermission(CoreObjectNameSingular.Opportunity) &&
- (objectMetadataItem?.nameSingular !==
- CoreObjectNameSingular.Opportunity ||
- viewType === CommandMenuItemViewType.SHOW_PAGE),
- component: (
-
- ),
- hotKeys: ['G', 'O'],
- },
- [NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
- label: msg`Go to Settings`,
- shortLabel: msg`Settings`,
- position: 27,
- Icon: IconSettings,
- isPinned: false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- shouldBeRegistered: () => true,
- component: (
-
- ),
- hotKeys: ['G', 'S'],
- },
- [NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_TASKS,
- label: msg`Go to Tasks`,
- shortLabel: msg`Tasks`,
- position: 28,
- Icon: IconCheckbox,
- isPinned: false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.PAGE_EDIT_MODE,
- ],
- shouldBeRegistered: ({
- objectMetadataItem,
- viewType,
- getTargetObjectReadPermission,
- }) =>
- getTargetObjectReadPermission(CoreObjectNameSingular.Task) &&
- (objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Task ||
- viewType === CommandMenuItemViewType.SHOW_PAGE),
- component: (
-
- ),
- hotKeys: ['G', 'T'],
- },
- [NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_NOTES,
- label: msg`Go to Notes`,
- shortLabel: msg`Notes`,
- position: 29,
- Icon: IconCheckbox,
- isPinned: false,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.PAGE_EDIT_MODE,
- ],
- shouldBeRegistered: ({
- objectMetadataItem,
- viewType,
- getTargetObjectReadPermission,
- }) =>
- getTargetObjectReadPermission(CoreObjectNameSingular.Note) &&
- (objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Note ||
- viewType === CommandMenuItemViewType.SHOW_PAGE),
- component: (
-
- ),
- hotKeys: ['G', 'N'],
- },
-
- [RecordPageLayoutSingleRecordCommandKeys.EDIT_RECORD_PAGE_LAYOUT]: {
- key: RecordPageLayoutSingleRecordCommandKeys.EDIT_RECORD_PAGE_LAYOUT,
- label: msg`Edit Layout`,
- shortLabel: msg`Edit Layout`,
- isPinned: false,
- position: 30,
- Icon: IconPencil,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- requiredPermissionFlag: PermissionFlagType.LAYOUTS,
- shouldBeRegistered: ({
- selectedRecord,
- objectPermissions,
- objectMetadataItem,
- isFeatureFlagEnabled,
- }) =>
- isFeatureFlagEnabled(
- FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
- ) &&
- isDefined(selectedRecord) &&
- !selectedRecord?.isRemote &&
- !isDefined(selectedRecord?.deletedAt) &&
- objectPermissions.canUpdateObjectRecords &&
- objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Dashboard,
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- },
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowCommandMenuItemsConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowCommandMenuItemsConfig.tsx
deleted file mode 100644
index 72b037f297..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowCommandMenuItemsConfig.tsx
+++ /dev/null
@@ -1,390 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { NoSelectionWorkflowRecordCommandKeys } from '@/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { ActivateWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand';
-import { AddNodeWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand';
-import { DeactivateWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand';
-import { DiscardDraftWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand';
-import { DuplicateWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand';
-import { SeeActiveVersionWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand';
-import { SeeRunsWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand';
-import { SeeVersionsWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand';
-import { TestWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/TestWorkflowSingleRecordCommand';
-import { TidyUpWorkflowSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand';
-import { WorkflowSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/workflow/types/WorkflowSingleRecordCommandKeys';
-import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { CommandMenuItemViewType, AppPath } from 'twenty-shared/types';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import {
- type WorkflowStep,
- type WorkflowTrigger,
- type WorkflowWithCurrentVersion,
-} from '@/workflow/types/Workflow';
-import { msg } from '@lingui/core/macro';
-import { isDefined } from 'twenty-shared/utils';
-import {
- IconCopy,
- IconHistoryToggle,
- IconNoteOff,
- IconPlayerPause,
- IconPlayerPlay,
- IconPlus,
- IconPower,
- IconReorder,
- IconVersions,
-} from 'twenty-ui/display';
-
-const areWorkflowTriggerAndStepsDefined = (
- workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined,
-): workflowWithCurrentVersion is WorkflowWithCurrentVersion & {
- currentVersion: {
- trigger: WorkflowTrigger;
- steps: Array;
- };
-} => {
- return (
- isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) &&
- isDefined(workflowWithCurrentVersion.currentVersion?.steps) &&
- workflowWithCurrentVersion.currentVersion.steps.length > 0
- );
-};
-
-export const WORKFLOW_COMMAND_MENU_ITEMS_CONFIG =
- inheritCommandMenuItemsFromDefaultConfig({
- config: {
- [WorkflowSingleRecordCommandKeys.ACTIVATE]: {
- key: WorkflowSingleRecordCommandKeys.ACTIVATE,
- label: msg`Activate Workflow`,
- shortLabel: msg`Activate`,
- isPinned: true,
- isPrimaryCTA: true,
- position: 3,
- Icon: IconPower,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
- (workflowWithCurrentVersion.currentVersion.status === 'DRAFT' ||
- !workflowWithCurrentVersion.versions?.some(
- (version) => version.status === 'ACTIVE',
- )) &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowSingleRecordCommandKeys.DEACTIVATE]: {
- key: WorkflowSingleRecordCommandKeys.DEACTIVATE,
- label: msg`Deactivate Workflow`,
- shortLabel: msg`Deactivate`,
- isPinned: true,
- position: 4,
- Icon: IconPlayerPause,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- isDefined(workflowWithCurrentVersion) &&
- workflowWithCurrentVersion.currentVersion.status === 'ACTIVE' &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowSingleRecordCommandKeys.DISCARD_DRAFT]: {
- key: WorkflowSingleRecordCommandKeys.DISCARD_DRAFT,
- label: msg`Discard Draft`,
- shortLabel: msg`Discard Draft`,
- isPinned: true,
- position: 5,
- Icon: IconNoteOff,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- isDefined(workflowWithCurrentVersion) &&
- workflowWithCurrentVersion.versions.length > 1 &&
- workflowWithCurrentVersion.currentVersion.status === 'DRAFT' &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowSingleRecordCommandKeys.TEST]: {
- key: WorkflowSingleRecordCommandKeys.TEST,
- label: msg`Test Workflow`,
- shortLabel: msg`Test`,
- isPinned: true,
- position: 6,
- Icon: IconPlayerPlay,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
- ((workflowWithCurrentVersion.currentVersion.trigger.type ===
- 'MANUAL' &&
- !isDefined(
- workflowWithCurrentVersion.currentVersion.trigger.settings
- .objectType,
- )) ||
- workflowWithCurrentVersion.currentVersion.trigger.type ===
- 'WEBHOOK' ||
- workflowWithCurrentVersion.currentVersion.trigger.type ===
- 'CRON') &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
-
- [WorkflowSingleRecordCommandKeys.SEE_ACTIVE_VERSION]: {
- key: WorkflowSingleRecordCommandKeys.SEE_ACTIVE_VERSION,
- label: msg`See active version`,
- shortLabel: msg`See active version`,
- isPinned: false,
- position: 7,
- Icon: IconVersions,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ workflowWithCurrentVersion, selectedRecord }) =>
- (workflowWithCurrentVersion?.statuses?.includes('ACTIVE') || false) &&
- (workflowWithCurrentVersion?.statuses?.includes('DRAFT') || false) &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowSingleRecordCommandKeys.SEE_RUNS]: {
- key: WorkflowSingleRecordCommandKeys.SEE_RUNS,
- label: msg`See runs`,
- shortLabel: msg`See runs`,
- isPinned: true,
- position: 8,
- Icon: IconHistoryToggle,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- isDefined(workflowWithCurrentVersion) &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowSingleRecordCommandKeys.SEE_VERSIONS]: {
- key: WorkflowSingleRecordCommandKeys.SEE_VERSIONS,
- label: msg`See versions history`,
- shortLabel: msg`See versions`,
- isPinned: false,
- position: 9,
- Icon: IconVersions,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- isDefined(workflowWithCurrentVersion) &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
-
- [WorkflowSingleRecordCommandKeys.ADD_NODE]: {
- key: WorkflowSingleRecordCommandKeys.ADD_NODE,
- label: msg`Add a node`,
- shortLabel: msg`Add a node`,
- isPinned: true,
- position: 10,
- Icon: IconPlus,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- },
- [WorkflowSingleRecordCommandKeys.TIDY_UP]: {
- key: WorkflowSingleRecordCommandKeys.TIDY_UP,
- label: msg`Tidy up workflow`,
- shortLabel: msg`Tidy up`,
- isPinned: false,
- position: 11,
- Icon: IconReorder,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- areWorkflowTriggerAndStepsDefined(workflowWithCurrentVersion) &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [CommandMenuItemViewType.SHOW_PAGE],
- component: ,
- },
- [WorkflowSingleRecordCommandKeys.DUPLICATE_WORKFLOW]: {
- key: WorkflowSingleRecordCommandKeys.DUPLICATE_WORKFLOW,
- label: msg`Duplicate Workflow`,
- shortLabel: msg`Duplicate`,
- isPinned: false,
- position: 12,
- Icon: IconCopy,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- shouldBeRegistered: ({ selectedRecord, workflowWithCurrentVersion }) =>
- isDefined(workflowWithCurrentVersion) &&
- isDefined(workflowWithCurrentVersion.currentVersion) &&
- !isDefined(selectedRecord?.deletedAt),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS,
- label: msg`Go to runs`,
- shortLabel: msg`See runs`,
- position: 22,
- Icon: IconHistoryToggle,
- accent: 'default',
- isPinned: true,
- shouldBeRegistered: ({ hasAnySoftDeleteFilterOnView }) =>
- !hasAnySoftDeleteFilterOnView,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: (
-
- ),
- },
- },
- commandKeys: [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
- SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
- SingleRecordCommandKeys.DELETE,
- SingleRecordCommandKeys.DESTROY,
- SingleRecordCommandKeys.RESTORE,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
- MultipleRecordsCommandKeys.DELETE,
- MultipleRecordsCommandKeys.DESTROY,
- MultipleRecordsCommandKeys.RESTORE,
- MultipleRecordsCommandKeys.EXPORT,
- NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
- NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
- NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
- NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
- NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
- NoSelectionRecordCommandKeys.GO_TO_TASKS,
- NoSelectionRecordCommandKeys.GO_TO_NOTES,
- ],
- propertiesToOverwrite: {
- [SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
- position: 0,
- label: msg`Navigate to next workflow`,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
- position: 1,
- label: msg`Navigate to previous workflow`,
- },
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- position: 2,
- label: msg`Create new workflow`,
- },
- [SingleRecordCommandKeys.DELETE]: {
- position: 12,
- label: msg`Delete workflow`,
- },
- [MultipleRecordsCommandKeys.DELETE]: {
- position: 13,
- label: msg`Delete workflows`,
- },
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- position: 14,
- isPinned: false,
- },
- [SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
- position: 15,
- isPinned: false,
- },
- [SingleRecordCommandKeys.DESTROY]: {
- position: 16,
- label: msg`Permanently destroy workflow`,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
- position: 17,
- label: msg`Export workflow`,
- shouldBeRegistered: ({ selectedRecord }) =>
- !isDefined(selectedRecord?.deletedAt),
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
- position: 18,
- label: msg`Export workflow`,
- },
- [MultipleRecordsCommandKeys.EXPORT]: {
- position: 19,
- label: msg`Export workflows`,
- },
- [NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
- position: 20,
- label: msg`Export view`,
- },
- [MultipleRecordsCommandKeys.DESTROY]: {
- position: 21,
- label: msg`Permanently destroy workflows`,
- },
- [NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
- position: 22,
- label: msg`See deleted workflows`,
- },
- [NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
- position: 23,
- label: msg`Hide deleted workflows`,
- },
- [NoSelectionRecordCommandKeys.IMPORT_RECORDS]: {
- position: 24,
- label: msg`Import workflows`,
- },
- [NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
- position: 25,
- },
- [NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
- position: 26,
- },
- [NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
- position: 27,
- },
- [NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
- position: 28,
- },
- [NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
- position: 29,
- },
- [NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
- position: 30,
- },
- [NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
- position: 31,
- },
- },
- });
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowRunsCommandMenuItemsConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowRunsCommandMenuItemsConfig.tsx
deleted file mode 100644
index 6ad315176a..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowRunsCommandMenuItemsConfig.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { SeeVersionWorkflowRunSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand';
-import { SeeWorkflowWorkflowRunSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand';
-import { StopWorkflowRunSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand';
-import { WorkflowRunSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/workflow-runs/types/WorkflowRunSingleRecordCommandKeys';
-import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { CommandMenuItemViewType } from 'twenty-shared/types';
-import { msg } from '@lingui/core/macro';
-import {
- IconPlayerStop,
- IconSettingsAutomation,
- IconVersions,
-} from 'twenty-ui/display';
-
-export const WORKFLOW_RUNS_COMMAND_MENU_ITEMS_CONFIG =
- inheritCommandMenuItemsFromDefaultConfig({
- config: {
- [WorkflowRunSingleRecordCommandKeys.SEE_VERSION]: {
- key: WorkflowRunSingleRecordCommandKeys.SEE_VERSION,
- label: msg`See version`,
- shortLabel: msg`See version`,
- position: 0,
- isPinned: true,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- Icon: IconVersions,
- shouldBeRegistered: () => true,
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
-
- [WorkflowRunSingleRecordCommandKeys.SEE_WORKFLOW]: {
- key: WorkflowRunSingleRecordCommandKeys.SEE_WORKFLOW,
- label: msg`See workflow`,
- shortLabel: msg`See workflow`,
- position: 1,
- isPinned: true,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- Icon: IconSettingsAutomation,
- shouldBeRegistered: () => true,
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowRunSingleRecordCommandKeys.STOP]: {
- key: WorkflowRunSingleRecordCommandKeys.STOP,
- label: msg`Stop`,
- shortLabel: msg`Stop`,
- position: 2,
- isPinned: true,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- Icon: IconPlayerStop,
- shouldBeRegistered: ({ selectedRecord, isSelectAll }) => {
- if (isSelectAll === true) {
- return true;
- }
-
- const stoppableStatuses = ['NOT_STARTED', 'ENQUEUED', 'RUNNING'];
- return stoppableStatuses.includes(selectedRecord?.status);
- },
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- ],
- component: ,
- },
- },
- commandKeys: [
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
- SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
- SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
- MultipleRecordsCommandKeys.EXPORT,
- NoSelectionRecordCommandKeys.EXPORT_VIEW,
- NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
- NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
- NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
- NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
- NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
- NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
- NoSelectionRecordCommandKeys.GO_TO_TASKS,
- NoSelectionRecordCommandKeys.GO_TO_NOTES,
- ],
- propertiesToOverwrite: {
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- isPinned: false,
- position: 3,
- },
- [SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
- isPinned: false,
- position: 4,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
- position: 5,
- label: msg`Export run`,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
- position: 5,
- label: msg`Export run`,
- },
- [MultipleRecordsCommandKeys.EXPORT]: {
- position: 6,
- label: msg`Export runs`,
- },
- [NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
- position: 7,
- label: msg`Export view`,
- },
- [NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
- position: 8,
- label: msg`See deleted runs`,
- },
- [NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
- position: 9,
- label: msg`Hide deleted runs`,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
- position: 10,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
- position: 11,
- },
- [NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
- position: 12,
- isPinned: true,
- },
- [NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
- position: 13,
- },
- [NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
- position: 14,
- },
- [NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
- position: 15,
- },
- [NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
- position: 16,
- },
- [NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
- position: 17,
- },
- [NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
- position: 18,
- },
- [NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
- position: 19,
- },
- },
- });
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowVersionsCommandMenuItemsConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowVersionsCommandMenuItemsConfig.tsx
deleted file mode 100644
index 00c886e849..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowVersionsCommandMenuItemsConfig.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { NoSelectionWorkflowRecordCommandKeys } from '@/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { SeeRunsWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand';
-import { SeeVersionsWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand';
-import { SeeWorkflowWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand';
-import { UseAsDraftWorkflowVersionSingleRecordCommand } from '@/command-menu-item/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand';
-import { WorkflowVersionSingleRecordCommandKeys } from '@/command-menu-item/record/single-record/workflow-versions/types/WorkflowVersionSingleRecordCommandKeys';
-import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { CommandMenuItemViewType, AppPath } from 'twenty-shared/types';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import { msg } from '@lingui/core/macro';
-import { isDefined } from 'twenty-shared/utils';
-import {
- IconHistoryToggle,
- IconPencil,
- IconSettingsAutomation,
- IconVersions,
-} from 'twenty-ui/display';
-
-export const WORKFLOW_VERSIONS_COMMAND_MENU_ITEMS_CONFIG =
- inheritCommandMenuItemsFromDefaultConfig({
- config: {
- [WorkflowVersionSingleRecordCommandKeys.SEE_RUNS]: {
- key: WorkflowVersionSingleRecordCommandKeys.SEE_RUNS,
- label: msg`See runs`,
- shortLabel: msg`See runs`,
- position: 1,
- isPinned: true,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- Icon: IconHistoryToggle,
- shouldBeRegistered: ({ workflowWithCurrentVersion }) =>
- isDefined(workflowWithCurrentVersion),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowVersionSingleRecordCommandKeys.SEE_WORKFLOW]: {
- key: WorkflowVersionSingleRecordCommandKeys.SEE_WORKFLOW,
- label: msg`See workflow`,
- shortLabel: msg`See workflow`,
- position: 2,
- isPinned: true,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- Icon: IconSettingsAutomation,
- shouldBeRegistered: ({ selectedRecord }) =>
- isDefined(selectedRecord?.workflow?.id),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowVersionSingleRecordCommandKeys.USE_AS_DRAFT]: {
- key: WorkflowVersionSingleRecordCommandKeys.USE_AS_DRAFT,
- label: msg`Use as draft`,
- shortLabel: msg`Use as draft`,
- position: 3,
- isPinned: true,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- Icon: IconPencil,
- shouldBeRegistered: ({ selectedRecord }) =>
- isDefined(selectedRecord) && selectedRecord.status !== 'DRAFT',
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [WorkflowVersionSingleRecordCommandKeys.SEE_VERSIONS]: {
- key: WorkflowVersionSingleRecordCommandKeys.SEE_VERSIONS,
- label: msg`See versions history`,
- shortLabel: msg`See versions`,
- position: 4,
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- Icon: IconVersions,
- shouldBeRegistered: ({ workflowWithCurrentVersion }) =>
- isDefined(workflowWithCurrentVersion),
- availableOn: [
- CommandMenuItemViewType.SHOW_PAGE,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ],
- component: ,
- },
- [NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionWorkflowRecordCommandKeys.GO_TO_RUNS,
- label: msg`Go to runs`,
- shortLabel: msg`See runs`,
- position: 14,
- Icon: IconHistoryToggle,
- accent: 'default',
- isPinned: true,
- shouldBeRegistered: () => true,
- availableOn: [CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION],
- component: (
-
- ),
- },
- },
- commandKeys: [
- SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
- SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
- MultipleRecordsCommandKeys.EXPORT,
- NoSelectionRecordCommandKeys.EXPORT_VIEW,
- NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
- NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
- NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
- NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
- NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
- NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
- NoSelectionRecordCommandKeys.GO_TO_TASKS,
- NoSelectionRecordCommandKeys.GO_TO_NOTES,
- ],
- propertiesToOverwrite: {
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- position: 5,
- isPinned: false,
- },
- [SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
- position: 6,
- isPinned: false,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
- position: 7,
- label: msg`Export version`,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
- position: 7,
- label: msg`Export version`,
- },
- [MultipleRecordsCommandKeys.EXPORT]: {
- position: 8,
- label: msg`Export versions`,
- },
- [NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
- position: 9,
- },
- [NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
- position: 10,
- },
- [NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
- position: 11,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
- position: 12,
- label: msg`Navigate to previous version`,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
- position: 13,
- label: msg`Navigate to next version`,
- },
- [NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
- position: 15,
- isPinned: true,
- },
- [NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
- position: 16,
- },
- [NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
- position: 17,
- },
- [NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
- position: 18,
- },
- [NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
- position: 19,
- },
- [NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
- position: 20,
- },
- [NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
- position: 21,
- },
- [NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
- position: 22,
- },
- },
- });
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkspaceMembersCommandMenuItemsConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkspaceMembersCommandMenuItemsConfig.tsx
deleted file mode 100644
index 25bc8192c7..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/constants/WorkspaceMembersCommandMenuItemsConfig.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import {
- CommandMenuItemViewType,
- AppPath,
- SettingsPath,
-} from 'twenty-shared/types';
-import { msg } from '@lingui/core/macro';
-import { IconSettings } from 'twenty-ui/display';
-
-export const WORKSPACE_MEMBERS_COMMAND_MENU_ITEMS_CONFIG =
- inheritCommandMenuItemsFromDefaultConfig({
- config: {
- [NoSelectionRecordCommandKeys.GO_TO_SETTINGS]: {
- type: CommandMenuItemType.Navigation,
- scope: CommandMenuItemScope.Global,
- key: NoSelectionRecordCommandKeys.GO_TO_SETTINGS,
- label: msg`Manage members in settings`,
- shortLabel: msg`Manage in settings`,
- position: 14,
- Icon: IconSettings,
- isPinned: true,
- availableOn: [
- CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION,
- CommandMenuItemViewType.SHOW_PAGE,
- ],
- shouldBeRegistered: () => true,
- component: (
-
- ),
- hotKeys: ['G', 'S'],
- },
- },
- commandKeys: [
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
- SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD,
- SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX,
- SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW,
- MultipleRecordsCommandKeys.EXPORT,
- NoSelectionRecordCommandKeys.EXPORT_VIEW,
- NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS,
- NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS,
- NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
- NoSelectionRecordCommandKeys.GO_TO_COMPANIES,
- NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES,
- NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS,
- NoSelectionRecordCommandKeys.GO_TO_TASKS,
- NoSelectionRecordCommandKeys.GO_TO_NOTES,
- ],
- propertiesToOverwrite: {
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- isPinned: false,
- position: 0,
- },
- [SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
- isPinned: false,
- position: 1,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_INDEX]: {
- position: 2,
- label: msg`Export member`,
- },
- [SingleRecordCommandKeys.EXPORT_FROM_RECORD_SHOW]: {
- position: 2,
- label: msg`Export member`,
- },
- [MultipleRecordsCommandKeys.EXPORT]: {
- position: 3,
- label: msg`Export members`,
- },
- [NoSelectionRecordCommandKeys.EXPORT_VIEW]: {
- position: 4,
- label: msg`Export view`,
- },
- [NoSelectionRecordCommandKeys.SEE_DELETED_RECORDS]: {
- position: 5,
- label: msg`See deleted members`,
- },
- [NoSelectionRecordCommandKeys.HIDE_DELETED_RECORDS]: {
- position: 6,
- label: msg`Hide deleted members`,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
- position: 7,
- },
- [SingleRecordCommandKeys.NAVIGATE_TO_NEXT_RECORD]: {
- position: 8,
- },
- [NoSelectionRecordCommandKeys.GO_TO_WORKFLOWS]: {
- position: 9,
- },
- [NoSelectionRecordCommandKeys.GO_TO_PEOPLE]: {
- position: 10,
- },
- [NoSelectionRecordCommandKeys.GO_TO_COMPANIES]: {
- position: 11,
- },
- [NoSelectionRecordCommandKeys.GO_TO_OPPORTUNITIES]: {
- position: 12,
- },
- [NoSelectionRecordCommandKeys.GO_TO_DASHBOARDS]: {
- position: 13,
- },
- [NoSelectionRecordCommandKeys.GO_TO_TASKS]: {
- position: 15,
- },
- [NoSelectionRecordCommandKeys.GO_TO_NOTES]: {
- position: 16,
- },
- },
- });
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx
deleted file mode 100644
index b3a4ed0c2e..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
-import { Command } from '@/command-menu-item/display/components/Command';
-import { computeProgressText } from '@/command-menu-item/utils/computeProgressText';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
-import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
-import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
-import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
-import { useIncrementalDeleteManyRecords } from '@/object-record/hooks/useIncrementalDeleteManyRecords';
-import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
-import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useContext } from 'react';
-import { isDefined } from 'twenty-shared/utils';
-
-export const DeleteMultipleRecordsCommand = () => {
- const { recordIndexId, objectMetadataItem } =
- useRecordIndexIdFromCurrentContextStore();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const contextStoreFilters = useAtomComponentStateValue(
- contextStoreFiltersComponentState,
- );
-
- const contextStoreFilterGroups = useAtomComponentStateValue(
- contextStoreFilterGroupsComponentState,
- );
-
- const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
- contextStoreAnyFieldFilterValueComponentState,
- );
-
- const { removeSelectedRecordsFromRecordBoard } =
- useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
-
- const { filterValueDependencies } = useFilterValueDependencies();
-
- const graphqlFilter = computeContextStoreFilters({
- contextStoreTargetedRecordsRule,
- contextStoreFilters,
- contextStoreFilterGroups,
- objectMetadataItem,
- filterValueDependencies,
- contextStoreAnyFieldFilterValue,
- });
-
- const { incrementalDeleteManyRecords, progress } =
- useIncrementalDeleteManyRecords({
- objectNameSingular: objectMetadataItem.nameSingular,
- filter: graphqlFilter,
- pageSize: DEFAULT_QUERY_PAGE_SIZE,
- delayInMsBetweenMutations: 50,
- });
-
- const actionConfig = useContext(CommandConfigContext);
-
- if (!isDefined(actionConfig)) {
- return null;
- }
-
- const originalLabel = getCommandMenuItemLabel(actionConfig.label);
-
- const originalShortLabel = getCommandMenuItemLabel(actionConfig.shortLabel);
-
- const progressText = computeProgressText(progress);
-
- const actionConfigWithProgress = {
- ...actionConfig,
- label: `${originalLabel}${progressText}`,
- shortLabel: `${originalShortLabel}${progressText}`,
- };
-
- const handleDeleteClick = async () => {
- removeSelectedRecordsFromRecordBoard();
- resetTableRowSelection();
- await incrementalDeleteManyRecords();
- };
-
- return (
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx
deleted file mode 100644
index 8f2d0a017f..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
-import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
-import { computeProgressText } from '@/command-menu-item/utils/computeProgressText';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
-import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
-import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
-import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
-import { useIncrementalDestroyManyRecords } from '@/object-record/hooks/useIncrementalDestroyManyRecords';
-import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
-import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { t } from '@lingui/core/macro';
-import { useContext } from 'react';
-import { type RecordGqlOperationFilter } from 'twenty-shared/types';
-import { isDefined } from 'twenty-shared/utils';
-
-export const DestroyMultipleRecordsCommand = () => {
- const { recordIndexId, objectMetadataItem } =
- useRecordIndexIdFromCurrentContextStore();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
- const { removeSelectedRecordsFromRecordBoard } =
- useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const contextStoreFilters = useAtomComponentStateValue(
- contextStoreFiltersComponentState,
- );
-
- const contextStoreFilterGroups = useAtomComponentStateValue(
- contextStoreFilterGroupsComponentState,
- );
-
- const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
- contextStoreAnyFieldFilterValueComponentState,
- );
-
- const { filterValueDependencies } = useFilterValueDependencies();
-
- const deletedAtFilter: RecordGqlOperationFilter = {
- deletedAt: { is: 'NOT_NULL' },
- };
- const graphqlFilter = {
- ...computeContextStoreFilters({
- contextStoreTargetedRecordsRule,
- contextStoreFilters,
- contextStoreFilterGroups,
- objectMetadataItem,
- filterValueDependencies,
- contextStoreAnyFieldFilterValue,
- }),
- ...deletedAtFilter,
- };
-
- const { incrementalDestroyManyRecords, progress } =
- useIncrementalDestroyManyRecords({
- objectNameSingular: objectMetadataItem.nameSingular,
- filter: graphqlFilter,
- pageSize: DEFAULT_QUERY_PAGE_SIZE,
- delayInMsBetweenMutations: 50,
- });
-
- const actionConfig = useContext(CommandConfigContext);
-
- if (!isDefined(actionConfig)) {
- return null;
- }
-
- const originalLabel = getCommandMenuItemLabel(actionConfig.label);
-
- const originalShortLabel = getCommandMenuItemLabel(actionConfig.shortLabel);
-
- const progressText = computeProgressText(progress);
-
- const actionConfigWithProgress = {
- ...actionConfig,
- label: `${originalLabel}${progressText}`,
- shortLabel: `${originalShortLabel}${progressText}`,
- };
-
- const handleDestroyClick = async () => {
- removeSelectedRecordsFromRecordBoard();
- resetTableRowSelection();
- await incrementalDestroyManyRecords();
- };
-
- return (
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/ExportMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/ExportMultipleRecordsCommand.tsx
deleted file mode 100644
index ba5b03afba..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/ExportMultipleRecordsCommand.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
-import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
-import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
-import { computeProgressText } from '@/command-menu-item/utils/computeProgressText';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { useRecordIndexExportRecords } from '@/object-record/record-index/export/hooks/useRecordIndexExportRecords';
-import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useContext } from 'react';
-import { isDefined } from 'twenty-shared/utils';
-
-export const ExportMultipleRecordsCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const { download, progress } = useRecordIndexExportRecords({
- delayMs: 100,
- objectMetadataItem,
- recordIndexId: getRecordIndexIdFromObjectNamePluralAndViewId(
- objectMetadataItem.namePlural,
- contextStoreCurrentViewId,
- ),
- filename: `${objectMetadataItem.nameSingular}.csv`,
- });
-
- const { closeCommandMenu } = useCloseCommandMenu({});
-
- const exportProgress = isDefined(progress)
- ? {
- processedRecordCount: progress.processedRecordCount,
- totalRecordCount: progress.totalRecordCount,
- }
- : undefined;
-
- const actionConfig = useContext(CommandConfigContext);
-
- if (!isDefined(actionConfig)) {
- return null;
- }
-
- const originalLabel = getCommandMenuItemLabel(actionConfig.label);
-
- const originalShortLabel = getCommandMenuItemLabel(actionConfig.shortLabel);
-
- const progressText = computeProgressText(exportProgress);
-
- const actionConfigWithProgress = {
- ...actionConfig,
- label: `${originalLabel}${progressText}`,
- shortLabel: `${originalShortLabel}${progressText}`,
- };
-
- const handleClick = async () => {
- try {
- await download();
- closeCommandMenu();
- } catch (error) {
- closeCommandMenu();
- throw error;
- }
- };
-
- return (
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/MergeMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/MergeMultipleRecordsCommand.tsx
deleted file mode 100644
index d5baf93e84..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/MergeMultipleRecordsCommand.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
-import { useSelectedRecordIds } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIds';
-import { useOpenMergeRecordsPageInSidePanel } from '@/side-panel/hooks/useOpenMergeRecordsPageInSidePanel';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-
-export const MergeMultipleRecordsCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
- const selectedRecordIds = useSelectedRecordIds();
-
- const { openMergeRecordsPageInSidePanel } =
- useOpenMergeRecordsPageInSidePanel({
- objectNameSingular: objectMetadataItem.nameSingular,
- objectRecordIds: selectedRecordIds,
- });
-
- const handleClick = () => {
- openMergeRecordsPageInSidePanel();
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx
deleted file mode 100644
index 1905208920..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
-import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
-import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
-import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
-import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
-import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
-import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
-import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { t } from '@lingui/core/macro';
-import { type RecordGqlOperationFilter } from 'twenty-shared/types';
-
-export const RestoreMultipleRecordsCommand = () => {
- const { recordIndexId, objectMetadataItem } =
- useRecordIndexIdFromCurrentContextStore();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
- const { removeSelectedRecordsFromRecordBoard } =
- useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
-
- const { restoreManyRecords } = useRestoreManyRecords({
- objectNameSingular: objectMetadataItem.nameSingular,
- });
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const contextStoreFilters = useAtomComponentStateValue(
- contextStoreFiltersComponentState,
- );
-
- const contextStoreFilterGroups = useAtomComponentStateValue(
- contextStoreFilterGroupsComponentState,
- );
-
- const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
- contextStoreAnyFieldFilterValueComponentState,
- );
-
- const { filterValueDependencies } = useFilterValueDependencies();
-
- const deletedAtFilter: RecordGqlOperationFilter = {
- deletedAt: { is: 'NOT_NULL' },
- };
-
- const graphqlFilter = {
- ...computeContextStoreFilters({
- contextStoreTargetedRecordsRule,
- contextStoreFilters,
- contextStoreFilterGroups,
- objectMetadataItem,
- filterValueDependencies,
- contextStoreAnyFieldFilterValue,
- }),
- ...deletedAtFilter,
- };
-
- const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
- objectNameSingular: objectMetadataItem.nameSingular,
- filter: graphqlFilter,
- limit: DEFAULT_QUERY_PAGE_SIZE,
- recordGqlFields: { id: true },
- });
-
- const handleRestoreClick = async () => {
- removeSelectedRecordsFromRecordBoard();
- const recordsToRestore = await fetchAllRecordIds();
- const recordIdsToRestore = recordsToRestore.map((record) => record.id);
-
- resetTableRowSelection();
-
- await restoreManyRecords({
- idsToRestore: recordIdsToRestore,
- });
- };
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx
deleted file mode 100644
index 2114c1653f..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
-import { useOpenUpdateMultipleRecordsPageInSidePanel } from '@/side-panel/hooks/useOpenUpdateMultipleRecordsPageInSidePanel';
-import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
-import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
-
-export const UpdateMultipleRecordsCommand = () => {
- const contextStoreInstanceId = useAvailableComponentInstanceIdOrThrow(
- ContextStoreComponentInstanceContext,
- );
-
- const { openUpdateMultipleRecordsPageInSidePanel } =
- useOpenUpdateMultipleRecordsPageInSidePanel({
- contextStoreInstanceId,
- });
-
- const handleClick = () => {
- openUpdateMultipleRecordsPageInSidePanel();
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys.ts
deleted file mode 100644
index bd60395b84..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export enum MultipleRecordsCommandKeys {
- UPDATE = 'update-multiple-records',
- DELETE = 'delete-multiple-records',
- EXPORT = 'export-multiple-records',
- MERGE = 'merge-multiple-records',
- DESTROY = 'destroy-multiple-records',
- RESTORE = 'restore-multiple-records',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx
deleted file mode 100644
index 855bbbedba..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
-
-export const CreateNewIndexRecordNoSelectionRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const { createNewIndexRecord } = useCreateNewIndexRecord({
- objectMetadataItem,
- });
-
- return (
- createNewIndexRecord({ position: 'first' })}
- closeSidePanelOnCommandMenuListExecution={false}
- />
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx
deleted file mode 100644
index 3e66886c2c..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
-import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
-import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
-import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
-import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
-
-export const CreateNewViewNoSelectionRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
- const { openDropdown } = useOpenDropdown();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
- objectMetadataItem.namePlural,
- contextStoreCurrentViewId,
- );
-
- const setViewPickerReferenceViewId = useSetAtomComponentState(
- viewPickerReferenceViewIdComponentState,
- recordIndexId,
- );
-
- const { setViewPickerMode } = useViewPickerMode(recordIndexId);
-
- const handleAddViewButtonClick = () => {
- setViewPickerReferenceViewId(contextStoreCurrentViewId);
- setViewPickerMode('create-empty');
- openDropdown({
- dropdownComponentInstanceIdFromProps: VIEW_PICKER_DROPDOWN_ID,
- });
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/EditNavigationSidebarNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/EditNavigationSidebarNoSelectionRecordCommand.tsx
deleted file mode 100644
index 023fb65d09..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/EditNavigationSidebarNoSelectionRecordCommand.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/useEnterLayoutCustomizationMode';
-import { Command } from '@/command-menu-item/display/components/Command';
-
-export const EditNavigationSidebarNoSelectionRecordCommand = () => {
- const { enterLayoutCustomizationMode } = useEnterLayoutCustomizationMode();
-
- return (
- enterLayoutCustomizationMode()}
- closeSidePanelOnCommandMenuListExecution
- />
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx
deleted file mode 100644
index 32056cd8be..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
-import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
-import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
-import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
-import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { isDefined } from 'twenty-shared/utils';
-
-export const HideDeletedRecordsNoSelectionRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
- objectMetadataItem.namePlural,
- contextStoreCurrentViewId,
- );
-
- const { toggleSoftDeleteFilterState } = useHandleToggleTrashColumnFilter({
- objectNameSingular: objectMetadataItem.nameSingular,
- viewBarId: recordIndexId,
- });
-
- const { isRecordFilterAboutSoftDelete } = useCheckIsSoftDeleteFilter();
-
- const currentRecordFilters = useAtomComponentStateValue(
- currentRecordFiltersComponentState,
- recordIndexId,
- );
-
- const deletedFilter = currentRecordFilters.find(
- isRecordFilterAboutSoftDelete,
- );
-
- const { removeRecordFilter } = useRemoveRecordFilter();
-
- const handleClick = () => {
- if (!isDefined(deletedFilter)) {
- return;
- }
-
- removeRecordFilter({ recordFilterId: deletedFilter.id });
- toggleSoftDeleteFilterState(false);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx
deleted file mode 100644
index 9005584ff4..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { useOpenObjectRecordsSpreadsheetImportDialog } from '@/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog';
-
-export const ImportRecordsNoSelectionRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const { openObjectRecordsSpreadsheetImportDialog } =
- useOpenObjectRecordsSpreadsheetImportDialog(
- objectMetadataItem.nameSingular,
- );
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx
deleted file mode 100644
index 8603d73cc6..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
-import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-
-export const SeeDeletedRecordsNoSelectionRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
- objectMetadataItem.namePlural,
- contextStoreCurrentViewId,
- );
-
- const { handleToggleTrashColumnFilter, toggleSoftDeleteFilterState } =
- useHandleToggleTrashColumnFilter({
- objectNameSingular: objectMetadataItem.nameSingular,
- viewBarId: recordIndexId,
- });
-
- return (
- {
- handleToggleTrashColumnFilter();
- toggleSoftDeleteFilterState(true);
- }}
- />
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys.ts
deleted file mode 100644
index 3a0f4e8975..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export enum NoSelectionRecordCommandKeys {
- EXPORT_VIEW = 'export-view',
- CREATE_NEW_RECORD = 'create-new-record',
- SEE_DELETED_RECORDS = 'see-deleted-records',
- HIDE_DELETED_RECORDS = 'hide-deleted-records',
- IMPORT_RECORDS = 'import-records',
- GO_TO_WORKFLOWS = 'go-to-workflows',
- GO_TO_PEOPLE = 'go-to-people',
- GO_TO_COMPANIES = 'go-to-companies',
- GO_TO_DASHBOARDS = 'go-to-dashboards',
- GO_TO_OPPORTUNITIES = 'go-to-opportunities',
- GO_TO_SETTINGS = 'go-to-settings',
- GO_TO_TASKS = 'go-to-tasks',
- GO_TO_NOTES = 'go-to-notes',
- CREATE_NEW_VIEW = 'create-view',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys.ts
deleted file mode 100644
index 2f8d1af84a..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export enum NoSelectionWorkflowRecordCommandKeys {
- GO_TO_RUNS = 'go-to-runs',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx
deleted file mode 100644
index 1ced342b23..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { NavigationMenuItemType } from 'twenty-shared/types';
-import { isDefined } from 'twenty-shared/utils';
-import { v4 as uuidv4 } from 'uuid';
-
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { useCreateManyNavigationMenuItems } from '@/navigation-menu-item/common/hooks/useCreateManyNavigationMenuItems';
-import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
-
-export const AddToFavoritesSingleRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const { createManyNavigationMenuItems } = useCreateManyNavigationMenuItems();
- const { navigationMenuItems, currentWorkspaceMemberId } =
- useNavigationMenuItemsData();
-
- const handleClick = () => {
- const relevantItems = navigationMenuItems.filter(
- (item) => !isDefined(item.folderId) && isDefined(item.userWorkspaceId),
- );
-
- const maxPosition = Math.max(
- ...relevantItems.map((item) => item.position),
- 0,
- );
-
- createManyNavigationMenuItems([
- {
- id: uuidv4(),
- type: NavigationMenuItemType.RECORD,
- targetRecordId: recordId,
- targetObjectMetadataId: objectMetadataItem.id,
- userWorkspaceId: currentWorkspaceMemberId,
- position: maxPosition + 1,
- },
- ]);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/CreateRelatedRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/CreateRelatedRecordCommand.tsx
deleted file mode 100644
index ceef716f06..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/CreateRelatedRecordCommand.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
-import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-import { CoreObjectNameSingular } from 'twenty-shared/types';
-import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
-import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
-import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
-import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
-
-interface CreateRelatedRecordCommandProps {
- targetFieldMetadataItemRelation: FieldMetadataItemRelation;
-}
-
-export const CreateRelatedRecordCommand = ({
- targetFieldMetadataItemRelation,
-}: CreateRelatedRecordCommandProps) => {
- const sourceRecordId = useSelectedRecordIdOrThrow();
-
- const { objectMetadataItem: targetObjectMetadataItem } =
- useObjectMetadataItem({
- objectNameSingular:
- targetFieldMetadataItemRelation.targetObjectMetadata.nameSingular,
- });
-
- const { objectMetadataItem: taskObjectMetadataItem } = useObjectMetadataItem({
- objectNameSingular: CoreObjectNameSingular.Task,
- });
-
- const { objectMetadataItem: noteObjectMetadataItem } = useObjectMetadataItem({
- objectNameSingular: CoreObjectNameSingular.Note,
- });
-
- const { openRecordInSidePanel } = useOpenRecordInSidePanel();
-
- const { createOneRecord: createOneTaskTarget } = useCreateOneRecord({
- objectNameSingular: CoreObjectNameSingular.TaskTarget,
- });
-
- const { createOneRecord: createOneNoteTarget } = useCreateOneRecord({
- objectNameSingular: CoreObjectNameSingular.NoteTarget,
- });
-
- const targetObject =
- targetObjectMetadataItem.nameSingular === CoreObjectNameSingular.TaskTarget
- ? taskObjectMetadataItem
- : targetObjectMetadataItem.nameSingular ===
- CoreObjectNameSingular.NoteTarget
- ? noteObjectMetadataItem
- : targetObjectMetadataItem;
-
- const { createOneRecord } = useCreateOneRecord({
- objectNameSingular: targetObject.nameSingular,
- });
-
- const handleCreateRelatedRecord = async () => {
- const foreignKeyFieldName =
- targetFieldMetadataItemRelation.targetFieldMetadata.name;
- const foreignKeyIdFieldName =
- getForeignKeyNameFromRelationFieldName(foreignKeyFieldName);
-
- let createdRecord: ObjectRecord;
-
- switch (targetObjectMetadataItem.nameSingular) {
- case CoreObjectNameSingular.TaskTarget: {
- createdRecord = await createOneRecord({});
-
- await createOneTaskTarget({
- taskId: createdRecord.id,
- [foreignKeyIdFieldName]: sourceRecordId,
- });
- break;
- }
- case CoreObjectNameSingular.NoteTarget: {
- createdRecord = await createOneRecord({});
-
- await createOneNoteTarget({
- noteId: createdRecord.id,
- [foreignKeyIdFieldName]: sourceRecordId,
- });
- break;
- }
- default:
- createdRecord = await createOneRecord({
- [foreignKeyIdFieldName]: sourceRecordId,
- });
- break;
- }
-
- openRecordInSidePanel({
- recordId: createdRecord.id,
- objectNameSingular: targetObject.nameSingular,
- isNewRecord: true,
- });
- };
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DeleteSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DeleteSingleRecordCommand.tsx
deleted file mode 100644
index ce83d78318..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DeleteSingleRecordCommand.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
-import { useRemoveNavigationMenuItemByTargetRecordId } from '@/navigation-menu-item/common/hooks/useRemoveNavigationMenuItemByTargetRecordId';
-import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
-import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
-import { isDefined } from 'twenty-shared/utils';
-
-export const DeleteSingleRecordCommand = () => {
- const { recordIndexId, objectMetadataItem } =
- useRecordIndexIdFromCurrentContextStore();
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
-
- const { removeSelectedRecordsFromRecordBoard } =
- useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
-
- const { deleteOneRecord } = useDeleteOneRecord({
- objectNameSingular: objectMetadataItem.nameSingular,
- });
-
- const { navigationMenuItems, workspaceNavigationMenuItems } =
- useNavigationMenuItemsData();
- const { removeNavigationMenuItemsByTargetRecordIds } =
- useRemoveNavigationMenuItemByTargetRecordId();
-
- const handleDeleteClick = async () => {
- removeSelectedRecordsFromRecordBoard();
-
- resetTableRowSelection();
-
- const foundNavigationMenuItem = [
- ...navigationMenuItems,
- ...workspaceNavigationMenuItems,
- ].find((item) => item.targetRecordId === recordId);
-
- if (isDefined(foundNavigationMenuItem)) {
- removeNavigationMenuItemsByTargetRecordIds([recordId]);
- }
-
- await deleteOneRecord(recordId);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DestroySingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DestroySingleRecordCommand.tsx
deleted file mode 100644
index 1ca7dd63bb..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DestroySingleRecordCommand.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
-import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
-import { t } from '@lingui/core/macro';
-import { AppPath } from 'twenty-shared/types';
-import { useNavigateApp } from '~/hooks/useNavigateApp';
-
-export const DestroySingleRecordCommand = () => {
- const { recordIndexId, objectMetadataItem } =
- useRecordIndexIdFromCurrentContextStore();
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const navigateApp = useNavigateApp();
-
- const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
-
- const { removeSelectedRecordsFromRecordBoard } =
- useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
-
- const { destroyOneRecord } = useDestroyOneRecord({
- objectNameSingular: objectMetadataItem.nameSingular,
- });
-
- const handleDeleteClick = async () => {
- removeSelectedRecordsFromRecordBoard();
- resetTableRowSelection();
-
- await destroyOneRecord(recordId);
- navigateApp(AppPath.RecordIndexPage, {
- objectNamePlural: objectMetadataItem.namePlural,
- });
- };
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportNoteSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportNoteSingleRecordCommand.tsx
deleted file mode 100644
index b2104d25ff..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportNoteSingleRecordCommand.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { isDefined } from 'twenty-shared/utils';
-
-export const ExportNoteSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
-
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- const filename = `${(recordStore?.title || 'Untitled Note').replace(/[<>:"/\\|?*]/g, '-')}`;
-
- const handleClick = async () => {
- if (!isDefined(recordStore)) {
- return;
- }
-
- const initialBody = recordStore.bodyV2?.blocknote;
-
- let parsedBody = [];
-
- // TODO: Remove this once we have removed the old rich text
- try {
- parsedBody = JSON.parse(initialBody);
- } catch {
- // oxlint-disable-next-line no-console
- console.warn(
- `Failed to parse body for record ${recordId}, for rich text version 'v2'`,
- );
- // oxlint-disable-next-line no-console
- console.warn(initialBody);
- }
-
- const { exportBlockNoteEditorToPdf } = await import(
- '@/command-menu-item/record/single-record/utils/exportBlockNoteEditorToPdf'
- );
-
- await exportBlockNoteEditorToPdf(parsedBody, filename);
-
- // TODO later: implement DOCX export
- // const { exportBlockNoteEditorToDocx } = await import(
- // '@/command-menu-item/record/single-record/utils/exportBlockNoteEditorToDocx'
- // );
- // await exportBlockNoteEditorToDocx(editor, filename);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportSingleRecordCommand.tsx
deleted file mode 100644
index 379066b68b..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportSingleRecordCommand.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { useExportSingleRecord } from '@/object-record/record-show/hooks/useExportSingleRecord';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-
-import { Command } from '@/command-menu-item/display/components/Command';
-
-export const ExportSingleRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const contextStoreCurrentViewId = useAtomComponentStateValue(
- contextStoreCurrentViewIdComponentState,
- );
-
- if (!contextStoreCurrentViewId) {
- throw new Error('Current view ID is not defined');
- }
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const filename = `${objectMetadataItem.nameSingular}.csv`;
- const { download } = useExportSingleRecord({
- filename,
- objectMetadataItem,
- recordId,
- });
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx
deleted file mode 100644
index 682e00a437..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
-
-export const NavigateToNextRecordSingleRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const { navigateToNextRecord } = useRecordShowPagePagination(
- objectMetadataItem.nameSingular,
- recordId,
- );
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx
deleted file mode 100644
index 15b7e7d0f9..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
-
-export const NavigateToPreviousRecordSingleRecordCommand = () => {
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const { navigateToPreviousRecord } = useRecordShowPagePagination(
- objectMetadataItem.nameSingular,
- recordId,
- );
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx
deleted file mode 100644
index 479386c344..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { isDefined } from 'twenty-shared/utils';
-
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
-import { useDeleteManyNavigationMenuItems } from '@/navigation-menu-item/common/hooks/useDeleteManyNavigationMenuItems';
-import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
-
-export const RemoveFromFavoritesSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
-
- const { navigationMenuItems, workspaceNavigationMenuItems } =
- useNavigationMenuItemsData();
-
- const { deleteManyNavigationMenuItems } = useDeleteManyNavigationMenuItems();
-
- const foundNavigationMenuItem = [
- ...navigationMenuItems,
- ...workspaceNavigationMenuItems,
- ].find(
- (item) =>
- item.targetRecordId === recordId &&
- item.targetObjectMetadataId === objectMetadataItem.id,
- );
-
- const handleClick = () => {
- if (!isDefined(foundNavigationMenuItem)) {
- return;
- }
-
- deleteManyNavigationMenuItems([foundNavigationMenuItem.id]);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RestoreSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RestoreSingleRecordCommand.tsx
deleted file mode 100644
index c8ff9eea86..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RestoreSingleRecordCommand.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { CommandModal } from '@/command-menu-item/display/components/CommandModal';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
-import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
-import { t } from '@lingui/core/macro';
-
-export const RestoreSingleRecordCommand = () => {
- const { recordIndexId, objectMetadataItem } =
- useRecordIndexIdFromCurrentContextStore();
-
- const recordId = useSelectedRecordIdOrThrow();
-
- const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
- const { removeSelectedRecordsFromRecordBoard } =
- useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
-
- const { restoreManyRecords } = useRestoreManyRecords({
- objectNameSingular: objectMetadataItem.nameSingular,
- });
-
- const handleRestoreClick = async () => {
- removeSelectedRecordsFromRecordBoard();
- resetTableRowSelection();
-
- await restoreManyRecords({
- idsToRestore: [recordId],
- });
- };
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx
deleted file mode 100644
index 0b5aa0e957..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout';
-import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
-import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
-import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { isDefined } from 'twenty-shared/utils';
-import { PageLayoutType } from '~/generated-metadata/graphql';
-
-export const CancelDashboardSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
-
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- const pageLayoutId = recordStore?.pageLayoutId;
-
- if (!isDefined(pageLayoutId)) {
- throw new Error(
- 'CancelDashboardSingleRecordCommand requires a valid pageLayoutId from the record store.',
- );
- }
-
- const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({
- pageLayoutId,
- layoutType: PageLayoutType.DASHBOARD,
- targetRecordIdentifier: { id: recordId, targetObjectNameSingular: '' },
- });
-
- const { closeSidePanelMenu } = useSidePanelMenu();
-
- const { setIsPageLayoutInEditMode } =
- useSetIsPageLayoutInEditMode(pageLayoutId);
-
- const { resetDraftPageLayoutToPersistedPageLayout } =
- useResetDraftPageLayoutToPersistedPageLayout({
- pageLayoutId,
- tabListInstanceId,
- });
-
- const handleClick = () => {
- closeSidePanelMenu();
-
- resetDraftPageLayoutToPersistedPageLayout();
- setIsPageLayoutInEditMode(false);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx
deleted file mode 100644
index bd9f264385..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useDuplicateDashboard } from '@/dashboards/hooks/useDuplicateDashboard';
-import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
-import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
-import { useLingui } from '@lingui/react/macro';
-import { isNonEmptyString } from '@sniptt/guards';
-import { isDefined } from 'twenty-shared/utils';
-import { useNavigateApp } from '~/hooks/useNavigateApp';
-
-export const DuplicateDashboardSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const { duplicateDashboard } = useDuplicateDashboard();
- const navigate = useNavigateApp();
- const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
- const { t } = useLingui();
-
- const handleClick = async () => {
- const result = await duplicateDashboard(recordId);
-
- if (isDefined(result) && isNonEmptyString(result.id)) {
- enqueueSuccessSnackBar({
- message: t`Dashboard duplicated successfully`,
- });
- navigate(AppPath.RecordShowPage, {
- objectNameSingular: CoreObjectNameSingular.Dashboard,
- objectRecordId: result.id,
- });
- } else {
- enqueueErrorSnackBar({
- message: t`Failed to duplicate dashboard`,
- });
- }
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx
deleted file mode 100644
index 2b4f332985..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { useResetLocationHash } from 'twenty-ui/utilities';
-
-export const EditDashboardSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
-
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- const pageLayoutId = recordStore?.pageLayoutId;
-
- const { setIsPageLayoutInEditMode } =
- useSetIsPageLayoutInEditMode(pageLayoutId);
-
- const { resetLocationHash } = useResetLocationHash();
-
- const handleClick = () => {
- setIsPageLayoutInEditMode(true);
- resetLocationHash();
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx
deleted file mode 100644
index 5cb840e64c..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout';
-import { useSavePageLayoutWidgetsData } from '@/page-layout/hooks/useSavePageLayoutWidgetsData';
-import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-
-export const SaveDashboardSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
-
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- const pageLayoutId = recordStore?.pageLayoutId;
-
- const { savePageLayout } = useSavePageLayout(pageLayoutId);
-
- const { savePageLayoutWidgetsData } = useSavePageLayoutWidgetsData();
-
- const { setIsPageLayoutInEditMode } =
- useSetIsPageLayoutInEditMode(pageLayoutId);
-
- const { closeSidePanelMenu } = useSidePanelMenu();
-
- const handleClick = async () => {
- const result = await savePageLayout();
-
- if (result.status === 'successful') {
- await savePageLayoutWidgetsData(pageLayoutId);
- closeSidePanelMenu();
- setIsPageLayoutInEditMode(false);
- }
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/types/DashboardSingleRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/types/DashboardSingleRecordCommandKeys.ts
deleted file mode 100644
index 10a298ecf8..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/types/DashboardSingleRecordCommandKeys.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export enum DashboardSingleRecordCommandKeys {
- DUPLICATE_DASHBOARD = 'duplicate-dashboard-single-record',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow.tsx
deleted file mode 100644
index 2b01308dc0..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-
-export const useSelectedRecordIdOrThrow = () => {
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- if (
- contextStoreTargetedRecordsRule.mode === 'exclusion' ||
- (contextStoreTargetedRecordsRule.mode === 'selection' &&
- contextStoreTargetedRecordsRule.selectedRecordIds.length === 0)
- ) {
- throw new Error('Selected record ID is required');
- }
-
- return contextStoreTargetedRecordsRule.selectedRecordIds[0];
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIds.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIds.tsx
deleted file mode 100644
index 0d57e7a3bf..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIds.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-
-export const useSelectedRecordIds = () => {
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- if (
- contextStoreTargetedRecordsRule.mode === 'exclusion' ||
- (contextStoreTargetedRecordsRule.mode === 'selection' &&
- contextStoreTargetedRecordsRule.selectedRecordIds.length === 0)
- ) {
- return [];
- }
-
- return contextStoreTargetedRecordsRule.selectedRecordIds;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx
deleted file mode 100644
index 11861b1af5..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/useEnterLayoutCustomizationMode';
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useResetLocationHash } from 'twenty-ui/utilities';
-
-export const EditRecordPageLayoutSingleRecordCommand = () => {
- const { enterLayoutCustomizationMode } = useEnterLayoutCustomizationMode();
-
- const { resetLocationHash } = useResetLocationHash();
-
- const handleClick = () => {
- enterLayoutCustomizationMode();
- resetLocationHash();
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/types/RecordPageLayoutSingleRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/types/RecordPageLayoutSingleRecordCommandKeys.ts
deleted file mode 100644
index eadab1d383..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/types/RecordPageLayoutSingleRecordCommandKeys.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export enum RecordPageLayoutSingleRecordCommandKeys {
- EDIT_RECORD_PAGE_LAYOUT = 'edit-record-page-layout-single-record',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/types/SingleRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/single-record/types/SingleRecordCommandKeys.ts
deleted file mode 100644
index c8ab553602..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/types/SingleRecordCommandKeys.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export enum SingleRecordCommandKeys {
- DELETE = 'delete-single-record',
- DESTROY = 'destroy-single-record',
- ADD_TO_FAVORITES = 'add-to-favorites-single-record',
- REMOVE_FROM_FAVORITES = 'remove-from-favorites-single-record',
- NAVIGATE_TO_NEXT_RECORD = 'navigate-to-next-record-single-record',
- NAVIGATE_TO_PREVIOUS_RECORD = 'navigate-to-previous-record-single-record',
- EXPORT_NOTE_TO_PDF = 'export-note-to-pdf-single-record',
- EXPORT_FROM_RECORD_INDEX = 'export-from-record-index-single-record',
- EXPORT_FROM_RECORD_SHOW = 'export-from-record-show-single-record',
- RESTORE = 'restore-single-record',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/utils/exportBlockNoteEditorToDocx.ts b/packages/twenty-front/src/modules/command-menu-item/record/single-record/utils/exportBlockNoteEditorToDocx.ts
deleted file mode 100644
index 2b2308c04c..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/utils/exportBlockNoteEditorToDocx.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { type BlockNoteEditor } from '@blocknote/core';
-import {
- docxDefaultSchemaMappings,
- DOCXExporter,
-} from '@blocknote/xl-docx-exporter';
-import { saveAs } from 'file-saver';
-
-export const exportBlockNoteEditorToDocx = async (
- editor: BlockNoteEditor,
- filename: string,
-) => {
- const exporter = new DOCXExporter(editor.schema, docxDefaultSchemaMappings);
-
- const blob = await exporter.toBlob(editor.document);
- saveAs(blob, `${filename}.docx`);
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx
deleted file mode 100644
index 61ec3bfc86..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { isDefined } from 'twenty-shared/utils';
-
-export const SeeVersionWorkflowRunSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- if (!isDefined(recordStore) || !isDefined(recordStore?.workflowVersion?.id)) {
- return null;
- }
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx
deleted file mode 100644
index 7ee2e321c3..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { isDefined } from 'twenty-shared/utils';
-
-export const SeeWorkflowWorkflowRunSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- if (!isDefined(recordStore) || !isDefined(recordStore?.workflow?.id)) {
- return null;
- }
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx
deleted file mode 100644
index 058d32098f..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
-import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
-import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
-import { CoreObjectNameSingular } from 'twenty-shared/types';
-import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
-import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
-import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
-import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useStopWorkflowRun } from '@/workflow/hooks/useStopWorkflowRun';
-
-export const StopWorkflowRunSingleRecordCommand = () => {
- const { objectMetadataItem } = useRecordIndexIdFromCurrentContextStore();
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const contextStoreFilters = useAtomComponentStateValue(
- contextStoreFiltersComponentState,
- );
-
- const contextStoreFilterGroups = useAtomComponentStateValue(
- contextStoreFilterGroupsComponentState,
- );
-
- const contextStoreAnyFieldFilterValue = useAtomComponentStateValue(
- contextStoreAnyFieldFilterValueComponentState,
- );
-
- const { filterValueDependencies } = useFilterValueDependencies();
-
- const graphqlFilter = computeContextStoreFilters({
- contextStoreTargetedRecordsRule,
- contextStoreFilters,
- contextStoreFilterGroups,
- objectMetadataItem,
- filterValueDependencies,
- contextStoreAnyFieldFilterValue,
- });
-
- const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
- objectNameSingular: CoreObjectNameSingular.WorkflowRun,
- filter: graphqlFilter,
- limit: DEFAULT_QUERY_PAGE_SIZE,
- recordGqlFields: { id: true },
- });
-
- const { stopWorkflowRun } = useStopWorkflowRun();
-
- const handleClick = async () => {
- if (contextStoreTargetedRecordsRule.mode === 'selection') {
- for (const selectedRecordId of contextStoreTargetedRecordsRule.selectedRecordIds) {
- await stopWorkflowRun(selectedRecordId);
- }
- } else {
- const records = await fetchAllRecordIds();
-
- for (const record of records) {
- await stopWorkflowRun(record.id);
- }
- }
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/types/WorkflowRunSingleRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/types/WorkflowRunSingleRecordCommandKeys.ts
deleted file mode 100644
index 8d3654eddf..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/types/WorkflowRunSingleRecordCommandKeys.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export enum WorkflowRunSingleRecordCommandKeys {
- STOP = 'stop-workflow-run-single-record',
- SEE_WORKFLOW = 'see-workflow-workflow-run-single-record',
- SEE_VERSION = 'see-version-workflow-run-single-record',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx
deleted file mode 100644
index adc6271486..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
-import { isDefined } from 'twenty-shared/utils';
-
-const SeeRunsWorkflowVersionSingleRecordCommandContent = ({
- workflowId,
- recordId,
-}: {
- workflowId: string;
- recordId: string;
-}) => {
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
-
- return (
-
- );
-};
-
-export const SeeRunsWorkflowVersionSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- const workflowId = recordStore?.workflow?.id;
-
- if (!isDefined(workflowId)) {
- return null;
- }
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx
deleted file mode 100644
index cb2d6a6a23..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
-import { isDefined } from 'twenty-shared/utils';
-
-const SeeVersionsWorkflowVersionSingleRecordCommandContent = ({
- workflowId,
-}: {
- workflowId: string;
-}) => {
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
-
- return (
-
- );
-};
-
-export const SeeVersionsWorkflowVersionSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- if (!isDefined(recordStore) || !isDefined(recordStore.workflowId)) {
- return null;
- }
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx
deleted file mode 100644
index 24cb2699d7..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
-
-export const SeeWorkflowWorkflowVersionSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx
deleted file mode 100644
index 161258ffc7..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
-import { useModal } from '@/ui/layout/modal/hooks/useModal';
-import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal';
-import { OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID } from '@/workflow/constants/OverrideWorkflowDraftConfirmationModalId';
-import { useCreateDraftFromWorkflowVersion } from '@/workflow/hooks/useCreateDraftFromWorkflowVersion';
-import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { useState } from 'react';
-import { isDefined } from 'twenty-shared/utils';
-import { useNavigateApp } from '~/hooks/useNavigateApp';
-
-const UseAsDraftWorkflowVersionSingleRecordCommandContent = ({
- workflowId,
- workflowVersionId,
-}: {
- workflowId: string;
- workflowVersionId: string;
-}) => {
- const { openModal } = useModal();
- const workflow = useWorkflowWithCurrentVersion(workflowId);
- const { createDraftFromWorkflowVersion } =
- useCreateDraftFromWorkflowVersion();
- const navigate = useNavigateApp();
- const [hasNavigated, setHasNavigated] = useState(false);
-
- const hasAlreadyDraftVersion =
- workflow?.versions.some((version) => version.status === 'DRAFT') || false;
-
- const handleClick = () => {
- if (!isDefined(workflow) || hasNavigated) {
- return;
- }
-
- if (hasAlreadyDraftVersion) {
- openModal(OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID);
- } else {
- const executeCommandWithoutWaiting = async () => {
- await createDraftFromWorkflowVersion({
- workflowId,
- workflowVersionIdToCopy: workflowVersionId,
- });
-
- navigate(AppPath.RecordShowPage, {
- objectNameSingular: CoreObjectNameSingular.Workflow,
- objectRecordId: workflowId,
- });
-
- setHasNavigated(true);
- };
-
- executeCommandWithoutWaiting();
- }
- };
-
- return (
- <>
-
-
- >
- );
-};
-
-export const UseAsDraftWorkflowVersionSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const workflowVersion = useWorkflowVersion(recordId);
-
- if (!isDefined(workflowVersion?.workflow?.id)) {
- return null;
- }
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/types/WorkflowVersionSingleRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/types/WorkflowVersionSingleRecordCommandKeys.ts
deleted file mode 100644
index 1001095a7a..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/types/WorkflowVersionSingleRecordCommandKeys.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum WorkflowVersionSingleRecordCommandKeys {
- SEE_RUNS = 'see-runs-workflow-version-single-record',
- SEE_VERSIONS = 'see-versions-workflow-version-single-record',
- USE_AS_DRAFT = 'use-as-draft-workflow-version-single-record',
- SEE_WORKFLOW = 'see-workflow-workflow-version-single-record',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 772c551f57..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { isDefined } from 'twenty-shared/utils';
-
-export const ActivateWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const { activateWorkflowVersion } = useActivateWorkflowVersion();
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
-
- const onClick = () => {
- if (!isDefined(workflowWithCurrentVersion)) {
- return;
- }
-
- activateWorkflowVersion({
- workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
- workflowId: workflowWithCurrentVersion.id,
- });
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 07b06d0314..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useSidePanelWorkflowNavigation } from '@/side-panel/pages/workflow/hooks/useSidePanelWorkflowNavigation';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { isDefined } from 'twenty-shared/utils';
-
-export const AddNodeWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
- const { openWorkflowCreateStepInSidePanel } =
- useSidePanelWorkflowNavigation();
-
- const onClick = () => {
- if (!isDefined(workflowWithCurrentVersion)) {
- return;
- }
-
- openWorkflowCreateStepInSidePanel(workflowWithCurrentVersion.id);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 54ac6267ba..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { isDefined } from 'twenty-shared/utils';
-
-export const DeactivateWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion();
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
-
- const onClick = () => {
- if (!isDefined(workflowWithCurrentVersion)) {
- return;
- }
-
- deactivateWorkflowVersion({
- workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
- });
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 2484b6aafa..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { isDefined } from 'twenty-shared/utils';
-
-export const DiscardDraftWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion();
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
-
- const onClick = () => {
- if (!isDefined(workflowWithCurrentVersion)) {
- return;
- }
-
- deleteOneWorkflowVersion({
- workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
- });
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index a3ca2b6fa6..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
-import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
-import { useDuplicateWorkflow } from '@/workflow/hooks/useDuplicateWorkflow';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { useLingui } from '@lingui/react/macro';
-import { isNonEmptyString } from '@sniptt/guards';
-import { isDefined } from 'twenty-shared/utils';
-import { useNavigateApp } from '~/hooks/useNavigateApp';
-
-export const DuplicateWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const workflow = useWorkflowWithCurrentVersion(recordId);
- const { duplicateWorkflow } = useDuplicateWorkflow();
- const navigate = useNavigateApp();
- const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
- const { t } = useLingui();
-
- const handleClick = async () => {
- if (!isDefined(workflow) || !isDefined(workflow.currentVersion)) {
- return;
- }
-
- const result = await duplicateWorkflow({
- workflowIdToDuplicate: workflow.id,
- workflowVersionIdToCopy: workflow.currentVersion.id,
- });
-
- if (isDefined(result) && isNonEmptyString(result.workflowId)) {
- enqueueSuccessSnackBar({
- message: t`Workflow duplicated successfully`,
- });
- navigate(AppPath.RecordShowPage, {
- objectNameSingular: CoreObjectNameSingular.Workflow,
- objectRecordId: result.workflowId,
- });
- } else {
- enqueueErrorSnackBar({
- message: t`Failed to duplicate workflow`,
- });
- }
- };
-
- return isDefined(workflow) ? : null;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 2e370f6328..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
-import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion';
-
-export const SeeActiveVersionWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
-
- const { workflowVersion, loading } = useActiveWorkflowVersion({
- workflowId: recordId,
- });
-
- if (loading) {
- return ;
- }
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 5ea85b74aa..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
-
-export const SeeRunsWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 33678a91b1..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { CommandLink } from '@/command-menu-item/display/components/CommandLink';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { AppPath, ViewFilterOperand } from 'twenty-shared/types';
-
-export const SeeVersionsWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
-
- return (
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 607b5fc343..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
-import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
-import { isDefined } from 'twenty-shared/utils';
-
-export const TestWorkflowSingleRecordCommand = () => {
- const recordId = useSelectedRecordIdOrThrow();
- const { runWorkflowVersion } = useRunWorkflowVersion();
- const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(recordId);
-
- const onClick = () => {
- if (!isDefined(workflowWithCurrentVersion)) {
- return;
- }
-
- runWorkflowVersion({
- workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
- workflowId: workflowWithCurrentVersion.id,
- });
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx
deleted file mode 100644
index 7c00fe385c..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { useSelectedRecordIdOrThrow } from '@/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow';
-import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
-import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
-import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
-import { useTidyUpWorkflowVersion } from '@/workflow/workflow-version/hooks/useTidyUpWorkflowVersion';
-import { isDefined } from 'twenty-shared/utils';
-import { useStore } from 'jotai';
-
-export const TidyUpWorkflowSingleRecordCommand = () => {
- const store = useStore();
- const recordId = useSelectedRecordIdOrThrow();
- const { tidyUpWorkflowVersion } = useTidyUpWorkflowVersion();
- const instanceId = getWorkflowVisualizerComponentInstanceId({
- recordId,
- });
- const { getUpdatableWorkflowVersion } =
- useGetUpdatableWorkflowVersionOrThrow(instanceId);
-
- const onClick = async () => {
- const workflowDiagramAtom = workflowDiagramComponentState.atomFamily({
- instanceId,
- });
- const workflowDiagram = store.get(workflowDiagramAtom);
-
- if (!isDefined(workflowDiagram)) {
- return;
- }
-
- const workflowVersionId = await getUpdatableWorkflowVersion();
-
- await tidyUpWorkflowVersion(workflowVersionId, workflowDiagram);
- };
-
- return ;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/types/WorkflowSingleRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/types/WorkflowSingleRecordCommandKeys.ts
deleted file mode 100644
index 43ccd977f3..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/types/WorkflowSingleRecordCommandKeys.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export enum WorkflowSingleRecordCommandKeys {
- ACTIVATE = 'activate-workflow-single-record',
- DEACTIVATE = 'deactivate-workflow-single-record',
- DISCARD_DRAFT = 'discard-draft-workflow-single-record',
- DUPLICATE_WORKFLOW = 'duplicate-workflow-single-record',
- SEE_ACTIVE_VERSION = 'see-active-version-workflow-single-record',
- SEE_RUNS = 'see-runs-workflow-single-record',
- SEE_VERSIONS = 'see-versions-workflow-single-record',
- TEST = 'test-workflow-single-record',
- ADD_NODE = 'add-node-workflow-single-record',
- TIDY_UP = 'tidy-up-workflow-single-record',
-}
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/types/DefaultRecordCommandKeys.ts b/packages/twenty-front/src/modules/command-menu-item/record/types/DefaultRecordCommandKeys.ts
deleted file mode 100644
index 61129793d7..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/types/DefaultRecordCommandKeys.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { type MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { type NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { type SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-
-export type DefaultRecordCommandKeys =
- | NoSelectionRecordCommandKeys
- | SingleRecordCommandKeys
- | MultipleRecordsCommandKeys;
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/inheritCommandMenuItemsFromDefaultConfig.test.tsx b/packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/inheritCommandMenuItemsFromDefaultConfig.test.tsx
deleted file mode 100644
index 5000398ec1..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/inheritCommandMenuItemsFromDefaultConfig.test.tsx
+++ /dev/null
@@ -1,330 +0,0 @@
-import { DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig';
-import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
-import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
-import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { type DefaultRecordCommandKeys } from '@/command-menu-item/record/types/DefaultRecordCommandKeys';
-import { IconHeart, IconPlus } from 'twenty-ui/display';
-import { inheritCommandMenuItemsFromDefaultConfig } from '@/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig';
-
-const MockComponent = Mock Component
;
-
-describe('inheritCommandMenuItemsFromDefaultConfig', () => {
- it('should return empty object when no action keys are provided', () => {
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: {},
- commandKeys: [],
- propertiesToOverwrite: {},
- });
-
- expect(result).toEqual({});
- });
-
- it('should return only provided config when no default action keys are specified', () => {
- const customConfig: Record = {
- 'custom-action': {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: 'custom-action',
- label: 'Custom Action',
- position: 100,
- Icon: IconPlus,
- shouldBeRegistered: () => true,
- component: MockComponent,
- },
- };
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: customConfig,
- commandKeys: [],
- propertiesToOverwrite: {},
- });
-
- expect(result).toEqual(customConfig);
- });
-
- it('should inherit actions from default config', () => {
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- ];
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: {},
- commandKeys,
- propertiesToOverwrite: {},
- });
-
- expect(result).toEqual({
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]:
- DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
- ],
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]:
- DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- SingleRecordCommandKeys.ADD_TO_FAVORITES
- ],
- });
- });
-
- it('should overwrite specific properties of inherited actions', () => {
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- ];
-
- const propertiesToOverwrite = {
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- label: 'Custom Create Label',
- position: 999,
- isPinned: false,
- },
- };
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: {},
- commandKeys,
- propertiesToOverwrite,
- });
-
- const expectedCommandMenuItem = {
- ...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
- ],
- label: 'Custom Create Label',
- position: 999,
- isPinned: false,
- };
-
- expect(result).toEqual({
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: expectedCommandMenuItem,
- });
- });
-
- it('should overwrite properties for multiple actions', () => {
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- ];
-
- const propertiesToOverwrite = {
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- position: 10,
- },
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- label: 'Custom Favorite Label',
- Icon: IconHeart,
- },
- };
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: {},
- commandKeys,
- propertiesToOverwrite,
- });
-
- expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual({
- ...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
- ],
- position: 10,
- });
-
- expect(result[SingleRecordCommandKeys.ADD_TO_FAVORITES]).toEqual({
- ...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- SingleRecordCommandKeys.ADD_TO_FAVORITES
- ],
- label: 'Custom Favorite Label',
- Icon: IconHeart,
- });
- });
-
- it('should only overwrite properties for specified actions', () => {
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- ];
-
- const propertiesToOverwrite = {
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- position: 10,
- },
- };
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: {},
- commandKeys,
- propertiesToOverwrite,
- });
-
- expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual({
- ...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
- ],
- position: 10,
- });
-
- expect(result[SingleRecordCommandKeys.ADD_TO_FAVORITES]).toEqual(
- DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- SingleRecordCommandKeys.ADD_TO_FAVORITES
- ],
- );
- });
-
- it('should merge inherited actions with provided config', () => {
- const customConfig: Record = {
- 'custom-action': {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: 'custom-action',
- label: 'Custom Action',
- position: 100,
- Icon: IconPlus,
- shouldBeRegistered: () => true,
- component: MockComponent,
- },
- };
-
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- ];
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: customConfig,
- commandKeys,
- propertiesToOverwrite: {},
- });
-
- expect(result).toEqual({
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]:
- DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
- ],
- 'custom-action': customConfig['custom-action'],
- });
- });
-
- it('should prioritize provided config over inherited actions when keys conflict', () => {
- const customConfig: Record = {
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- label: 'Overridden Create Action',
- position: 999,
- Icon: IconHeart,
- shouldBeRegistered: () => false,
- component: MockComponent,
- },
- };
-
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- ];
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: customConfig,
- commandKeys,
- propertiesToOverwrite: {},
- });
-
- expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual(
- customConfig[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD],
- );
- expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD].label).toBe(
- 'Overridden Create Action',
- );
- });
-
- it('should handle complex scenario with inheritance, overrides, and custom config', () => {
- const customConfig: Record = {
- 'custom-action-1': {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.Object,
- key: 'custom-action-1',
- label: 'Custom Action 1',
- position: 50,
- Icon: IconPlus,
- shouldBeRegistered: () => true,
- component: MockComponent,
- },
- [SingleRecordCommandKeys.ADD_TO_FAVORITES]: {
- type: CommandMenuItemType.Standard,
- scope: CommandMenuItemScope.RecordSelection,
- key: SingleRecordCommandKeys.ADD_TO_FAVORITES,
- label: 'Completely Custom Favorites',
- position: 1000,
- Icon: IconHeart,
- shouldBeRegistered: () => false,
- component: MockComponent,
- },
- };
-
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- SingleRecordCommandKeys.ADD_TO_FAVORITES,
- SingleRecordCommandKeys.REMOVE_FROM_FAVORITES,
- ];
-
- const propertiesToOverwrite = {
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {
- label: 'Modified Create Label',
- position: 5,
- },
- [SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]: {
- isPinned: false,
- },
- };
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: customConfig,
- commandKeys,
- propertiesToOverwrite,
- });
-
- expect(Object.keys(result)).toHaveLength(4);
-
- expect(result['custom-action-1']).toEqual(customConfig['custom-action-1']);
-
- expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual({
- ...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
- ],
- label: 'Modified Create Label',
- position: 5,
- });
-
- expect(result[SingleRecordCommandKeys.ADD_TO_FAVORITES]).toEqual(
- customConfig[SingleRecordCommandKeys.ADD_TO_FAVORITES],
- );
-
- expect(result[SingleRecordCommandKeys.REMOVE_FROM_FAVORITES]).toEqual({
- ...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- SingleRecordCommandKeys.REMOVE_FROM_FAVORITES
- ],
- isPinned: false,
- });
- });
-
- it('should handle empty overrides gracefully', () => {
- const commandKeys: DefaultRecordCommandKeys[] = [
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD,
- ];
-
- const propertiesToOverwrite = {
- [NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]: {},
- };
-
- const result = inheritCommandMenuItemsFromDefaultConfig({
- config: {},
- commandKeys,
- propertiesToOverwrite,
- });
-
- expect(result[NoSelectionRecordCommandKeys.CREATE_NEW_RECORD]).toEqual(
- DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[
- NoSelectionRecordCommandKeys.CREATE_NEW_RECORD
- ],
- );
- });
-});
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/isBulkRecordsManualTrigger.test.ts b/packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/isBulkRecordsManualTrigger.test.ts
new file mode 100644
index 0000000000..fe8916ad1a
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/isBulkRecordsManualTrigger.test.ts
@@ -0,0 +1,71 @@
+import { type WorkflowTrigger } from '@/workflow/types/Workflow';
+import { isBulkRecordsManualTrigger } from '@/command-menu-item/record/utils/isBulkRecordsManualTrigger';
+
+describe('isBulkRecordsManualTrigger', () => {
+ it('should return true for a MANUAL trigger with BULK_RECORDS availability', () => {
+ const trigger: WorkflowTrigger = {
+ type: 'MANUAL',
+ settings: {
+ outputSchema: {},
+ availability: {
+ type: 'BULK_RECORDS',
+ objectNameSingular: 'person',
+ },
+ },
+ };
+
+ expect(isBulkRecordsManualTrigger(trigger)).toBe(true);
+ });
+
+ it('should return false for a MANUAL trigger with SINGLE_RECORD availability', () => {
+ const trigger: WorkflowTrigger = {
+ type: 'MANUAL',
+ settings: {
+ outputSchema: {},
+ availability: {
+ type: 'SINGLE_RECORD',
+ objectNameSingular: 'person',
+ },
+ },
+ };
+
+ expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
+ });
+
+ it('should return false for a MANUAL trigger with GLOBAL availability', () => {
+ const trigger: WorkflowTrigger = {
+ type: 'MANUAL',
+ settings: {
+ outputSchema: {},
+ availability: {
+ type: 'GLOBAL',
+ },
+ },
+ };
+
+ expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
+ });
+
+ it('should return false for a MANUAL trigger with no availability', () => {
+ const trigger: WorkflowTrigger = {
+ type: 'MANUAL',
+ settings: {
+ outputSchema: {},
+ },
+ };
+
+ expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
+ });
+
+ it('should return false for a non-MANUAL trigger', () => {
+ const trigger: WorkflowTrigger = {
+ type: 'DATABASE_EVENT',
+ settings: {
+ eventName: 'company.created',
+ outputSchema: {},
+ },
+ };
+
+ expect(isBulkRecordsManualTrigger(trigger)).toBe(false);
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig.ts b/packages/twenty-front/src/modules/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig.ts
deleted file mode 100644
index d7fa3cd8a5..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig';
-import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
-import { type DefaultRecordCommandKeys } from '@/command-menu-item/record/types/DefaultRecordCommandKeys';
-
-export const inheritCommandMenuItemsFromDefaultConfig = ({
- config,
- commandKeys,
- propertiesToOverwrite,
-}: {
- config: Record;
- commandKeys: DefaultRecordCommandKeys[];
- propertiesToOverwrite: Partial<
- Record>
- >;
-}): Record => {
- const commandMenuItemsFromDefaultConfig = commandKeys.reduce(
- (acc, key) => ({
- ...acc,
- [key]: {
- ...DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[key],
- ...propertiesToOverwrite[key],
- },
- }),
- {} as Record,
- );
-
- return {
- ...commandMenuItemsFromDefaultConfig,
- ...config,
- };
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/utils/isGlobalManualTrigger.ts b/packages/twenty-front/src/modules/command-menu-item/record/utils/isGlobalManualTrigger.ts
deleted file mode 100644
index 173143d6a4..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/utils/isGlobalManualTrigger.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { type WorkflowTrigger } from '@/workflow/types/Workflow';
-import { isDefined } from 'twenty-shared/utils';
-
-export const isGlobalManualTrigger = (trigger: WorkflowTrigger) => {
- if (trigger.type !== 'MANUAL') {
- return false;
- }
-
- // Legacy support for manual triggers without availability
- if (!isDefined(trigger.settings?.availability)) {
- return !isDefined(trigger.settings?.objectType);
- }
-
- return trigger.settings.availability.type === 'GLOBAL';
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands.tsx b/packages/twenty-front/src/modules/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands.tsx
deleted file mode 100644
index c0b21d9d48..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { Command } from '@/command-menu-item/display/components/Command';
-import { isBulkRecordsManualTrigger } from '@/command-menu-item/record/utils/isBulkRecordsManualTrigger';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
-import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
-
-import { type WorkflowVersion } from '@/workflow/types/Workflow';
-import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
-import { t } from '@lingui/core/macro';
-import { useCallback } from 'react';
-import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
-import { capitalize, isDefined } from 'twenty-shared/utils';
-import { useIcons } from 'twenty-ui/display';
-import { useStore } from 'jotai';
-
-export const useRunWorkflowRecordCommands = ({
- objectMetadataItem,
- skip,
-}: {
- objectMetadataItem: EnrichedObjectMetadataItem;
- skip?: boolean;
-}) => {
- const store = useStore();
- const { getIcon } = useIcons();
- const { enqueueWarningSnackBar } = useSnackBar();
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const contextStoreIsPageInEditMode = useAtomComponentStateValue(
- contextStoreIsPageInEditModeComponentState,
- );
-
- const selectedRecordIds =
- contextStoreTargetedRecordsRule.mode === 'selection'
- ? contextStoreTargetedRecordsRule.selectedRecordIds
- : undefined;
-
- const { records: activeWorkflowVersions } =
- useActiveWorkflowVersionsWithManualTrigger({
- objectMetadataItem,
- skip,
- });
-
- const { runWorkflowVersion } = useRunWorkflowVersion();
-
- const runWorkflowVersionOnSelectedRecords = useCallback(
- async (
- selectedRecordIds: string[],
- activeWorkflowVersion: Pick<
- WorkflowVersion,
- 'id' | 'workflowId' | 'trigger'
- >,
- ) => {
- if (selectedRecordIds.length > QUERY_MAX_RECORDS) {
- const selectedCountFormatted =
- selectedRecordIds.length.toLocaleString();
- const limitFormatted = QUERY_MAX_RECORDS.toLocaleString();
-
- enqueueWarningSnackBar({
- message: t`You selected ${selectedCountFormatted} records but manual triggers can run on at most ${limitFormatted} records at once. Only the first ${limitFormatted} records will be processed.`,
- options: {
- dedupeKey: 'workflow-manual-trigger-selection-limit',
- },
- });
- }
-
- const limitedSelectedRecordIds = selectedRecordIds.slice(
- 0,
- QUERY_MAX_RECORDS,
- );
-
- if (
- isDefined(activeWorkflowVersion?.trigger) &&
- isBulkRecordsManualTrigger(activeWorkflowVersion.trigger)
- ) {
- const objectNamePlural = objectMetadataItem.namePlural;
- const selectedRecords = limitedSelectedRecordIds
- .map((recordId) =>
- store.get(recordStoreFamilyState.atomFamily(recordId)),
- )
- .filter(isDefined);
-
- await runWorkflowVersion({
- workflowId: activeWorkflowVersion.workflowId,
- workflowVersionId: activeWorkflowVersion.id,
- payload: {
- [objectNamePlural]: selectedRecords,
- },
- });
- } else {
- for (const selectedRecordId of limitedSelectedRecordIds) {
- const selectedRecord = store.get(
- recordStoreFamilyState.atomFamily(selectedRecordId),
- );
-
- if (!isDefined(selectedRecord)) {
- continue;
- }
-
- await runWorkflowVersion({
- workflowId: activeWorkflowVersion.workflowId,
- workflowVersionId: activeWorkflowVersion.id,
- payload: selectedRecord,
- });
- }
- }
- },
- [runWorkflowVersion, objectMetadataItem, enqueueWarningSnackBar, store],
- );
-
- return activeWorkflowVersions
- .filter((activeWorkflowVersion) =>
- isDefined(activeWorkflowVersion.workflow),
- )
- .map((activeWorkflowVersion, index) => {
- const name = capitalize(activeWorkflowVersion.workflow.name);
-
- const Icon = getIcon(
- activeWorkflowVersion.trigger?.settings.icon,
- COMMAND_MENU_DEFAULT_ICON,
- );
-
- return {
- type: CommandMenuItemType.WorkflowRun,
- key: `workflow-run-${activeWorkflowVersion.id}`,
- scope: CommandMenuItemScope.RecordSelection,
- label: name,
- shortLabel: name,
- position: index,
- Icon,
- isPinned:
- !contextStoreIsPageInEditMode &&
- activeWorkflowVersion.trigger?.settings?.isPinned,
- shouldBeRegistered: () => true,
- component: (
- {
- if (!isDefined(selectedRecordIds)) {
- return;
- }
-
- await runWorkflowVersionOnSelectedRecords(
- selectedRecordIds,
- activeWorkflowVersion,
- );
- }}
- closeSidePanelOnCommandMenuListExecution={false}
- />
- ),
- };
- });
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx
new file mode 100644
index 0000000000..e7694420fe
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx
@@ -0,0 +1,154 @@
+import { useCommandMenuItemsFromBackend } from '@/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend';
+import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
+import { renderHook } from '@testing-library/react';
+import {
+ CommandMenuContextApiPageType,
+ type CommandMenuContextApi,
+} from 'twenty-shared/types';
+import { Icon123 } from 'twenty-ui/display';
+import {
+ CommandMenuItemAvailabilityType,
+ type CommandMenuItemFieldsFragment,
+ EngineComponentKey,
+} from '~/generated-metadata/graphql';
+
+jest.mock('@/ui/utilities/state/jotai/hooks/useAtomStateValue');
+jest.mock('twenty-ui/display', () => ({
+ ...jest.requireActual('twenty-ui/display'),
+ useIcons: () => ({
+ getIcon: () => Icon123,
+ }),
+}));
+jest.mock('twenty-shared/utils', () => {
+ const actual = jest.requireActual('twenty-shared/utils');
+
+ return {
+ ...actual,
+ interpolateCommandMenuItemLabel: jest.fn(
+ ({ label }: { label?: string | null }) => label ?? null,
+ ),
+ evaluateConditionalAvailabilityExpression: jest.fn(
+ (expression?: string | null) => expression !== 'hide',
+ ),
+ };
+});
+
+const mockedUseAtomStateValue = jest.mocked(useAtomStateValue);
+
+const getCommandMenuContextApi = (
+ numberOfSelectedRecords: number,
+): CommandMenuContextApi => ({
+ pageType: CommandMenuContextApiPageType.INDEX_PAGE,
+ isInSidePanel: false,
+ isPageInEditMode: false,
+ favoriteRecordIds: [],
+ isSelectAll: false,
+ hasAnySoftDeleteFilterOnView: false,
+ objectMetadataItem: {
+ id: 'company-id',
+ },
+ objectMetadataLabel: 'Company',
+ numberOfSelectedRecords,
+ objectPermissions: {
+ objectMetadataId: 'company-id',
+ canReadObjectRecords: true,
+ canUpdateObjectRecords: true,
+ canSoftDeleteObjectRecords: true,
+ canDestroyObjectRecords: true,
+ restrictedFields: {},
+ rowLevelPermissionPredicates: [],
+ rowLevelPermissionPredicateGroups: [],
+ },
+ selectedRecords: [],
+ featureFlags: {},
+ targetObjectReadPermissions: {},
+ targetObjectWritePermissions: {},
+});
+
+const buildCommandMenuItem = ({
+ id,
+ position,
+ availabilityType,
+ expression,
+ isPinned = true,
+}: {
+ id: string;
+ position: number;
+ availabilityType: CommandMenuItemAvailabilityType;
+ expression?: string;
+ isPinned?: boolean;
+}): CommandMenuItemFieldsFragment => ({
+ id,
+ label: id,
+ shortLabel: `${id}-short`,
+ icon: 'Icon123',
+ position,
+ isPinned,
+ hotKeys: null,
+ engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD,
+ frontComponentId: null,
+ workflowVersionId: null,
+ availabilityType,
+ availabilityObjectMetadataId: 'company-id',
+ conditionalAvailabilityExpression: expression ?? null,
+});
+
+describe('useCommandMenuItemsFromBackend', () => {
+ it('filters out items hidden by conditional availability expression', () => {
+ mockedUseAtomStateValue.mockReturnValue([
+ buildCommandMenuItem({
+ id: 'global-visible',
+ position: 1,
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ }),
+ buildCommandMenuItem({
+ id: 'global-hidden',
+ position: 2,
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ expression: 'hide',
+ }),
+ buildCommandMenuItem({
+ id: 'record-visible',
+ position: 3,
+ availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
+ }),
+ ]);
+
+ const { result } = renderHook(() =>
+ useCommandMenuItemsFromBackend(getCommandMenuContextApi(0)),
+ );
+
+ expect(result.current.map((item) => item.id)).toEqual(['global-visible']);
+ });
+
+ it('maps global, record selection and fallback items to V2 output', () => {
+ mockedUseAtomStateValue.mockReturnValue([
+ buildCommandMenuItem({
+ id: 'global-item',
+ position: 2,
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ }),
+ buildCommandMenuItem({
+ id: 'record-item',
+ position: 1,
+ availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
+ }),
+ buildCommandMenuItem({
+ id: 'fallback-item',
+ position: 3,
+ availabilityType: CommandMenuItemAvailabilityType.FALLBACK,
+ }),
+ ]);
+
+ const { result } = renderHook(() =>
+ useCommandMenuItemsFromBackend(getCommandMenuContextApi(1)),
+ );
+
+ expect(result.current.map((item) => item.id)).toEqual([
+ 'record-item',
+ 'global-item',
+ 'fallback-item',
+ ]);
+ expect(result.current[2].isPinned).toBe(false);
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx
index cac4d7b268..0ac84cb2ac 100644
--- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx
@@ -3,6 +3,7 @@ import { HeadlessCommandMenuItem } from '@/command-menu-item/display/components/
import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector';
import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/server-items/common/utils/doesCommandMenuItemMatchObjectMetadataId';
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
+import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
import { type CommandMenuContextApi } from 'twenty-shared/types';
@@ -45,7 +46,7 @@ const buildCommandMenuItemFromFrontComponent = ({
isPinned,
getIcon,
commandMenuContextApi,
-}: BuildCommandMenuItemFromFrontComponentParams) => {
+}: BuildCommandMenuItemFromFrontComponentParams): CommandMenuItemConfig => {
const displayLabel = interpolateCommandMenuItemLabel({
label: item.label,
context: commandMenuContextApi,
@@ -71,11 +72,6 @@ const buildCommandMenuItemFromFrontComponent = ({
isPinned,
Icon,
hotKeys: item.hotKeys,
- shouldBeRegistered: () =>
- evaluateConditionalAvailabilityExpression(
- item.conditionalAvailabilityExpression,
- commandMenuContextApi,
- ),
component: isHeadless ? (
) : (
@@ -100,7 +96,7 @@ const buildCommandItemFromEngineKey = ({
isPinned,
getIcon,
commandMenuContextApi,
-}: BuildCommandMenuItemFromStandardKeyParams) => {
+}: BuildCommandMenuItemFromStandardKeyParams): CommandMenuItemConfig => {
const Icon = getIcon(item.icon, COMMAND_MENU_DEFAULT_ICON);
return {
@@ -120,18 +116,13 @@ const buildCommandItemFromEngineKey = ({
isPinned,
Icon,
hotKeys: item.hotKeys,
- shouldBeRegistered: () =>
- evaluateConditionalAvailabilityExpression(
- item.conditionalAvailabilityExpression,
- commandMenuContextApi,
- ),
component: ,
};
};
export const useCommandMenuItemsFromBackend = (
commandMenuContextApi: CommandMenuContextApi,
-) => {
+): CommandMenuItemConfig[] => {
const { getIcon } = useIcons();
const currentObjectMetadataItemId =
commandMenuContextApi.objectMetadataItem.id;
@@ -143,6 +134,12 @@ export const useCommandMenuItemsFromBackend = (
const itemsWithObjectMatches = commandMenuItems.filter(
doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId),
);
+ const availableItems = itemsWithObjectMatches.filter((item) =>
+ evaluateConditionalAvailabilityExpression(
+ item.conditionalAvailabilityExpression,
+ commandMenuContextApi,
+ ),
+ );
const buildCommandMenuItem = ({
item,
@@ -154,7 +151,7 @@ export const useCommandMenuItemsFromBackend = (
scope: CommandMenuItemScope;
isPinned: boolean;
typeOverride?: CommandMenuItemType;
- }) => {
+ }): CommandMenuItemConfig | null => {
if (isDefined(item.engineComponentKey)) {
return buildCommandItemFromEngineKey({
item,
@@ -180,17 +177,17 @@ export const useCommandMenuItemsFromBackend = (
return null;
};
- const globalItems = itemsWithObjectMatches.filter(
+ const globalItems = availableItems.filter(
(item) => item.availabilityType === CommandMenuItemAvailabilityType.GLOBAL,
);
- const recordScopedItems = itemsWithObjectMatches.filter(
+ const recordScopedItems = availableItems.filter(
(item) =>
item.availabilityType ===
CommandMenuItemAvailabilityType.RECORD_SELECTION,
);
- const fallbackItems = itemsWithObjectMatches.filter(
+ const fallbackItems = availableItems.filter(
(item) =>
item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK,
);
@@ -232,7 +229,5 @@ export const useCommandMenuItemsFromBackend = (
...globalCommandMenuItems,
...recordScopedCommandMenuItems,
...fallbackCommandMenuItems,
- ]
- .filter((item) => item.shouldBeRegistered())
- .sort((a, b) => a.position - b.position);
+ ].sort((a, b) => a.position - b.position);
};
diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx
deleted file mode 100644
index e4f4da4cba..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import { FrontComponentCommandMenuItem } from '@/command-menu-item/display/components/FrontComponentCommandMenuItem';
-import { HeadlessCommandMenuItem } from '@/command-menu-item/display/components/HeadlessCommandMenuItem';
-import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
-import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
-import { useCallback } from 'react';
-import { type CommandMenuContextApi } from 'twenty-shared/types';
-import {
- evaluateConditionalAvailabilityExpression,
- interpolateCommandMenuItemLabel,
- isDefined,
-} from 'twenty-shared/utils';
-import { useIcons } from 'twenty-ui/display';
-import {
- CommandMenuItemAvailabilityType,
- type CommandMenuItemFieldsFragment,
- EngineComponentKey,
-} from '~/generated-metadata/graphql';
-
-const resolveScope = (
- availabilityType: CommandMenuItemAvailabilityType,
-): CommandMenuItemScope => {
- if (availabilityType === CommandMenuItemAvailabilityType.RECORD_SELECTION) {
- return CommandMenuItemScope.RecordSelection;
- }
- return CommandMenuItemScope.Global;
-};
-
-const resolveType = (
- item: CommandMenuItemFieldsFragment,
-): CommandMenuItemType => {
- if (item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK) {
- return CommandMenuItemType.Fallback;
- }
-
- switch (item.engineComponentKey) {
- case EngineComponentKey.FRONT_COMPONENT_RENDERER:
- return CommandMenuItemType.FrontComponent;
- case EngineComponentKey.TRIGGER_WORKFLOW_VERSION:
- return CommandMenuItemType.WorkflowRun;
- default:
- return CommandMenuItemType.Standard;
- }
-};
-
-// TODO: Remove this hook once we finish refactoring and we use
-// the new types to build command menu items.
-export const useConvertBackendItemToCommandMenuItemConfig = () => {
- const { getIcon } = useIcons();
-
- const contextStoreIsPageInEditMode = useAtomComponentStateValue(
- contextStoreIsPageInEditModeComponentState,
- );
-
- const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const selectedRecordIds =
- contextStoreTargetedRecordsRule.mode === 'selection'
- ? contextStoreTargetedRecordsRule.selectedRecordIds
- : [];
-
- const hasRecordSelection =
- selectedRecordIds.length >= 1 ||
- contextStoreTargetedRecordsRule.mode === 'exclusion';
-
- const convertBackendItemToCommandMenuItemConfig = useCallback(
- (
- item: CommandMenuItemFieldsFragment,
- commandMenuContextApi: CommandMenuContextApi,
- ) => {
- const scope = resolveScope(item.availabilityType);
-
- if (
- scope === CommandMenuItemScope.RecordSelection &&
- !hasRecordSelection
- ) {
- return null;
- }
-
- const isEditModeItem =
- item.conditionalAvailabilityExpression?.includes('isPageInEditMode') ??
- false;
-
- const isPinned =
- item.availabilityType !== CommandMenuItemAvailabilityType.FALLBACK &&
- (!contextStoreIsPageInEditMode || isEditModeItem) &&
- item.isPinned;
-
- const Icon = getIcon(item.icon, COMMAND_MENU_DEFAULT_ICON);
-
- const label = interpolateCommandMenuItemLabel({
- label: item.label,
- context: commandMenuContextApi,
- });
-
- const shortLabel = interpolateCommandMenuItemLabel({
- label: item.shortLabel,
- context: commandMenuContextApi,
- });
-
- const component = (() => {
- if (
- item.engineComponentKey ===
- EngineComponentKey.FRONT_COMPONENT_RENDERER &&
- isDefined(item.frontComponentId) &&
- item.frontComponent?.isHeadless !== true
- ) {
- return (
-
- );
- }
-
- return ;
- })();
-
- return {
- type: resolveType(item),
- key: `command-menu-item-${item.id}`,
- scope,
- label,
- shortLabel,
- position: item.position,
- isPinned,
- Icon,
- hotKeys: item.hotKeys,
- shouldBeRegistered: () =>
- evaluateConditionalAvailabilityExpression(
- item.conditionalAvailabilityExpression,
- commandMenuContextApi,
- ),
- component,
- };
- },
- [getIcon, contextStoreIsPageInEditMode, hasRecordSelection],
- );
-
- return { convertBackendItemToCommandMenuItemConfig };
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditButton.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditButton.tsx
index 4ff5c6ca66..90080ef4a7 100644
--- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditButton.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditButton.tsx
@@ -6,13 +6,11 @@ import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro';
import { useStore } from 'jotai';
import { SidePanelPages } from 'twenty-shared/types';
import { IconPencil, IconX } from 'twenty-ui/display';
import { AnimatedButton } from 'twenty-ui/input';
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const CommandMenuItemEditButton = () => {
const { t } = useLingui();
@@ -26,14 +24,10 @@ export const CommandMenuItemEditButton = () => {
const isSidePanelOpened = useAtomStateValue(isSidePanelOpenedState);
const sidePanelPage = useAtomStateValue(sidePanelPageState);
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
-
const isCommandMenuEditPageActive =
isSidePanelOpened && sidePanelPage === SidePanelPages.CommandMenuEdit;
- if (!isLayoutCustomizationModeEnabled || !isCommandMenuItemEnabled) {
+ if (!isLayoutCustomizationModeEnabled) {
return null;
}
diff --git a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts b/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts
index af2d171b03..8357fc8651 100644
--- a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts
+++ b/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts
@@ -1,6 +1,5 @@
import { type CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
import { type CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
-import { type ShouldBeRegisteredFunctionParams } from '@/command-menu-item/types/ShouldBeRegisteredFunctionParams';
import { type MessageDescriptor } from '@lingui/core';
import {
type CommandMenuItemViewType,
@@ -24,7 +23,7 @@ export type CommandMenuItemConfig = {
isPrimaryCTA?: boolean;
accent?: MenuItemAccent;
availableOn?: CommandMenuItemViewType[];
- shouldBeRegistered: (params: ShouldBeRegisteredFunctionParams) => boolean;
+ shouldBeRegistered?: () => boolean;
component: React.ReactNode;
hotKeys?: Nullable;
requiredPermissionFlag?: PermissionFlagType;
diff --git a/packages/twenty-front/src/modules/command-menu-item/types/ShouldBeRegisteredFunctionParams.ts b/packages/twenty-front/src/modules/command-menu-item/types/ShouldBeRegisteredFunctionParams.ts
deleted file mode 100644
index 398518704f..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/types/ShouldBeRegisteredFunctionParams.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
-import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
-import { type WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
-import {
- type CommandMenuItemViewType,
- type ObjectPermissions,
-} from 'twenty-shared/types';
-import { type FeatureFlagKey } from '~/generated-metadata/graphql';
-
-export type ShouldBeRegisteredFunctionParams = {
- objectMetadataItem?: EnrichedObjectMetadataItem;
- objectPermissions: ObjectPermissions;
- recordFilters?: RecordFilter[];
- isShowPage?: boolean;
- hasAnySoftDeleteFilterOnView?: boolean;
- isInSidePanel?: boolean;
- isFavorite?: boolean;
- isRemote?: boolean;
- isNoteOrTask?: boolean;
- isSelectAll?: boolean;
- loadedRecords?: ObjectRecord[];
- selectedRecord?: ObjectRecord;
- numberOfSelectedRecords?: number;
- workflowWithCurrentVersion?: WorkflowWithCurrentVersion;
- viewType?: CommandMenuItemViewType;
- getTargetObjectReadPermission: (
- objectMetadataItemNameSingular: string,
- ) => boolean;
- getTargetObjectWritePermission: (
- objectMetadataItemNameSingular: string,
- ) => boolean;
- isFeatureFlagEnabled: (featureFlagKey: FeatureFlagKey) => boolean;
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemConfig.ts b/packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemConfig.ts
deleted file mode 100644
index 905a79c538..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemConfig.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { DASHBOARD_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/DashboardCommandMenuItemsConfig';
-import { DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig';
-import { WORKFLOW_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/WorkflowCommandMenuItemsConfig';
-import { WORKFLOW_RUNS_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/WorkflowRunsCommandMenuItemsConfig';
-import { WORKFLOW_VERSIONS_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/WorkflowVersionsCommandMenuItemsConfig';
-import { WORKSPACE_MEMBERS_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/WorkspaceMembersCommandMenuItemsConfig';
-import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
-import { CoreObjectNameSingular } from 'twenty-shared/types';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { isDefined } from 'twenty-shared/utils';
-
-export const getCommandMenuItemConfig = ({
- objectMetadataItem,
-}: {
- objectMetadataItem?: EnrichedObjectMetadataItem;
-}): Record => {
- if (!isDefined(objectMetadataItem)) {
- return {};
- }
-
- switch (objectMetadataItem.nameSingular) {
- case CoreObjectNameSingular.Dashboard: {
- return DASHBOARD_COMMAND_MENU_ITEMS_CONFIG;
- }
- case CoreObjectNameSingular.Workflow: {
- return WORKFLOW_COMMAND_MENU_ITEMS_CONFIG;
- }
- case CoreObjectNameSingular.WorkflowVersion: {
- return WORKFLOW_VERSIONS_COMMAND_MENU_ITEMS_CONFIG;
- }
- case CoreObjectNameSingular.WorkflowRun: {
- return WORKFLOW_RUNS_COMMAND_MENU_ITEMS_CONFIG;
- }
- case CoreObjectNameSingular.WorkspaceMember: {
- return WORKSPACE_MEMBERS_COMMAND_MENU_ITEMS_CONFIG;
- }
- case CoreObjectNameSingular.Company: {
- return DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG;
- }
- default: {
- return DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG;
- }
- }
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemViewType.ts b/packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemViewType.ts
deleted file mode 100644
index 67d1a75816..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemViewType.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { CommandMenuItemViewType } from 'twenty-shared/types';
-import { type ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
-
-export const getCommandMenuItemViewType = (
- contextStoreCurrentViewType: ContextStoreViewType | null,
- contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule,
-) => {
- if (contextStoreCurrentViewType === null) {
- return null;
- }
-
- if (contextStoreCurrentViewType === ContextStoreViewType.ShowPage) {
- return CommandMenuItemViewType.SHOW_PAGE;
- }
-
- if (
- contextStoreTargetedRecordsRule.mode === 'selection' &&
- contextStoreTargetedRecordsRule.selectedRecordIds.length === 0
- ) {
- return CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION;
- }
-
- if (
- contextStoreTargetedRecordsRule.mode === 'selection' &&
- contextStoreTargetedRecordsRule.selectedRecordIds.length === 1
- ) {
- return CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION;
- }
-
- return CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION;
-};
diff --git a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx
index d0a71561d0..e6312a3512 100644
--- a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx
@@ -7,11 +7,13 @@ import { expect, userEvent, waitFor, within } from 'storybook/test';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
+import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
+import { mockedBackendCommandMenuItems } from '~/testing/mock-data/command-menu-items';
import {
mockCurrentWorkspace,
mockedLimitedPermissionsUserData,
@@ -40,7 +42,6 @@ import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { HttpResponse, graphql } from 'msw';
import { SidePanelPages } from 'twenty-shared/types';
-import { isDefined } from 'twenty-shared/utils';
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
@@ -80,6 +81,11 @@ const meta: Meta = {
decorators: [
(Story) => {
jotaiStore.set(currentWorkspaceState.atom, mockCurrentWorkspace);
+ jotaiStore.set(metadataStoreState.atomFamily('commandMenuItems'), {
+ current: mockedBackendCommandMenuItems,
+ draft: [],
+ status: 'up-to-date',
+ });
jotaiStore.set(
currentWorkspaceMemberState.atom,
mockedWorkspaceMemberData,
@@ -108,21 +114,24 @@ const meta: Meta = {
const companyMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === 'company',
);
- if (isDefined(companyMetadataItem)) {
- jotaiStore.set(
- contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
- instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
- }),
- companyMetadataItem.id,
- );
- jotaiStore.set(
- contextStoreCurrentViewTypeComponentState.atomFamily({
- instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
- }),
- ContextStoreViewType.Table,
- );
+
+ if (companyMetadataItem === undefined) {
+ return ;
}
+ jotaiStore.set(
+ contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
+ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
+ }),
+ companyMetadataItem.id,
+ );
+ jotaiStore.set(
+ contextStoreCurrentViewTypeComponentState.atomFamily({
+ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
+ }),
+ ContextStoreViewType.Table,
+ );
+
return ;
},
ContextStoreDecorator,
@@ -215,8 +224,6 @@ export const NoResultsSearchFallback: Story = {
await sleep(openTimeout);
await userEvent.type(searchInput, 'input without results');
expect(await canvas.findByText('No results found')).toBeVisible();
- const searchRecordsButton = await canvas.findByText('Search records');
- expect(searchRecordsButton).toBeVisible();
},
parameters: {
msw: {
diff --git a/packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx
new file mode 100644
index 0000000000..56e9253870
--- /dev/null
+++ b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx
@@ -0,0 +1,393 @@
+import { act, renderHook } from '@testing-library/react';
+
+import { useFrontComponentExecutionContext } from '@/front-components/hooks/useFrontComponentExecutionContext';
+
+const mockNavigateApp = jest.fn();
+const mockRequestAccessTokenRefresh = jest.fn();
+const mockOpenConfirmationModal = jest.fn();
+const mockNavigateSidePanel = jest.fn();
+const mockSetSidePanelSearch = jest.fn();
+const mockGetIcon = jest.fn((name: string) => `icon-${name}`);
+const mockUnmountEngineCommand = jest.fn();
+const mockEnqueueSuccessSnackBar = jest.fn();
+const mockEnqueueErrorSnackBar = jest.fn();
+const mockEnqueueInfoSnackBar = jest.fn();
+const mockEnqueueWarningSnackBar = jest.fn();
+const mockCloseSidePanelMenu = jest.fn();
+const mockSetCommandMenuItemProgress = jest.fn();
+
+let mockCurrentUser: { id: string } | null = { id: 'user-123' };
+let mockTargetRecordIdentifier: { id: string } | undefined = {
+ id: 'record-456',
+};
+
+jest.mock('~/hooks/useNavigateApp', () => ({
+ useNavigateApp: () => mockNavigateApp,
+}));
+
+jest.mock('@/front-components/hooks/useRequestApplicationTokenRefresh', () => ({
+ useRequestApplicationTokenRefresh: () => ({
+ requestAccessTokenRefresh: mockRequestAccessTokenRefresh,
+ }),
+}));
+
+jest.mock(
+ '@/command-menu-item/confirmation-modal/hooks/useCommandMenuConfirmationModal',
+ () => ({
+ useCommandMenuConfirmationModal: () => ({
+ openConfirmationModal: mockOpenConfirmationModal,
+ }),
+ }),
+);
+
+jest.mock('@/side-panel/hooks/useNavigateSidePanel', () => ({
+ useNavigateSidePanel: () => ({
+ navigateSidePanel: mockNavigateSidePanel,
+ }),
+}));
+
+jest.mock(
+ '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand',
+ () => ({
+ useUnmountCommand: () => mockUnmountEngineCommand,
+ }),
+);
+
+jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({
+ useSnackBar: () => ({
+ enqueueSuccessSnackBar: mockEnqueueSuccessSnackBar,
+ enqueueErrorSnackBar: mockEnqueueErrorSnackBar,
+ enqueueInfoSnackBar: mockEnqueueInfoSnackBar,
+ enqueueWarningSnackBar: mockEnqueueWarningSnackBar,
+ }),
+}));
+
+jest.mock('@/side-panel/hooks/useSidePanelMenu', () => ({
+ useSidePanelMenu: () => ({
+ closeSidePanelMenu: mockCloseSidePanelMenu,
+ }),
+}));
+
+jest.mock('twenty-ui/display', () => ({
+ useIcons: () => ({
+ getIcon: mockGetIcon,
+ }),
+}));
+
+jest.mock('@/ui/utilities/state/jotai/hooks/useAtomStateValue', () => ({
+ useAtomStateValue: () => mockCurrentUser,
+}));
+
+jest.mock('@/ui/utilities/state/jotai/hooks/useSetAtomState', () => ({
+ useSetAtomState: () => mockSetSidePanelSearch,
+}));
+
+jest.mock('@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState', () => ({
+ useSetAtomFamilyState: () => mockSetCommandMenuItemProgress,
+}));
+
+jest.mock('@/ui/layout/contexts/LayoutRenderingContext', () => ({
+ useLayoutRenderingContext: () => ({
+ targetRecordIdentifier: mockTargetRecordIdentifier,
+ }),
+}));
+
+const FRONT_COMPONENT_ID = 'fc-test-id';
+const COMMAND_MENU_ITEM_ID = 'cmd-item-1';
+
+describe('useFrontComponentExecutionContext', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockCurrentUser = { id: 'user-123' };
+ mockTargetRecordIdentifier = { id: 'record-456' };
+ });
+
+ describe('executionContext', () => {
+ it('should return frontComponentId, userId, and recordId', () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ expect(result.current.executionContext).toEqual({
+ frontComponentId: FRONT_COMPONENT_ID,
+ userId: 'user-123',
+ recordId: 'record-456',
+ });
+ });
+
+ it('should return null userId when no current user', () => {
+ mockCurrentUser = null;
+
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ expect(result.current.executionContext.userId).toBeNull();
+ });
+
+ it('should return null recordId when no target record', () => {
+ mockTargetRecordIdentifier = undefined;
+
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ expect(result.current.executionContext.recordId).toBeNull();
+ });
+ });
+
+ describe('navigate', () => {
+ it('should call navigateApp with the provided arguments', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.navigate(
+ '/settings' as never,
+ { id: '1' } as never,
+ { tab: 'general' } as never,
+ { replace: true } as never,
+ );
+ });
+
+ expect(mockNavigateApp).toHaveBeenCalledWith(
+ '/settings',
+ { id: '1' },
+ { tab: 'general' },
+ { replace: true },
+ );
+ });
+ });
+
+ describe('openSidePanelPage', () => {
+ it('should call navigateSidePanel with resolved icon', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.openSidePanelPage(
+ {
+ page: '/side-panel-page' as never,
+ pageTitle: 'My Page',
+ pageIcon: 'IconSettings',
+ shouldResetSearchState: false,
+ },
+ );
+ });
+
+ expect(mockNavigateSidePanel).toHaveBeenCalledWith({
+ page: '/side-panel-page',
+ pageTitle: 'My Page',
+ pageIcon: 'icon-IconSettings',
+ });
+
+ expect(mockSetSidePanelSearch).not.toHaveBeenCalled();
+ });
+
+ it('should reset side panel search state when shouldResetSearchState is true', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.openSidePanelPage(
+ {
+ page: '/page' as never,
+ pageTitle: 'Title',
+ pageIcon: 'IconSearch',
+ shouldResetSearchState: true,
+ },
+ );
+ });
+
+ expect(mockSetSidePanelSearch).toHaveBeenCalledWith('');
+ });
+ });
+
+ describe('openCommandConfirmationModal', () => {
+ it('should call openConfirmationModal with frontComponent caller', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.openCommandConfirmationModal(
+ {
+ title: 'Confirm?',
+ subtitle: 'Are you sure?',
+ confirmButtonText: 'Yes',
+ confirmButtonAccent: 'danger' as never,
+ },
+ );
+ });
+
+ expect(mockOpenConfirmationModal).toHaveBeenCalledWith({
+ caller: {
+ type: 'frontComponent',
+ frontComponentId: FRONT_COMPONENT_ID,
+ },
+ title: 'Confirm?',
+ subtitle: 'Are you sure?',
+ confirmButtonText: 'Yes',
+ confirmButtonAccent: 'danger',
+ });
+ });
+ });
+
+ describe('enqueueSnackbar', () => {
+ it.each([
+ { variant: 'success' as const, mock: () => mockEnqueueSuccessSnackBar },
+ { variant: 'error' as const, mock: () => mockEnqueueErrorSnackBar },
+ { variant: 'info' as const, mock: () => mockEnqueueInfoSnackBar },
+ { variant: 'warning' as const, mock: () => mockEnqueueWarningSnackBar },
+ ])(
+ 'should route $variant snackbar to the correct handler',
+ async ({ variant, mock }) => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.enqueueSnackbar(
+ {
+ message: `${variant} message`,
+ variant,
+ duration: 3000,
+ detailedMessage: 'details',
+ dedupeKey: 'key-1',
+ },
+ );
+ });
+
+ expect(mock()).toHaveBeenCalledWith({
+ message: `${variant} message`,
+ options: {
+ duration: 3000,
+ detailedMessage: 'details',
+ dedupeKey: 'key-1',
+ },
+ });
+ },
+ );
+ });
+
+ describe('unmountFrontComponent', () => {
+ it('should call unmountEngineCommand when commandMenuItemId is provided', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ commandMenuItemId: COMMAND_MENU_ITEM_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.unmountFrontComponent();
+ });
+
+ expect(mockUnmountEngineCommand).toHaveBeenCalledWith(
+ COMMAND_MENU_ITEM_ID,
+ );
+ });
+
+ it('should not call unmountEngineCommand when commandMenuItemId is undefined', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.unmountFrontComponent();
+ });
+
+ expect(mockUnmountEngineCommand).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('closeSidePanel', () => {
+ it('should call closeSidePanelMenu', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.closeSidePanel();
+ });
+
+ expect(mockCloseSidePanelMenu).toHaveBeenCalled();
+ });
+ });
+
+ describe('updateProgress', () => {
+ it('should set clamped progress when commandMenuItemId is provided', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ commandMenuItemId: COMMAND_MENU_ITEM_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.updateProgress(
+ 50,
+ );
+ });
+
+ expect(mockSetCommandMenuItemProgress).toHaveBeenCalledWith(50);
+ });
+
+ it('should clamp progress to 0 when negative value is provided', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ commandMenuItemId: COMMAND_MENU_ITEM_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.updateProgress(
+ -10,
+ );
+ });
+
+ expect(mockSetCommandMenuItemProgress).toHaveBeenCalledWith(0);
+ });
+
+ it('should clamp progress to 100 when value exceeds 100', async () => {
+ const { result } = renderHook(() =>
+ useFrontComponentExecutionContext({
+ frontComponentId: FRONT_COMPONENT_ID,
+ commandMenuItemId: COMMAND_MENU_ITEM_ID,
+ }),
+ );
+
+ await act(async () => {
+ await result.current.frontComponentHostCommunicationApi.updateProgress(
+ 150,
+ );
+ });
+
+ expect(mockSetCommandMenuItemProgress).toHaveBeenCalledWith(100);
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/front-components/hooks/__tests__/useOnFrontComponentUpdated.test.tsx b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useOnFrontComponentUpdated.test.tsx
new file mode 100644
index 0000000000..b427f855bf
--- /dev/null
+++ b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useOnFrontComponentUpdated.test.tsx
@@ -0,0 +1,85 @@
+import { renderHook } from '@testing-library/react';
+
+import { useOnFrontComponentUpdated } from '@/front-components/hooks/useOnFrontComponentUpdated';
+import { AllMetadataName } from '~/generated-metadata/graphql';
+
+const mockUseListenToEventsForQuery = jest.fn();
+const mockUseListenToMetadataOperationBrowserEvent = jest.fn();
+const mockUpdateFrontComponentApolloCache = jest.fn();
+
+jest.mock('@/sse-db-event/hooks/useListenToEventsForQuery', () => ({
+ useListenToEventsForQuery: (...args: unknown[]) =>
+ mockUseListenToEventsForQuery(...args),
+}));
+
+jest.mock(
+ '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent',
+ () => ({
+ useListenToMetadataOperationBrowserEvent: (...args: unknown[]) =>
+ mockUseListenToMetadataOperationBrowserEvent(...args),
+ }),
+);
+
+jest.mock(
+ '@/front-components/hooks/useUpdateFrontComponentApolloCache',
+ () => ({
+ useUpdateFrontComponentApolloCache: () => ({
+ updateFrontComponentApolloCache: mockUpdateFrontComponentApolloCache,
+ }),
+ }),
+);
+
+const FRONT_COMPONENT_ID = 'fc-test-id';
+
+describe('useOnFrontComponentUpdated', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should call useListenToEventsForQuery with correct queryId and operationSignature', () => {
+ renderHook(() =>
+ useOnFrontComponentUpdated({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ expect(mockUseListenToEventsForQuery).toHaveBeenCalledWith({
+ queryId: `front-component-updated-${FRONT_COMPONENT_ID}`,
+ operationSignature: {
+ metadataName: AllMetadataName.frontComponent,
+ variables: {
+ filter: { id: { eq: FRONT_COMPONENT_ID } },
+ },
+ },
+ });
+ });
+
+ it('should call useListenToMetadataOperationBrowserEvent with frontComponent metadata name', () => {
+ renderHook(() =>
+ useOnFrontComponentUpdated({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ expect(mockUseListenToMetadataOperationBrowserEvent).toHaveBeenCalledWith({
+ metadataName: AllMetadataName.frontComponent,
+ onMetadataOperationBrowserEvent: mockUpdateFrontComponentApolloCache,
+ });
+ });
+
+ it('should derive queryId from frontComponentId', () => {
+ const customId = 'custom-fc-123';
+
+ renderHook(() =>
+ useOnFrontComponentUpdated({
+ frontComponentId: customId,
+ }),
+ );
+
+ expect(mockUseListenToEventsForQuery).toHaveBeenCalledWith(
+ expect.objectContaining({
+ queryId: `front-component-updated-${customId}`,
+ }),
+ );
+ });
+});
diff --git a/packages/twenty-front/src/modules/front-components/hooks/__tests__/useRequestApplicationTokenRefresh.test.tsx b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useRequestApplicationTokenRefresh.test.tsx
new file mode 100644
index 0000000000..356b769d5f
--- /dev/null
+++ b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useRequestApplicationTokenRefresh.test.tsx
@@ -0,0 +1,237 @@
+import { CombinedGraphQLErrors } from '@apollo/client/errors';
+import { act, renderHook } from '@testing-library/react';
+import { atom, createStore, Provider as JotaiProvider } from 'jotai';
+import { type ReactNode } from 'react';
+
+import { useRequestApplicationTokenRefresh } from '@/front-components/hooks/useRequestApplicationTokenRefresh';
+import { type ApplicationTokenPair } from '~/generated-metadata/graphql';
+
+const mockQuery = jest.fn();
+const mockMutate = jest.fn();
+
+jest.mock('@apollo/client/react', () => ({
+ ...jest.requireActual('@apollo/client/react'),
+ useApolloClient: () => ({
+ query: mockQuery,
+ mutate: mockMutate,
+ }),
+}));
+
+const tokenPairAtom = atom(null);
+
+jest.mock(
+ '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState',
+ () => ({
+ useAtomComponentStateCallbackState: () => tokenPairAtom,
+ }),
+);
+
+const FRONT_COMPONENT_ID = 'fc-test-id';
+
+const buildTokenPair = (
+ accessToken = 'access-token-1',
+ refreshToken = 'refresh-token-1',
+): ApplicationTokenPair => ({
+ __typename: 'ApplicationTokenPair',
+ applicationAccessToken: {
+ __typename: 'AuthToken',
+ token: accessToken,
+ expiresAt: '2099-01-01T00:00:00.000Z',
+ },
+ applicationRefreshToken: {
+ __typename: 'AuthToken',
+ token: refreshToken,
+ expiresAt: '2099-01-01T00:00:00.000Z',
+ },
+});
+
+const getWrapper =
+ (store: ReturnType) =>
+ ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+
+describe('useRequestApplicationTokenRefresh', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should throw when application token pair is not initialized in the store', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const { result } = renderHook(
+ () =>
+ useRequestApplicationTokenRefresh({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ { wrapper },
+ );
+
+ await expect(
+ act(() => result.current.requestAccessTokenRefresh()),
+ ).rejects.toThrow(
+ 'Application token pair must be initialized before requesting a refresh',
+ );
+ });
+
+ it('should renew token via mutation and update the store', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const initialTokenPair = buildTokenPair('old-access', 'old-refresh');
+ store.set(tokenPairAtom, initialTokenPair);
+
+ const renewedTokenPair = buildTokenPair(
+ 'new-access-token',
+ 'new-refresh-token',
+ );
+
+ mockMutate.mockResolvedValue({
+ data: { renewApplicationToken: renewedTokenPair },
+ });
+
+ const { result } = renderHook(
+ () =>
+ useRequestApplicationTokenRefresh({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ { wrapper },
+ );
+
+ let accessToken: string | undefined;
+
+ await act(async () => {
+ accessToken = await result.current.requestAccessTokenRefresh();
+ });
+
+ expect(accessToken).toBe('new-access-token');
+ expect(store.get(tokenPairAtom)).toEqual(renewedTokenPair);
+ expect(mockMutate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ variables: {
+ applicationRefreshToken: 'old-refresh',
+ },
+ }),
+ );
+ });
+
+ it('should fallback to refetching front component when refresh token is expired', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const initialTokenPair = buildTokenPair('old-access', 'expired-refresh');
+ store.set(tokenPairAtom, initialTokenPair);
+
+ const expiredError = new CombinedGraphQLErrors({
+ errors: [
+ {
+ message: 'Refresh token expired',
+ extensions: {
+ subCode: 'APPLICATION_REFRESH_TOKEN_INVALID_OR_EXPIRED',
+ },
+ },
+ ],
+ });
+
+ mockMutate.mockRejectedValue(expiredError);
+
+ const refetchedTokenPair = buildTokenPair(
+ 'refetched-access',
+ 'refetched-refresh',
+ );
+
+ mockQuery.mockResolvedValue({
+ data: {
+ frontComponent: {
+ applicationTokenPair: refetchedTokenPair,
+ },
+ },
+ });
+
+ const { result } = renderHook(
+ () =>
+ useRequestApplicationTokenRefresh({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ { wrapper },
+ );
+
+ let accessToken: string | undefined;
+
+ await act(async () => {
+ accessToken = await result.current.requestAccessTokenRefresh();
+ });
+
+ expect(accessToken).toBe('refetched-access');
+ expect(store.get(tokenPairAtom)).toEqual(refetchedTokenPair);
+ expect(mockQuery).toHaveBeenCalledWith(
+ expect.objectContaining({
+ variables: { id: FRONT_COMPONENT_ID },
+ fetchPolicy: 'network-only',
+ }),
+ );
+ });
+
+ it('should re-throw non-token-expiry errors from the mutation', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const initialTokenPair = buildTokenPair();
+ store.set(tokenPairAtom, initialTokenPair);
+
+ const networkError = new Error('Network failure');
+ mockMutate.mockRejectedValue(networkError);
+
+ const { result } = renderHook(
+ () =>
+ useRequestApplicationTokenRefresh({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ { wrapper },
+ );
+
+ await expect(
+ act(() => result.current.requestAccessTokenRefresh()),
+ ).rejects.toThrow('Network failure');
+
+ expect(mockQuery).not.toHaveBeenCalled();
+ });
+
+ it('should throw when refetch returns no token pair', async () => {
+ const store = createStore();
+ const wrapper = getWrapper(store);
+
+ const initialTokenPair = buildTokenPair('old-access', 'expired-refresh');
+ store.set(tokenPairAtom, initialTokenPair);
+
+ const expiredError = new CombinedGraphQLErrors({
+ errors: [
+ {
+ message: 'Refresh token expired',
+ extensions: {
+ subCode: 'APPLICATION_REFRESH_TOKEN_INVALID_OR_EXPIRED',
+ },
+ },
+ ],
+ });
+
+ mockMutate.mockRejectedValue(expiredError);
+
+ mockQuery.mockResolvedValue({
+ data: { frontComponent: { applicationTokenPair: null } },
+ });
+
+ const { result } = renderHook(
+ () =>
+ useRequestApplicationTokenRefresh({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ { wrapper },
+ );
+
+ await expect(
+ act(() => result.current.requestAccessTokenRefresh()),
+ ).rejects.toThrow('Failed to refetch application token pair');
+ });
+});
diff --git a/packages/twenty-front/src/modules/front-components/hooks/__tests__/useUpdateFrontComponentApolloCache.test.tsx b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useUpdateFrontComponentApolloCache.test.tsx
new file mode 100644
index 0000000000..32e0661017
--- /dev/null
+++ b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useUpdateFrontComponentApolloCache.test.tsx
@@ -0,0 +1,178 @@
+import { renderHook } from '@testing-library/react';
+
+import { type MetadataOperationBrowserEventDetail } from '@/browser-event/types/MetadataOperationBrowserEventDetail';
+import { useUpdateFrontComponentApolloCache } from '@/front-components/hooks/useUpdateFrontComponentApolloCache';
+import { type FrontComponent } from '~/generated-metadata/graphql';
+
+const mockUpdateQuery = jest.fn();
+const mockApolloClient = {
+ cache: { updateQuery: mockUpdateQuery },
+};
+
+jest.mock('@apollo/client/react', () => ({
+ ...jest.requireActual('@apollo/client/react'),
+ useApolloClient: () => mockApolloClient,
+}));
+
+const FRONT_COMPONENT_ID = 'fc-test-id';
+
+const buildFrontComponentRecord = (
+ overrides: Partial = {},
+): FrontComponent =>
+ ({
+ id: FRONT_COMPONENT_ID,
+ name: 'Test Component',
+ __typename: 'FrontComponent',
+ ...overrides,
+ }) as FrontComponent;
+
+describe('useUpdateFrontComponentApolloCache', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should call cache.updateQuery when operation is update and record ID matches', () => {
+ const { result } = renderHook(() =>
+ useUpdateFrontComponentApolloCache({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ const detail: MetadataOperationBrowserEventDetail = {
+ metadataName: 'frontComponent' as never,
+ operation: {
+ type: 'update',
+ updatedRecord: buildFrontComponentRecord(),
+ },
+ };
+
+ result.current.updateFrontComponentApolloCache(detail);
+
+ expect(mockUpdateQuery).toHaveBeenCalledTimes(1);
+ });
+
+ it('should not call cache.updateQuery when operation type is create', () => {
+ const { result } = renderHook(() =>
+ useUpdateFrontComponentApolloCache({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ const detail: MetadataOperationBrowserEventDetail = {
+ metadataName: 'frontComponent' as never,
+ operation: {
+ type: 'create',
+ createdRecord: buildFrontComponentRecord(),
+ },
+ };
+
+ result.current.updateFrontComponentApolloCache(detail);
+
+ expect(mockUpdateQuery).not.toHaveBeenCalled();
+ });
+
+ it('should not call cache.updateQuery when operation type is delete', () => {
+ const { result } = renderHook(() =>
+ useUpdateFrontComponentApolloCache({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ const detail: MetadataOperationBrowserEventDetail = {
+ metadataName: 'frontComponent' as never,
+ operation: {
+ type: 'delete',
+ deletedRecordId: FRONT_COMPONENT_ID,
+ },
+ };
+
+ result.current.updateFrontComponentApolloCache(detail);
+
+ expect(mockUpdateQuery).not.toHaveBeenCalled();
+ });
+
+ it('should not call cache.updateQuery when updatedRecord ID does not match frontComponentId', () => {
+ const { result } = renderHook(() =>
+ useUpdateFrontComponentApolloCache({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ const detail: MetadataOperationBrowserEventDetail = {
+ metadataName: 'frontComponent' as never,
+ operation: {
+ type: 'update',
+ updatedRecord: buildFrontComponentRecord({ id: 'other-id' as never }),
+ },
+ };
+
+ result.current.updateFrontComponentApolloCache(detail);
+
+ expect(mockUpdateQuery).not.toHaveBeenCalled();
+ });
+
+ it('should merge updatedRecord into existing cache data', () => {
+ const { result } = renderHook(() =>
+ useUpdateFrontComponentApolloCache({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ const updatedRecord = buildFrontComponentRecord({
+ name: 'Updated Name' as never,
+ });
+
+ const detail: MetadataOperationBrowserEventDetail = {
+ metadataName: 'frontComponent' as never,
+ operation: {
+ type: 'update',
+ updatedRecord,
+ },
+ };
+
+ result.current.updateFrontComponentApolloCache(detail);
+
+ const updaterFn = mockUpdateQuery.mock.calls[0][1];
+
+ const existingData = {
+ frontComponent: {
+ id: FRONT_COMPONENT_ID,
+ name: 'Old Name',
+ __typename: 'FrontComponent' as const,
+ },
+ };
+
+ const updatedData = updaterFn(existingData);
+
+ expect(updatedData).toEqual({
+ frontComponent: {
+ ...existingData.frontComponent,
+ ...updatedRecord,
+ },
+ });
+ });
+
+ it('should return existing data when frontComponent is not in cache', () => {
+ const { result } = renderHook(() =>
+ useUpdateFrontComponentApolloCache({
+ frontComponentId: FRONT_COMPONENT_ID,
+ }),
+ );
+
+ const detail: MetadataOperationBrowserEventDetail = {
+ metadataName: 'frontComponent' as never,
+ operation: {
+ type: 'update',
+ updatedRecord: buildFrontComponentRecord(),
+ },
+ };
+
+ result.current.updateFrontComponentApolloCache(detail);
+
+ const updaterFn = mockUpdateQuery.mock.calls[0][1];
+ const existingData = { frontComponent: null };
+ const updatedData = updaterFn(existingData);
+
+ expect(updatedData).toEqual(existingData);
+ });
+});
diff --git a/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts b/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts
index 7a923993c4..43565809f7 100644
--- a/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts
+++ b/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts
@@ -15,16 +15,10 @@ import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/commo
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const useEnterLayoutCustomizationMode = () => {
const store = useStore();
const { navigateSidePanel } = useNavigateSidePanel();
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
const enterLayoutCustomizationMode = useCallback(() => {
const isLayoutCustomizationModeAlreadyEnabled = store.get(
@@ -54,7 +48,6 @@ export const useEnterLayoutCustomizationMode = () => {
const currentSidePanelPage = store.get(sidePanelPageState.atom);
if (
- isCommandMenuItemEnabled &&
isSidePanelOpened &&
currentSidePanelPage === SidePanelPages.CommandMenuDisplay
) {
@@ -67,7 +60,7 @@ export const useEnterLayoutCustomizationMode = () => {
resetNavigationStack: true,
});
}
- }, [isCommandMenuItemEnabled, navigateSidePanel, store]);
+ }, [navigateSidePanel, store]);
return { enterLayoutCustomizationMode };
};
diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts
index a3f4cafbf7..85da24bb04 100644
--- a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts
+++ b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts
@@ -6,13 +6,11 @@ import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelate
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useApolloClient } from '@apollo/client/react';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import {
- FeatureFlagKey,
FindAllViewsDocument,
FindManyCommandMenuItemsDocument,
FindAllRecordPageLayoutsDocument,
@@ -57,9 +55,6 @@ export const useLoadStaleMetadataEntities = () => {
const client = useApolloClient();
const store = useStore();
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
const loadStaleMetadataEntities = useCallback(
async (staleEntityKeys: MetadataEntityKey[]) => {
@@ -199,10 +194,7 @@ export const useLoadStaleMetadataEntities = () => {
);
}
- if (
- staleEntityKeys.includes('commandMenuItems') &&
- isCommandMenuItemEnabled
- ) {
+ if (staleEntityKeys.includes('commandMenuItems')) {
fetchPromises.push(
client
.query({
@@ -222,7 +214,7 @@ export const useLoadStaleMetadataEntities = () => {
await Promise.all(fetchPromises);
applyChanges();
},
- [client, store, replaceDraft, applyChanges, isCommandMenuItemEnabled],
+ [client, store, replaceDraft, applyChanges],
);
return { loadStaleMetadataEntities };
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx
index 0b3b052e1e..cd99d15f0f 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx
@@ -11,12 +11,10 @@ import { PageHeaderToggleSidePanelButton } from '@/ui/layout/page-header/compone
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledTitleWithSelectedRecords = styled.div`
display: flex;
@@ -69,9 +67,6 @@ export const RecordIndexPageHeader = () => {
const isLayoutCustomizationModeEnabled = useAtomStateValue(
isLayoutCustomizationModeEnabledState,
);
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
return (
{
{isDefined(contextStoreCurrentViewId) && (
<>
- {isCommandMenuItemEnabled ? (
- !isLayoutCustomizationModeEnabled && (
-
- )
+ {!isLayoutCustomizationModeEnabled ? (
+
) : (
)}
diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelMultipleRecordsInfo.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelMultipleRecordsInfo.tsx
index 27d53ff94d..7e90a478c0 100644
--- a/packages/twenty-front/src/modules/side-panel/components/SidePanelMultipleRecordsInfo.tsx
+++ b/packages/twenty-front/src/modules/side-panel/components/SidePanelMultipleRecordsInfo.tsx
@@ -1,10 +1,8 @@
-import { DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG } from '@/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig';
-import { MultipleRecordsCommandKeys } from '@/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys';
-import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { SidePanelPageInfoLayout } from '@/side-panel/components/SidePanelPageInfoLayout';
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
+import { IconPencil } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme-constants';
type SidePanelMultipleRecordsInfoProps = {
@@ -20,14 +18,13 @@ export const SidePanelMultipleRecordsInfo = ({
limit: 1,
});
- const { Icon, label } =
- DEFAULT_RECORD_COMMAND_MENU_ITEMS_CONFIG[MultipleRecordsCommandKeys.UPDATE];
-
return (
}
+ icon={
+
+ }
iconColor={theme.font.color.tertiary}
- title={getCommandMenuItemLabel(label)}
+ title={t`Update records`}
label={t`${totalCount} selected`}
/>
);
diff --git a/packages/twenty-front/src/modules/side-panel/constants/SidePanelPagesConfig.tsx b/packages/twenty-front/src/modules/side-panel/constants/SidePanelPagesConfig.tsx
index 63c6487110..f946b43a37 100644
--- a/packages/twenty-front/src/modules/side-panel/constants/SidePanelPagesConfig.tsx
+++ b/packages/twenty-front/src/modules/side-panel/constants/SidePanelPagesConfig.tsx
@@ -26,27 +26,13 @@ import { SidePanelWorkflowEditStepType } from '@/side-panel/pages/workflow/step/
import { SidePanelWorkflowRunViewStep } from '@/side-panel/pages/workflow/step/view-run/components/SidePanelWorkflowRunViewStep';
import { SidePanelWorkflowViewStep } from '@/side-panel/pages/workflow/step/view/components/SidePanelWorkflowViewStep';
import { SidePanelWorkflowSelectTriggerType } from '@/side-panel/pages/workflow/trigger-type/components/SidePanelWorkflowSelectTriggerType';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { SidePanelPages } from 'twenty-shared/types';
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
-
-const SidePanelCommandMenuDisplayPageSwitch = () => {
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
-
- if (isCommandMenuItemEnabled) {
- return ;
- }
-
- return ;
-};
export const SIDE_PANEL_PAGES_CONFIG = new Map(
[
[
SidePanelPages.CommandMenuDisplay,
- ,
+ ,
],
[SidePanelPages.ViewRecord, ],
[SidePanelPages.MergeRecords, ],
diff --git a/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts b/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts
deleted file mode 100644
index c158de9b6d..0000000000
--- a/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { isGlobalManualTrigger } from '@/command-menu-item/record/utils/isGlobalManualTrigger';
-import { CoreObjectNameSingular } from 'twenty-shared/types';
-import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
-import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
-import {
- type ManualTriggerWorkflowVersion,
- type Workflow,
-} from '@/workflow/types/Workflow';
-import { isDefined } from 'twenty-shared/utils';
-
-export const useActiveWorkflowVersionsWithManualTrigger = ({
- objectMetadataItem,
- skip,
-}: {
- objectMetadataItem?: EnrichedObjectMetadataItem;
- skip?: boolean;
-}) => {
- const filters = [
- {
- status: {
- eq: 'ACTIVE',
- },
- },
- {
- trigger: {
- like: `%"type": "MANUAL"%`,
- },
- },
- ];
-
- if (isDefined(objectMetadataItem)) {
- filters.push({
- trigger: {
- like: `%"objectNameSingular": "${objectMetadataItem?.nameSingular}"%`,
- },
- });
- }
-
- const { records } = useFindManyRecords<
- Pick<
- ManualTriggerWorkflowVersion,
- 'id' | '__typename' | 'status' | 'workflowId' | 'trigger'
- > & {
- workflow: Workflow;
- }
- >({
- objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
- filter: {
- and: filters,
- },
- recordGqlFields: {
- id: true,
- trigger: true,
- workflowId: true,
- workflow: true,
- status: true,
- },
- skip,
- });
-
- // TODO: refactor when we can use 'not like' in the RawJson filter
- if (!isDefined(objectMetadataItem)) {
- return {
- records: records.filter(
- (record) =>
- record.status === 'ACTIVE' &&
- isDefined(record.trigger) &&
- isGlobalManualTrigger(record.trigger),
- ),
- };
- }
-
- return { records: records.filter((record) => isDefined(record.workflow)) };
-};
diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx
index 2ad447824b..03ac948bdf 100644
--- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx
+++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx
@@ -16,18 +16,13 @@ import { computeRecordShowComponentInstanceId } from '@/object-record/record-sho
import { PageHeaderToggleSidePanelButton } from '@/ui/layout/page-header/components/PageHeaderToggleSidePanelButton';
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader';
import { RecordShowPageTitle } from '~/pages/object-record/RecordShowPageTitle';
-import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const RecordShowPage = () => {
const isLayoutCustomizationModeEnabled = useAtomStateValue(
isLayoutCustomizationModeEnabledState,
);
- const isCommandMenuItemEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- );
const parameters = useParams<{
objectNameSingular: string;
@@ -62,10 +57,8 @@ export const RecordShowPage = () => {
objectRecordId={objectRecordId}
>
- {isCommandMenuItemEnabled ? (
- !isLayoutCustomizationModeEnabled && (
-
- )
+ {!isLayoutCustomizationModeEnabled ? (
+
) : (
)}
diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx
index 75ffd4ef10..0779e2fa17 100644
--- a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx
+++ b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx
@@ -3,10 +3,11 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
+import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
+import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { t } from '@lingui/core/macro';
import {
IconChartBar,
diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts
index 352dbb2575..bdb3bc3bb0 100644
--- a/packages/twenty-front/src/testing/graphqlMocks.ts
+++ b/packages/twenty-front/src/testing/graphqlMocks.ts
@@ -22,6 +22,7 @@ import { mockedCompanyRecords } from '~/testing/mock-data/generated/data/compani
import { mockedTaskRecords } from '~/testing/mock-data/generated/data/tasks/mock-tasks-data';
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
import { mockedRoles } from '~/testing/mock-data/generated/metadata/roles/mock-roles-data';
+import { mockedBackendCommandMenuItems } from '~/testing/mock-data/command-menu-items';
import { type Task } from '@/activities/types/Task';
import { FIND_MINIMAL_METADATA } from '@/metadata-store/graphql/queries/findMinimalMetadata';
@@ -211,7 +212,7 @@ export const graphqlMocks = {
}),
metadataGraphql.query('FindManyCommandMenuItems', () => {
return HttpResponse.json({
- data: { commandMenuItems: [] },
+ data: { commandMenuItems: mockedBackendCommandMenuItems },
});
}),
graphql.query('SearchPeople', () => {
diff --git a/packages/twenty-front/src/testing/mock-data/command-menu-items.ts b/packages/twenty-front/src/testing/mock-data/command-menu-items.ts
new file mode 100644
index 0000000000..c826dcacee
--- /dev/null
+++ b/packages/twenty-front/src/testing/mock-data/command-menu-items.ts
@@ -0,0 +1,128 @@
+import {
+ CommandMenuItemAvailabilityType,
+ type CommandMenuItemFieldsFragment,
+ EngineComponentKey,
+} from '~/generated-metadata/graphql';
+
+export const mockedBackendCommandMenuItems: CommandMenuItemFieldsFragment[] = [
+ {
+ __typename: 'CommandMenuItem',
+ id: 'mock-go-to-people',
+ workflowVersionId: null,
+ frontComponentId: null,
+ frontComponent: null,
+ engineComponentKey: EngineComponentKey.GO_TO_PEOPLE,
+ label: 'Go to People',
+ icon: 'IconUser',
+ shortLabel: 'People',
+ position: 23,
+ isPinned: false,
+ hotKeys: ['G', 'P'],
+ conditionalAvailabilityExpression: 'targetObjectReadPermissions.person',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ availabilityObjectMetadataId: null,
+ },
+ {
+ __typename: 'CommandMenuItem',
+ id: 'mock-go-to-opportunities',
+ workflowVersionId: null,
+ frontComponentId: null,
+ frontComponent: null,
+ engineComponentKey: EngineComponentKey.GO_TO_OPPORTUNITIES,
+ label: 'Go to Opportunities',
+ icon: 'IconTargetArrow',
+ shortLabel: 'Opportunities',
+ position: 26,
+ isPinned: false,
+ hotKeys: ['G', 'O'],
+ conditionalAvailabilityExpression:
+ 'targetObjectReadPermissions.opportunity',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ availabilityObjectMetadataId: null,
+ },
+ {
+ __typename: 'CommandMenuItem',
+ id: 'mock-go-to-settings',
+ workflowVersionId: null,
+ frontComponentId: null,
+ frontComponent: null,
+ engineComponentKey: EngineComponentKey.GO_TO_SETTINGS,
+ label: 'Go to Settings',
+ icon: 'IconSettings',
+ shortLabel: 'Settings',
+ position: 27,
+ isPinned: false,
+ hotKeys: ['G', 'S'],
+ conditionalAvailabilityExpression: null,
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ availabilityObjectMetadataId: null,
+ },
+ {
+ __typename: 'CommandMenuItem',
+ id: 'mock-go-to-tasks',
+ workflowVersionId: null,
+ frontComponentId: null,
+ frontComponent: null,
+ engineComponentKey: EngineComponentKey.GO_TO_TASKS,
+ label: 'Go to Tasks',
+ icon: 'IconCheckbox',
+ shortLabel: 'Tasks',
+ position: 28,
+ isPinned: false,
+ hotKeys: ['G', 'T'],
+ conditionalAvailabilityExpression: 'targetObjectReadPermissions.task',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ availabilityObjectMetadataId: null,
+ },
+ {
+ __typename: 'CommandMenuItem',
+ id: 'mock-go-to-notes',
+ workflowVersionId: null,
+ frontComponentId: null,
+ frontComponent: null,
+ engineComponentKey: EngineComponentKey.GO_TO_NOTES,
+ label: 'Go to Notes',
+ icon: 'IconCheckbox',
+ shortLabel: 'Notes',
+ position: 29,
+ isPinned: false,
+ hotKeys: ['G', 'N'],
+ conditionalAvailabilityExpression: 'targetObjectReadPermissions.note',
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ availabilityObjectMetadataId: null,
+ },
+ {
+ __typename: 'CommandMenuItem',
+ id: 'mock-search-records',
+ workflowVersionId: null,
+ frontComponentId: null,
+ frontComponent: null,
+ engineComponentKey: EngineComponentKey.SEARCH_RECORDS,
+ label: 'Search ${capitalize(objectMetadataItem.labelPlural)}',
+ icon: 'IconSearch',
+ shortLabel: 'Search',
+ position: 60,
+ isPinned: false,
+ hotKeys: ['/'],
+ conditionalAvailabilityExpression: null,
+ availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
+ availabilityObjectMetadataId: null,
+ },
+ {
+ __typename: 'CommandMenuItem',
+ id: 'mock-search-records-fallback',
+ workflowVersionId: null,
+ frontComponentId: null,
+ frontComponent: null,
+ engineComponentKey: EngineComponentKey.SEARCH_RECORDS_FALLBACK,
+ label: 'Search ${capitalize(objectMetadataItem.labelPlural)}',
+ icon: 'IconSearch',
+ shortLabel: 'Search',
+ position: 61,
+ isPinned: false,
+ hotKeys: ['/'],
+ conditionalAvailabilityExpression: null,
+ availabilityType: CommandMenuItemAvailabilityType.FALLBACK,
+ availabilityObjectMetadataId: null,
+ },
+];
diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.resolver.ts
index 4e74e57c82..25b8b0842a 100644
--- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.resolver.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/command-menu-item.resolver.ts
@@ -2,16 +2,11 @@ import { UseGuards, UseInterceptors } from '@nestjs/common';
import { Args, Mutation, Parent, Query, ResolveField } from '@nestjs/graphql';
import { isDefined } from 'twenty-shared/utils';
-import { FeatureFlagKey } from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
-import {
- FeatureFlagGuard,
- RequireFeatureFlag,
-} from 'src/engine/guards/feature-flag.guard';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CommandMenuItemService } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.service';
@@ -23,7 +18,7 @@ import { FrontComponentDTO } from 'src/engine/metadata-modules/front-component/d
import { FrontComponentService } from 'src/engine/metadata-modules/front-component/front-component.service';
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
-@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
+@UseGuards(WorkspaceAuthGuard)
@UseInterceptors(
WorkspaceMigrationGraphqlApiExceptionInterceptor,
CommandMenuItemGraphqlApiExceptionInterceptor,
@@ -52,7 +47,6 @@ export class CommandMenuItemResolver {
@Query(() => [CommandMenuItemDTO])
@UseGuards(NoPermissionGuard)
- @RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
async commandMenuItems(
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise {
@@ -61,7 +55,6 @@ export class CommandMenuItemResolver {
@Query(() => CommandMenuItemDTO, { nullable: true })
@UseGuards(NoPermissionGuard)
- @RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
async commandMenuItem(
@Args('id', { type: () => UUIDScalarType }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@@ -71,7 +64,6 @@ export class CommandMenuItemResolver {
@Mutation(() => CommandMenuItemDTO)
@UseGuards(NoPermissionGuard)
- @RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
async createCommandMenuItem(
@Args('input') input: CreateCommandMenuItemInput,
@AuthWorkspace() workspace: WorkspaceEntity,
@@ -81,7 +73,6 @@ export class CommandMenuItemResolver {
@Mutation(() => CommandMenuItemDTO)
@UseGuards(NoPermissionGuard)
- @RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
async updateCommandMenuItem(
@Args('input') input: UpdateCommandMenuItemInput,
@AuthWorkspace() workspace: WorkspaceEntity,
@@ -91,7 +82,6 @@ export class CommandMenuItemResolver {
@Mutation(() => CommandMenuItemDTO)
@UseGuards(NoPermissionGuard)
- @RequireFeatureFlag(FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED)
async deleteCommandMenuItem(
@Args('id', { type: () => UUIDScalarType }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
index 2bd4af3cae..1eb1911811 100644
--- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
@@ -238,7 +238,6 @@ describe('WorkspaceEntityManager', () => {
IS_PUBLIC_DOMAIN_ENABLED: false,
IS_EMAILING_DOMAIN_ENABLED: false,
IS_JUNCTION_RELATIONS_ENABLED: false,
- IS_COMMAND_MENU_ITEM_ENABLED: false,
IS_DRAFT_EMAIL_ENABLED: false,
IS_USAGE_ANALYTICS_ENABLED: false,
IS_RICH_TEXT_V1_MIGRATED: false,
@@ -248,6 +247,7 @@ describe('WorkspaceEntityManager', () => {
IS_GRAPHQL_QUERY_TIMING_ENABLED: false,
IS_RECORD_TABLE_WIDGET_ENABLED: false,
IS_DATASOURCE_MIGRATED: false,
+ IS_COMMAND_MENU_ITEM_ENABLED: false,
},
userWorkspaceRoleMap: {},
eventEmitterService: {
diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts
index 817de9dd80..51f3df2b1a 100644
--- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts
@@ -50,11 +50,6 @@ export const seedFeatureFlags = async ({
workspaceId: workspaceId,
value: true,
},
- {
- key: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- workspaceId: workspaceId,
- value: true,
- },
{
key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
workspaceId: workspaceId,
diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts
index dc36e83273..3b5ae61b52 100644
--- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts
+++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts
@@ -1,9 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
-import { FeatureFlagKey } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
-import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { CommandMenuItemService } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.service';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
@@ -50,7 +48,6 @@ export class WorkflowCommonWorkspaceService {
private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly commandMenuItemService: CommandMenuItemService,
- private readonly featureFlagService: FeatureFlagService,
) {}
async getWorkflowVersionOrFail({
@@ -341,16 +338,6 @@ export class WorkflowCommonWorkspaceService {
workflowVersionId: string,
workspaceId: string,
) {
- const isCommandMenuItemEnabled =
- await this.featureFlagService.isFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- workspaceId,
- );
-
- if (!isCommandMenuItemEnabled) {
- return;
- }
-
const existingCommandMenuItem =
await this.commandMenuItemService.findByWorkflowVersionId(
workflowVersionId,
diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts
index 82899d1ab2..926d4c937f 100644
--- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts
+++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts
@@ -1,9 +1,8 @@
import { Injectable, Logger } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
-import { type ActorMetadata, FeatureFlagKey } from 'twenty-shared/types';
+import { type ActorMetadata } from 'twenty-shared/types';
-import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { CommandMenuItemService } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.service';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
@@ -50,7 +49,6 @@ export class WorkflowTriggerWorkspaceService {
private readonly automatedTriggerWorkspaceService: AutomatedTriggerWorkspaceService,
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
private readonly commandMenuItemService: CommandMenuItemService,
- private readonly featureFlagService: FeatureFlagService,
) {}
async runWorkflowVersion({
@@ -391,16 +389,6 @@ export class WorkflowTriggerWorkspaceService {
return;
}
- const isCommandMenuItemEnabled =
- await this.featureFlagService.isFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- workspaceId,
- );
-
- if (!isCommandMenuItemEnabled) {
- return;
- }
-
const trigger = workflowVersion.trigger as WorkflowManualTrigger;
const { availabilityType, availabilityObjectMetadataId } =
@@ -456,16 +444,6 @@ export class WorkflowTriggerWorkspaceService {
return;
}
- const isCommandMenuItemEnabled =
- await this.featureFlagService.isFeatureEnabled(
- FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- workspaceId,
- );
-
- if (!isCommandMenuItemEnabled) {
- return;
- }
-
const existingCommandMenuItem =
await this.commandMenuItemService.findByWorkflowVersionId(
workflowVersion.id,
diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-creation.integration-spec.ts
index 04253c7ddc..c426350532 100644
--- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-creation.integration-spec.ts
+++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-creation.integration-spec.ts
@@ -1,12 +1,10 @@
import { faker } from '@faker-js/faker';
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
-import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
-import { FeatureFlagKey } from 'twenty-shared/types';
import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
@@ -131,22 +129,6 @@ const failingCommandMenuItemCreationTestCases: EachTestingContext[]
];
describe('CommandMenuItem creation should fail', () => {
- beforeAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: true,
- expectToFail: false,
- });
- });
-
- afterAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: false,
- expectToFail: false,
- });
- });
-
it.each(eachTestingContextFilter(failingCommandMenuItemCreationTestCases))(
'$title',
async ({ context }) => {
diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-deletion.integration-spec.ts
index adcda48d29..6584c7e07b 100644
--- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-deletion.integration-spec.ts
+++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-deletion.integration-spec.ts
@@ -1,13 +1,11 @@
import { faker } from '@faker-js/faker';
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
-import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { type DeleteCommandMenuItemFactoryInput } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item-query-factory.util';
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
-import { FeatureFlagKey } from 'twenty-shared/types';
type TestContext = {
input: DeleteCommandMenuItemFactoryInput;
@@ -48,22 +46,6 @@ const failingCommandMenuItemDeletionTestCases: EachTestingContext[]
];
describe('CommandMenuItem deletion should fail', () => {
- beforeAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: true,
- expectToFail: false,
- });
- });
-
- afterAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: false,
- expectToFail: false,
- });
- });
-
it.each(eachTestingContextFilter(failingCommandMenuItemDeletionTestCases))(
'$title',
async ({ context }) => {
diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-update.integration-spec.ts
index dfed3e29e1..a2b081831b 100644
--- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-update.integration-spec.ts
+++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/failing-command-menu-item-update.integration-spec.ts
@@ -1,6 +1,5 @@
import { faker } from '@faker-js/faker';
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
-import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
import { updateCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/update-command-menu-item.util';
@@ -8,7 +7,6 @@ import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
-import { FeatureFlagKey } from 'twenty-shared/types';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
import { type UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input';
@@ -24,22 +22,6 @@ type TestSetup = {
describe('CommandMenuItem update should fail', () => {
let testCommandMenuItemId: string;
- beforeAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: true,
- expectToFail: false,
- });
- });
-
- afterAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: false,
- expectToFail: false,
- });
- });
-
beforeEach(async () => {
const { data } = await createCommandMenuItem({
expectToFail: false,
diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts
index c9ff53c779..7817679cbf 100644
--- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts
+++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-creation.integration-spec.ts
@@ -5,9 +5,7 @@ import { createFrontComponent } from 'test/integration/metadata/suites/front-com
import { deleteFrontComponent } from 'test/integration/metadata/suites/front-component/utils/delete-front-component.util';
import { seedBuiltFrontComponentFile } from 'test/integration/metadata/suites/front-component/utils/seed-built-front-component-file.util';
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
-import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
-import { FeatureFlagKey } from 'twenty-shared/types';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
@@ -20,12 +18,6 @@ describe('CommandMenuItem creation should succeed', () => {
let personObjectMetadataId: string;
beforeAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: true,
- expectToFail: false,
- });
-
const { cleanup } = await seedBuiltFrontComponentFile({
builtComponentPath: 'src/front-components/index.mjs',
});
@@ -62,12 +54,6 @@ describe('CommandMenuItem creation should succeed', () => {
afterAll(async () => {
cleanupBuiltFile?.();
-
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: false,
- expectToFail: false,
- });
});
afterEach(async () => {
diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-deletion.integration-spec.ts
index cd21cc7d91..65eee190bd 100644
--- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-deletion.integration-spec.ts
+++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-deletion.integration-spec.ts
@@ -1,29 +1,11 @@
import { faker } from '@faker-js/faker';
-import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
import { findCommandMenuItems } from 'test/integration/metadata/suites/command-menu-item/utils/find-command-menu-items.util';
-import { FeatureFlagKey } from 'twenty-shared/types';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
describe('CommandMenuItem deletion should succeed', () => {
- beforeAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: true,
- expectToFail: false,
- });
- });
-
- afterAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: false,
- expectToFail: false,
- });
- });
-
it('should delete an existing command menu item', async () => {
const workflowVersionId = faker.string.uuid();
diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts
index 2e721656c5..91a7d48548 100644
--- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts
+++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/successful-command-menu-item-update.integration-spec.ts
@@ -3,9 +3,7 @@ import { createCommandMenuItem } from 'test/integration/metadata/suites/command-
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
import { updateCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/update-command-menu-item.util';
-import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
-import { FeatureFlagKey } from 'twenty-shared/types';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
@@ -16,12 +14,6 @@ describe('CommandMenuItem update should succeed', () => {
let personObjectMetadataId: string;
beforeAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: true,
- expectToFail: false,
- });
-
const { objects } = await findManyObjectMetadata({
expectToFail: false,
input: {
@@ -50,14 +42,6 @@ describe('CommandMenuItem update should succeed', () => {
personObjectMetadataId = personObjectMetadata.id;
});
- afterAll(async () => {
- await updateFeatureFlag({
- featureFlag: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
- value: false,
- expectToFail: false,
- });
- });
-
beforeEach(async () => {
const workflowVersionId = faker.string.uuid();
diff --git a/packages/twenty-shared/src/types/FeatureFlagKey.ts b/packages/twenty-shared/src/types/FeatureFlagKey.ts
index 337ef6588f..1d14cb4366 100644
--- a/packages/twenty-shared/src/types/FeatureFlagKey.ts
+++ b/packages/twenty-shared/src/types/FeatureFlagKey.ts
@@ -2,12 +2,12 @@ export enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
IS_AI_ENABLED = 'IS_AI_ENABLED',
+ IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED',
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
- IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
IS_USAGE_ANALYTICS_ENABLED = 'IS_USAGE_ANALYTICS_ENABLED',
IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED',