From 9f95c4763c4cd44d115594a76ab8d933de213a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:56:52 +0200 Subject: [PATCH] [COMMAND MENU ITEMS] Remove deprecated code (#19199) This PR is the first one of a cleanup after upgrading command menu items to V2. --- .../src/metadata/generated/schema.graphql | 2 +- .../src/metadata/generated/schema.ts | 4 +- .../PageHeaderCommandMenuButtons.tsx | 47 - .../components/RecordIndexCommandMenu.tsx | 16 +- .../components/RecordShowCommandMenu.tsx | 15 +- .../contexts/CommandMenuContextProvider.tsx | 27 +- .../CommandMenuContextProviderDefault.tsx | 66 -- .../CommandMenuContextProviderLegacy.tsx | 65 -- ...mandMenuContextProviderWorkflowObjects.tsx | 159 ---- .../CommandMenuItemOpenSidePanelPage.tsx | 50 -- .../CommandMenuItemButton.stories.tsx | 14 +- .../CommandMenuItemComponent.stories.tsx | 16 +- .../CommandMenuItemDisplay.stories.tsx | 17 +- .../CommandMenuItemDropdownItem.stories.tsx | 16 +- .../CommandMenuItemListItem.stories.tsx | 16 +- ...WorkflowVersionTriggerInformation.test.tsx | 231 +++++ .../useHeadlessCommandContextApi.test.tsx | 68 ++ ...lessEngineCommandEffectInitialized.test.ts | 42 + .../hooks/__tests__/useMountCommand.test.tsx | 155 ++++ .../useUnmountEngineCommand.test.tsx | 103 +++ ...celRecordPageLayoutSingleRecordCommand.tsx | 55 -- ...aveRecordPageLayoutSingleRecordCommand.tsx | 44 - ...erWorkflowVersionCommandContextApi.test.ts | 46 + .../__tests__/useCloseCommandMenu.test.tsx | 201 +++++ .../useRegisteredCommandMenuItems.test.tsx | 186 ---- .../hooks/useRegisteredCommandMenuItems.ts | 96 --- ...ShouldCommandMenuItemBeRegisteredParams.ts | 144 ---- .../mock/command-menu-items.mock.tsx | 34 +- .../RecordAgnosticCommandMenuItemsConfig.tsx | 113 --- .../useRelatedRecordCommands.test.tsx | 252 ------ .../hooks/useRecordAgnosticCommands.ts | 37 - .../hooks/useRelatedRecordCommands.ts | 124 --- .../types/RecordAgnosticCommandKeys.ts | 7 - .../useRunWorkflowRecordAgnosticCommands.tsx | 71 -- .../DashboardCommandMenuItemsConfig.tsx | 204 ----- .../DefaultRecordCommandMenuItemsConfig.tsx | 813 ------------------ .../WorkflowCommandMenuItemsConfig.tsx | 390 --------- .../WorkflowRunsCommandMenuItemsConfig.tsx | 165 ---- ...WorkflowVersionsCommandMenuItemsConfig.tsx | 199 ----- ...WorkspaceMembersCommandMenuItemsConfig.tsx | 126 --- .../DeleteMultipleRecordsCommand.tsx | 102 --- .../DestroyMultipleRecordsCommand.tsx | 114 --- .../ExportMultipleRecordsCommand.tsx | 77 -- .../MergeMultipleRecordsCommand.tsx | 31 - .../RestoreMultipleRecordsCommand.tsx | 101 --- .../UpdateMultipleRecordsCommand.tsx | 21 - .../types/MultipleRecordsCommandKeys.ts | 8 - ...NewIndexRecordNoSelectionRecordCommand.tsx | 18 - .../CreateNewViewNoSelectionRecordCommand.tsx | 45 - ...igationSidebarNoSelectionRecordCommand.tsx | 13 - ...DeletedRecordsNoSelectionRecordCommand.tsx | 56 -- .../ImportRecordsNoSelectionRecordCommand.tsx | 14 - ...DeletedRecordsNoSelectionRecordCommand.tsx | 38 - .../types/NoSelectionRecordCommandKeys.ts | 16 - .../NoSelectionWorkflowRecordCommandKeys.ts | 3 - .../AddToFavoritesSingleRecordCommand.tsx | 43 - .../components/CreateRelatedRecordCommand.tsx | 103 --- .../components/DeleteSingleRecordCommand.tsx | 49 -- .../components/DestroySingleRecordCommand.tsx | 47 - .../ExportNoteSingleRecordCommand.tsx | 49 -- .../components/ExportSingleRecordCommand.tsx | 30 - ...avigateToNextRecordSingleRecordCommand.tsx | 17 - ...ateToPreviousRecordSingleRecordCommand.tsx | 17 - ...RemoveFromFavoritesSingleRecordCommand.tsx | 36 - .../components/RestoreSingleRecordCommand.tsx | 41 - .../CancelDashboardSingleRecordCommand.tsx | 50 -- .../DuplicateDashboardSingleRecordCommand.tsx | 37 - .../EditDashboardSingleRecordCommand.tsx | 26 - .../SaveDashboardSingleRecordCommand.tsx | 37 - .../types/DashboardSingleRecordCommandKeys.ts | 3 - .../hooks/useSelectedRecordIdOrThrow.tsx | 18 - .../hooks/useSelectedRecordIds.tsx | 18 - ...ditRecordPageLayoutSingleRecordCommand.tsx | 16 - ...RecordPageLayoutSingleRecordCommandKeys.ts | 3 - .../types/SingleRecordCommandKeys.ts | 12 - .../utils/exportBlockNoteEditorToDocx.ts | 16 - ...eVersionWorkflowRunSingleRecordCommand.tsx | 25 - ...WorkflowWorkflowRunSingleRecordCommand.tsx | 25 - .../StopWorkflowRunSingleRecordCommand.tsx | 69 -- .../WorkflowRunSingleRecordCommandKeys.ts | 5 - ...RunsWorkflowVersionSingleRecordCommand.tsx | 57 -- ...ionsWorkflowVersionSingleRecordCommand.tsx | 47 - ...flowWorkflowVersionSingleRecordCommand.tsx | 20 - ...raftWorkflowVersionSingleRecordCommand.tsx | 82 -- .../WorkflowVersionSingleRecordCommandKeys.ts | 6 - .../ActivateWorkflowSingleRecordCommand.tsx | 24 - .../AddNodeWorkflowSingleRecordCommand.tsx | 22 - .../DeactivateWorkflowSingleRecordCommand.tsx | 23 - ...iscardDraftWorkflowSingleRecordCommand.tsx | 23 - .../DuplicateWorkflowSingleRecordCommand.tsx | 46 - ...tiveVersionWorkflowSingleRecordCommand.tsx | 27 - .../SeeRunsWorkflowSingleRecordCommand.tsx | 26 - ...SeeVersionsWorkflowSingleRecordCommand.tsx | 26 - .../TestWorkflowSingleRecordCommand.tsx | 24 - .../TidyUpWorkflowSingleRecordCommand.tsx | 36 - .../types/WorkflowSingleRecordCommandKeys.ts | 12 - .../record/types/DefaultRecordCommandKeys.ts | 8 - ...CommandMenuItemsFromDefaultConfig.test.tsx | 330 ------- .../isBulkRecordsManualTrigger.test.ts | 71 ++ ...nheritCommandMenuItemsFromDefaultConfig.ts | 31 - .../record/utils/isGlobalManualTrigger.ts | 15 - .../hooks/useRunWorkflowRecordCommands.tsx | 161 ---- .../useCommandMenuItemsFromBackend.test.tsx | 154 ++++ .../hooks/useCommandMenuItemsFromBackend.tsx | 35 +- ...vertBackendItemToCommandMenuItemConfig.tsx | 145 ---- .../components/CommandMenuItemEditButton.tsx | 8 +- .../types/CommandMenuItemConfig.ts | 3 +- .../types/ShouldBeRegisteredFunctionParams.ts | 34 - .../utils/getCommandMenuItemConfig.ts | 44 - .../utils/getCommandMenuItemViewType.ts | 32 - .../__stories__/CommandMenu.stories.tsx | 39 +- ...useFrontComponentExecutionContext.test.tsx | 393 +++++++++ .../useOnFrontComponentUpdated.test.tsx | 85 ++ ...useRequestApplicationTokenRefresh.test.tsx | 237 +++++ ...seUpdateFrontComponentApolloCache.test.tsx | 178 ++++ .../hooks/useEnterLayoutCustomizationMode.ts | 9 +- .../hooks/useLoadStaleMetadataEntities.ts | 12 +- .../components/RecordIndexPageHeader.tsx | 11 +- .../SidePanelMultipleRecordsInfo.tsx | 13 +- .../constants/SidePanelPagesConfig.tsx | 16 +- ...ActiveWorkflowVersionsWithManualTrigger.ts | 74 -- .../pages/object-record/RecordShowPage.tsx | 11 +- .../src/pages/settings/ai/SettingsAI.tsx | 3 +- .../twenty-front/src/testing/graphqlMocks.ts | 3 +- .../testing/mock-data/command-menu-items.ts | 128 +++ .../command-menu-item.resolver.ts | 12 +- .../workspace-entity-manager.spec.ts | 2 +- .../core/utils/seed-feature-flags.util.ts | 5 - .../workflow-common.workspace-service.ts | 13 - .../workflow-trigger.workspace-service.ts | 24 +- ...and-menu-item-creation.integration-spec.ts | 18 - ...and-menu-item-deletion.integration-spec.ts | 18 - ...mmand-menu-item-update.integration-spec.ts | 18 - ...and-menu-item-creation.integration-spec.ts | 14 - ...and-menu-item-deletion.integration-spec.ts | 18 - ...mmand-menu-item-update.integration-spec.ts | 16 - .../twenty-shared/src/types/FeatureFlagKey.ts | 2 +- 137 files changed, 2216 insertions(+), 6613 deletions(-) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/components/PageHeaderCommandMenuButtons.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderDefault.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderLegacy.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWorkflowObjects.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemOpenSidePanelPage.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.test.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useHeadlessCommandContextApi.test.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useIsHeadlessEngineCommandEffectInitialized.test.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useMountCommand.test.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/__tests__/useUnmountEngineCommand.test.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/CancelRecordPageLayoutSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/SaveRecordPageLayoutSingleRecordCommand.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/utils/__tests__/isHeadlessTriggerWorkflowVersionCommandContextApi.test.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useRegisteredCommandMenuItems.test.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/hooks/useRegisteredCommandMenuItems.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record-agnostic/constants/RecordAgnosticCommandMenuItemsConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/__tests__/useRelatedRecordCommands.test.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRecordAgnosticCommands.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record-agnostic/hooks/useRelatedRecordCommands.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record-agnostic/types/RecordAgnosticCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/constants/DashboardCommandMenuItemsConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/constants/DefaultRecordCommandMenuItemsConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowCommandMenuItemsConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowRunsCommandMenuItemsConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/constants/WorkflowVersionsCommandMenuItemsConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/constants/WorkspaceMembersCommandMenuItemsConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/ExportMultipleRecordsCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/MergeMultipleRecordsCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/multiple-records/types/MultipleRecordsCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/EditNavigationSidebarNoSelectionRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/no-selection/workflow/types/NoSelectionWorkflowRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/CreateRelatedRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DeleteSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/DestroySingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportNoteSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/ExportSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/components/RestoreSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/dashboard/types/DashboardSingleRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIdOrThrow.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/hooks/useSelectedRecordIds.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/record-page-layout/types/RecordPageLayoutSingleRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/types/SingleRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/utils/exportBlockNoteEditorToDocx.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-runs/types/WorkflowRunSingleRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow-versions/types/WorkflowVersionSingleRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/single-record/workflow/types/WorkflowSingleRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/types/DefaultRecordCommandKeys.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/inheritCommandMenuItemsFromDefaultConfig.test.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/record/utils/__tests__/isBulkRecordsManualTrigger.test.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/utils/inheritCommandMenuItemsFromDefaultConfig.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/utils/isGlobalManualTrigger.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/types/ShouldBeRegisteredFunctionParams.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemConfig.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/utils/getCommandMenuItemViewType.ts create mode 100644 packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx create mode 100644 packages/twenty-front/src/modules/front-components/hooks/__tests__/useOnFrontComponentUpdated.test.tsx create mode 100644 packages/twenty-front/src/modules/front-components/hooks/__tests__/useRequestApplicationTokenRefresh.test.tsx create mode 100644 packages/twenty-front/src/modules/front-components/hooks/__tests__/useUpdateFrontComponentApolloCache.test.tsx delete mode 100644 packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts create mode 100644 packages/twenty-front/src/testing/mock-data/command-menu-items.ts 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',