From 49afe5dbc46f0dddc1a490d5baa28020771365c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:59:52 +0100 Subject: [PATCH] Refactor: Unify command menu item execution (#18908) - Makes engineComponentKey non-nullable on CommandMenuItem. Adds two new engine component keys: TRIGGER_WORKFLOW_VERSION and FRONT_COMPONENT_RENDERER to cover the former standalone cases. - Unifies the previously separated engine, front component and workflow runners into a single `CommandRunner` component with a unified `useMountCommand` hook that handles all command types. --- .../src/generated-metadata/graphql.ts | 10 +- .../app/components/AppRouterProviders.tsx | 6 +- .../EngineComponentKeyComponentMap.tsx | 254 ------------------ .../CommandMenuItemErrorBoundary.tsx | 10 +- .../components/EngineCommandMenuItem.tsx | 43 --- .../components/HeadlessCommandMenuItem.tsx | 48 +++- .../HeadlessFrontComponentCommandMenuItem.tsx | 68 ----- .../components/WorkflowCommandMenuItem.tsx | 128 --------- .../components/CommandRunner.tsx | 38 +++ ...ssConfirmationModalEngineCommandEffect.tsx | 23 +- .../HeadlessEngineCommandMountRoot.tsx | 41 --- .../HeadlessEngineCommandWrapperEffect.tsx | 16 +- ...essFrontComponentRendererEngineCommand.tsx | 60 +++++ ...EngineComponentKeyHeadlessComponentMap.tsx | 10 +- ...piWithWorkflowVersionTriggerInformation.ts | 132 +++++++++ .../hooks/useHeadlessCommandContextApi.ts | 25 ++ .../engine-command/hooks/useMountCommand.ts | 78 ++++++ .../hooks/useMountEngineCommand.ts | 129 --------- .../hooks/useMountedEngineCommandContext.ts | 22 -- .../hooks/useUnmountEngineCommand.ts | 10 +- .../TriggerWorkflowVersionEngineCommand.tsx | 39 +++ .../DeleteMultipleRecordsCommand.tsx | 4 +- .../DestroyMultipleRecordsCommand.tsx | 4 +- .../ExportMultipleRecordsCommand.tsx | 9 +- .../MergeMultipleRecordsCommand.tsx | 4 +- .../RestoreMultipleRecordsCommand.tsx | 4 +- .../UpdateMultipleRecordsCommand.tsx | 4 +- ...NewIndexRecordNoSelectionRecordCommand.tsx | 5 +- .../CreateNewViewNoSelectionRecordCommand.tsx | 4 +- ...DeletedRecordsNoSelectionRecordCommand.tsx | 5 +- .../ImportRecordsNoSelectionRecordCommand.tsx | 4 +- ...DeletedRecordsNoSelectionRecordCommand.tsx | 5 +- .../AddToFavoritesSingleRecordCommand.tsx | 4 +- .../components/DeleteSingleRecordCommand.tsx | 6 +- .../components/DestroySingleRecordCommand.tsx | 4 +- .../ExportNoteSingleRecordCommand.tsx | 4 +- .../components/ExportSingleRecordCommand.tsx | 4 +- ...avigateToNextRecordSingleRecordCommand.tsx | 4 +- ...ateToPreviousRecordSingleRecordCommand.tsx | 4 +- ...RemoveFromFavoritesSingleRecordCommand.tsx | 4 +- .../components/RestoreSingleRecordCommand.tsx | 4 +- .../CancelDashboardSingleRecordCommand.tsx | 4 +- .../DuplicateDashboardSingleRecordCommand.tsx | 4 +- .../EditDashboardSingleRecordCommand.tsx | 4 +- .../SaveDashboardSingleRecordCommand.tsx | 4 +- ...celRecordPageLayoutSingleRecordCommand.tsx | 4 +- ...ditRecordPageLayoutSingleRecordCommand.tsx | 4 +- ...aveRecordPageLayoutSingleRecordCommand.tsx | 4 +- ...eVersionWorkflowRunSingleRecordCommand.tsx | 4 +- ...WorkflowWorkflowRunSingleRecordCommand.tsx | 6 +- .../StopWorkflowRunSingleRecordCommand.tsx | 5 +- ...RunsWorkflowVersionSingleRecordCommand.tsx | 4 +- ...ionsWorkflowVersionSingleRecordCommand.tsx | 4 +- ...flowWorkflowVersionSingleRecordCommand.tsx | 4 +- ...raftWorkflowVersionSingleRecordCommand.tsx | 4 +- .../ActivateWorkflowSingleRecordCommand.tsx | 4 +- .../AddNodeWorkflowSingleRecordCommand.tsx | 4 +- .../DeactivateWorkflowSingleRecordCommand.tsx | 4 +- ...iscardDraftWorkflowSingleRecordCommand.tsx | 4 +- .../DuplicateWorkflowSingleRecordCommand.tsx | 4 +- ...tiveVersionWorkflowSingleRecordCommand.tsx | 4 +- .../SeeRunsWorkflowSingleRecordCommand.tsx | 4 +- ...SeeVersionsWorkflowSingleRecordCommand.tsx | 4 +- .../TestWorkflowSingleRecordCommand.tsx | 4 +- .../TidyUpWorkflowSingleRecordCommand.tsx | 4 +- .../isEngineCommandMountedFamilySelector.ts | 6 +- ....ts => CommandComponentInstanceContext.ts} | 3 +- .../states/headlessCommandContextApisState.ts | 9 + .../states/mountedEngineCommandsState.ts | 9 - ...ontext.ts => HeadlessCommandContextApi.ts} | 19 +- .../utils/buildHeadlessCommandContextApi.ts | 119 ++++++++ .../buildTriggerWorkflowVersionPayloads.ts | 84 ++++++ ...HeadlessFrontComponentCommandContextApi.ts | 9 + ...TriggerWorkflowVersionCommandContextApi.ts | 9 + ...vertBackendItemToCommandMenuItemConfig.tsx | 63 ++--- .../HeadlessFrontComponentMountRoot.tsx | 60 ----- .../useFrontComponentExecutionContext.ts | 8 +- .../hooks/useMountHeadlessFrontComponent.ts | 23 -- .../hooks/useUnmountHeadlessFrontComponent.ts | 33 --- ...lessFrontComponentMountedFamilySelector.ts | 14 - .../mountedHeadlessFrontComponentMapsState.ts | 14 - .../clients/generated/metadata/schema.graphql | 6 +- .../src/clients/generated/metadata/schema.ts | 10 +- ...-20-backfill-command-menu-items.command.ts | 3 +- ...convert-engine-component-key-to-varchar.ts | 55 ++++ ...o-universal-flat-command-menu-item.util.ts | 3 +- .../dtos/command-menu-item.dto.ts | 6 +- .../dtos/create-command-menu-item.input.ts | 6 +- .../entities/command-menu-item.entity.ts | 12 +- .../enums/engine-component-key.enum.ts | 2 + ...o-flat-command-menu-item-to-create.util.ts | 30 +-- ...menu-item-to-command-menu-item-dto.util.ts | 2 +- .../prefill-workflow-command-menu-items.ts | 3 +- ...lat-command-menu-item-validator.service.ts | 124 +++++++-- .../workflow-trigger.workspace-service.ts | 2 + ...enu-item-creation.integration-spec.ts.snap | 179 ++++++++++-- ...-menu-item-update.integration-spec.ts.snap | 35 +++ ...and-menu-item-creation.integration-spec.ts | 63 ++++- ...mmand-menu-item-update.integration-spec.ts | 12 + ...and-menu-item-creation.integration-spec.ts | 6 + ...and-menu-item-deletion.integration-spec.ts | 3 + ...mmand-menu-item-update.integration-spec.ts | 2 + .../src/types/ConfirmationModalCaller.ts | 2 +- 103 files changed, 1296 insertions(+), 1131 deletions(-) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/constants/EngineComponentKeyComponentMap.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/EngineCommandMenuItem.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessFrontComponentCommandMenuItem.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/WorkflowCommandMenuItem.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/components/CommandRunner.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandMountRoot.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessFrontComponentRendererEngineCommand.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountEngineCommand.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountedEngineCommandContext.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/TriggerWorkflowVersionEngineCommand.tsx rename packages/twenty-front/src/modules/command-menu-item/engine-command/states/contexts/{EngineCommandComponentInstanceContext.ts => CommandComponentInstanceContext.ts} (58%) create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/states/headlessCommandContextApisState.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/states/mountedEngineCommandsState.ts rename packages/twenty-front/src/modules/command-menu-item/engine-command/types/{MountedEngineCommandContext.ts => HeadlessCommandContextApi.ts} (59%) create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessFrontComponentCommandContextApi.ts create mode 100644 packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessTriggerWorkflowVersionCommandContextApi.ts delete mode 100644 packages/twenty-front/src/modules/front-components/components/HeadlessFrontComponentMountRoot.tsx delete mode 100644 packages/twenty-front/src/modules/front-components/hooks/useMountHeadlessFrontComponent.ts delete mode 100644 packages/twenty-front/src/modules/front-components/hooks/useUnmountHeadlessFrontComponent.ts delete mode 100644 packages/twenty-front/src/modules/front-components/selectors/isHeadlessFrontComponentMountedFamilySelector.ts delete mode 100644 packages/twenty-front/src/modules/front-components/states/mountedHeadlessFrontComponentMapsState.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1774363913813-convert-engine-component-key-to-varchar.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 07a98063fd..ced462f7fb 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -913,7 +913,7 @@ export type CommandMenuItem = { availabilityType: CommandMenuItemAvailabilityType; conditionalAvailabilityExpression?: Maybe; createdAt: Scalars['DateTime']; - engineComponentKey?: Maybe; + engineComponentKey: EngineComponentKey; frontComponent?: Maybe; frontComponentId?: Maybe; hotKeys?: Maybe>; @@ -1095,7 +1095,7 @@ export type CreateCommandMenuItemInput = { availabilityObjectMetadataId?: InputMaybe; availabilityType?: InputMaybe; conditionalAvailabilityExpression?: InputMaybe; - engineComponentKey?: InputMaybe; + engineComponentKey: EngineComponentKey; frontComponentId?: InputMaybe; hotKeys?: InputMaybe>; icon?: InputMaybe; @@ -1571,6 +1571,7 @@ export enum EngineComponentKey { EXPORT_MULTIPLE_RECORDS = 'EXPORT_MULTIPLE_RECORDS', EXPORT_NOTE_TO_PDF = 'EXPORT_NOTE_TO_PDF', EXPORT_VIEW = 'EXPORT_VIEW', + FRONT_COMPONENT_RENDERER = 'FRONT_COMPONENT_RENDERER', GO_TO_COMPANIES = 'GO_TO_COMPANIES', GO_TO_DASHBOARDS = 'GO_TO_DASHBOARDS', GO_TO_NOTES = 'GO_TO_NOTES', @@ -1603,6 +1604,7 @@ export enum EngineComponentKey { STOP_WORKFLOW_RUN = 'STOP_WORKFLOW_RUN', TEST_WORKFLOW = 'TEST_WORKFLOW', TIDY_UP_WORKFLOW = 'TIDY_UP_WORKFLOW', + TRIGGER_WORKFLOW_VERSION = 'TRIGGER_WORKFLOW_VERSION', UPDATE_MULTIPLE_RECORDS = 'UPDATE_MULTIPLE_RECORDS', USE_AS_DRAFT_WORKFLOW_VERSION = 'USE_AS_DRAFT_WORKFLOW_VERSION', VIEW_PREVIOUS_AI_CHATS = 'VIEW_PREVIOUS_AI_CHATS' @@ -6573,12 +6575,12 @@ export type ValidatePasswordResetTokenQueryVariables = Exact<{ export type ValidatePasswordResetTokenQuery = { __typename?: 'Query', validatePasswordResetToken: { __typename?: 'ValidatePasswordResetToken', id: string, email: string, hasPassword: boolean } }; -export type CommandMenuItemFieldsFragment = { __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, engineComponentKey?: EngineComponentKey | null, label: string, icon?: string | null, shortLabel?: string | null, position: number, isPinned: boolean, hotKeys?: Array | null, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }; +export type CommandMenuItemFieldsFragment = { __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, engineComponentKey: EngineComponentKey, label: string, icon?: string | null, shortLabel?: string | null, position: number, isPinned: boolean, hotKeys?: Array | null, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }; export type FindManyCommandMenuItemsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyCommandMenuItemsQuery = { __typename?: 'Query', commandMenuItems: Array<{ __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, engineComponentKey?: EngineComponentKey | null, label: string, icon?: string | null, shortLabel?: string | null, position: number, isPinned: boolean, hotKeys?: Array | null, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }> }; +export type FindManyCommandMenuItemsQuery = { __typename?: 'Query', commandMenuItems: Array<{ __typename?: 'CommandMenuItem', id: string, workflowVersionId?: string | null, frontComponentId?: string | null, engineComponentKey: EngineComponentKey, label: string, icon?: string | null, shortLabel?: string | null, position: number, isPinned: boolean, hotKeys?: Array | null, conditionalAvailabilityExpression?: string | null, availabilityType: CommandMenuItemAvailabilityType, availabilityObjectMetadataId?: string | null, frontComponent?: { __typename?: 'FrontComponent', id: string, name: string, isHeadless: boolean } | null }> }; export type PageLayoutFragmentFragment = { __typename?: 'PageLayout', id: string, name: string, objectMetadataId?: string | null, type: PageLayoutType, defaultTabToFocusOnMobileAndSidePanelId?: string | null, createdAt: string, updatedAt: string, tabs?: Array<{ __typename?: 'PageLayoutTab', id: string, applicationId: string, title: string, icon?: string | null, position: number, layoutMode?: PageLayoutTabLayoutMode | null, isOverridden: boolean, pageLayoutId: string, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: string, title: string, type: WidgetType, objectMetadataId?: string | null, isOverridden: boolean, createdAt: string, updatedAt: string, deletedAt?: string | null, conditionalDisplay?: any | null, pageLayoutTabId: string, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, position?: { __typename?: 'PageLayoutWidgetCanvasPosition', layoutMode: PageLayoutTabLayoutMode } | { __typename?: 'PageLayoutWidgetGridPosition', layoutMode: PageLayoutTabLayoutMode, row: number, column: number, rowSpan: number, columnSpan: number } | { __typename?: 'PageLayoutWidgetVerticalListPosition', layoutMode: PageLayoutTabLayoutMode, index: number } | null, configuration: { __typename?: 'AggregateChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: string, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: string, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: string | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, layout: BarChartLayout, isCumulative?: boolean | null, splitMultiValueFields?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'CalendarConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'EmailsConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldConfiguration', configurationType: WidgetConfigurationType, fieldDisplayMode: FieldDisplayMode, fieldMetadataId: string } | { __typename?: 'FieldRichTextConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FieldsConfiguration', configurationType: WidgetConfigurationType, viewId?: string | null, newFieldDefaultVisibility?: boolean | null, shouldAllowUserToSeeHiddenFields?: boolean | null } | { __typename?: 'FilesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'FrontComponentConfiguration', configurationType: WidgetConfigurationType, frontComponentId: string } | { __typename?: 'GaugeChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', configurationType: WidgetConfigurationType, url?: string | null } | { __typename?: 'LineChartConfiguration', configurationType: WidgetConfigurationType, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: string, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, primaryAxisManualSortOrder?: Array | null, secondaryAxisGroupByFieldMetadataId?: string | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisManualSortOrder?: Array | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, splitMultiValueFields?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'NotesConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'PieChartConfiguration', configurationType: WidgetConfigurationType, groupByFieldMetadataId: string, aggregateFieldMetadataId: string, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, manualSortOrder?: Array | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, hideEmptyCategory?: boolean | null, splitMultiValueFields?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', configurationType: WidgetConfigurationType, body: { __typename?: 'RichTextBody', blocknote?: string | null, markdown?: string | null } } | { __typename?: 'TasksConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'TimelineConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'ViewConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowRunConfiguration', configurationType: WidgetConfigurationType } | { __typename?: 'WorkflowVersionConfiguration', configurationType: WidgetConfigurationType } }> | null }> | null }; diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 1d1e3f73dd..4f70a86339 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -19,8 +19,7 @@ import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-compo import { ApolloCoreProvider } from '@/object-metadata/components/ApolloCoreProvider'; import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider'; -import { HeadlessEngineCommandMountRoot } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandMountRoot'; -import { HeadlessFrontComponentMountRoot } from '@/front-components/components/HeadlessFrontComponentMountRoot'; +import { CommandRunner } from '@/command-menu-item/engine-command/components/CommandRunner'; import { SSEProvider } from '@/sse-db-event/components/SSEProvider'; import { SupportChatEffect } from '@/support/components/SupportChatEffect'; import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager'; @@ -71,8 +70,7 @@ export const AppRouterProviders = () => { - - + diff --git a/packages/twenty-front/src/modules/command-menu-item/constants/EngineComponentKeyComponentMap.tsx b/packages/twenty-front/src/modules/command-menu-item/constants/EngineComponentKeyComponentMap.tsx deleted file mode 100644 index d829e365c4..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/constants/EngineComponentKeyComponentMap.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import { CommandLink } from '@/command-menu-item/display/components/CommandLink'; -import { CommandMenuItemOpenSidePanelPage } from '@/command-menu-item/display/components/CommandMenuItemOpenSidePanelPage'; -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 { 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 { 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 { 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 { EditRecordPageLayoutSingleRecordCommand } from '@/command-menu-item/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand'; -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 { 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 { 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 { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { msg } from '@lingui/core/macro'; -import { AppPath, SettingsPath, SidePanelPages } from 'twenty-shared/types'; -import { IconHistory, IconSearch, IconSparkles } from 'twenty-ui/display'; -import { EngineComponentKey } from '~/generated-metadata/graphql'; - -export const ENGINE_COMPONENT_KEY_COMPONENT_MAP: Record< - EngineComponentKey, - React.ReactNode -> = { - [EngineComponentKey.CREATE_NEW_RECORD]: ( - - ), - [EngineComponentKey.DELETE_SINGLE_RECORD]: , - [EngineComponentKey.DELETE_MULTIPLE_RECORDS]: ( - - ), - [EngineComponentKey.RESTORE_SINGLE_RECORD]: , - [EngineComponentKey.RESTORE_MULTIPLE_RECORDS]: ( - - ), - [EngineComponentKey.DESTROY_SINGLE_RECORD]: , - [EngineComponentKey.DESTROY_MULTIPLE_RECORDS]: ( - - ), - [EngineComponentKey.ADD_TO_FAVORITES]: , - [EngineComponentKey.REMOVE_FROM_FAVORITES]: ( - - ), - [EngineComponentKey.MERGE_MULTIPLE_RECORDS]: , - [EngineComponentKey.DUPLICATE_DASHBOARD]: ( - - ), - [EngineComponentKey.DUPLICATE_WORKFLOW]: ( - - ), - [EngineComponentKey.ACTIVATE_WORKFLOW]: ( - - ), - [EngineComponentKey.DEACTIVATE_WORKFLOW]: ( - - ), - [EngineComponentKey.DISCARD_DRAFT_WORKFLOW]: ( - - ), - [EngineComponentKey.TEST_WORKFLOW]: , - [EngineComponentKey.STOP_WORKFLOW_RUN]: ( - - ), - [EngineComponentKey.USE_AS_DRAFT_WORKFLOW_VERSION]: ( - - ), - [EngineComponentKey.SAVE_DASHBOARD_LAYOUT]: ( - - ), - [EngineComponentKey.TIDY_UP_WORKFLOW]: , - [EngineComponentKey.NAVIGATE_TO_NEXT_RECORD]: ( - - ), - [EngineComponentKey.NAVIGATE_TO_PREVIOUS_RECORD]: ( - - ), - [EngineComponentKey.EXPORT_NOTE_TO_PDF]: , - [EngineComponentKey.EXPORT_FROM_RECORD_INDEX]: ( - - ), - [EngineComponentKey.EXPORT_FROM_RECORD_SHOW]: , - [EngineComponentKey.EXPORT_MULTIPLE_RECORDS]: ( - - ), - [EngineComponentKey.UPDATE_MULTIPLE_RECORDS]: ( - - ), - [EngineComponentKey.IMPORT_RECORDS]: ( - - ), - [EngineComponentKey.EXPORT_VIEW]: , - [EngineComponentKey.SEE_DELETED_RECORDS]: ( - - ), - [EngineComponentKey.CREATE_NEW_VIEW]: ( - - ), - [EngineComponentKey.HIDE_DELETED_RECORDS]: ( - - ), - [EngineComponentKey.EDIT_RECORD_PAGE_LAYOUT]: ( - - ), - [EngineComponentKey.EDIT_DASHBOARD_LAYOUT]: ( - - ), - [EngineComponentKey.CANCEL_DASHBOARD_LAYOUT]: ( - - ), - [EngineComponentKey.GO_TO_PEOPLE]: ( - - ), - [EngineComponentKey.GO_TO_COMPANIES]: ( - - ), - [EngineComponentKey.GO_TO_DASHBOARDS]: ( - - ), - [EngineComponentKey.GO_TO_OPPORTUNITIES]: ( - - ), - [EngineComponentKey.GO_TO_SETTINGS]: ( - - ), - [EngineComponentKey.GO_TO_TASKS]: ( - - ), - [EngineComponentKey.GO_TO_NOTES]: ( - - ), - [EngineComponentKey.GO_TO_WORKFLOWS]: ( - - ), - [EngineComponentKey.GO_TO_RUNS]: ( - - ), - [EngineComponentKey.SEARCH_RECORDS]: ( - - ), - [EngineComponentKey.SEARCH_RECORDS_FALLBACK]: ( - - ), - [EngineComponentKey.ASK_AI]: ( - - ), - [EngineComponentKey.VIEW_PREVIOUS_AI_CHATS]: ( - - ), - [EngineComponentKey.SEE_ACTIVE_VERSION_WORKFLOW]: ( - - ), - [EngineComponentKey.SEE_RUNS_WORKFLOW]: ( - - ), - [EngineComponentKey.SEE_VERSIONS_WORKFLOW]: ( - - ), - [EngineComponentKey.ADD_NODE_WORKFLOW]: ( - - ), - [EngineComponentKey.SEE_VERSION_WORKFLOW_RUN]: ( - - ), - [EngineComponentKey.SEE_WORKFLOW_WORKFLOW_RUN]: ( - - ), - [EngineComponentKey.SEE_RUNS_WORKFLOW_VERSION]: ( - - ), - [EngineComponentKey.SEE_WORKFLOW_WORKFLOW_VERSION]: ( - - ), - [EngineComponentKey.SEE_VERSIONS_WORKFLOW_VERSION]: ( - - ), -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemErrorBoundary.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemErrorBoundary.tsx index 4b3e7c2809..4ba8ff34de 100644 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemErrorBoundary.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemErrorBoundary.tsx @@ -1,22 +1,26 @@ +import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { type ReactNode } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; type CommandMenuItemErrorBoundaryProps = { children: ReactNode; - engineCommandId: string; shouldReportToSentry?: boolean; onError?: () => void; }; export const CommandMenuItemErrorBoundary = ({ children, - engineCommandId, shouldReportToSentry = false, onError, }: CommandMenuItemErrorBoundaryProps) => { const { enqueueErrorSnackBar } = useSnackBar(); + const commandMenuItemId = useAvailableComponentInstanceIdOrThrow( + CommandComponentInstanceContext, + ); + const handleError = async (error: Error) => { enqueueErrorSnackBar({ message: error.message }); @@ -41,7 +45,7 @@ export const CommandMenuItemErrorBoundary = ({ null} onError={handleError} - resetKeys={[engineCommandId]} + resetKeys={[commandMenuItemId]} > {children} diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/EngineCommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/EngineCommandMenuItem.tsx deleted file mode 100644 index d0c97ebf60..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/EngineCommandMenuItem.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { isEngineCommandMountedFamilySelector } from '@/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector'; -import { useMountEngineCommand } from '@/command-menu-item/engine-command/hooks/useMountEngineCommand'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; -import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; -import { type EngineComponentKey } from '~/generated-metadata/graphql'; - -import { HeadlessCommandMenuItem } from './HeadlessCommandMenuItem'; - -export const EngineCommandMenuItem = ({ - commandMenuItemId, - engineComponentKey, -}: { - commandMenuItemId: string; - engineComponentKey: EngineComponentKey; -}) => { - const mountEngineCommand = useMountEngineCommand(); - - const contextStoreInstanceId = useAvailableComponentInstanceIdOrThrow( - ContextStoreComponentInstanceContext, - ); - - const isMounted = useAtomFamilySelectorValue( - isEngineCommandMountedFamilySelector, - commandMenuItemId, - ); - - const handleClick = () => { - mountEngineCommand( - commandMenuItemId, - contextStoreInstanceId, - engineComponentKey, - ); - }; - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx index 6414cd3353..e8438de09a 100644 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx @@ -1,22 +1,35 @@ -import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext'; -import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu'; -import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; -import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { useContext } from 'react'; +import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext'; +import { useMountCommand } from '@/command-menu-item/engine-command/hooks/useMountCommand'; +import { isEngineCommandMountedFamilySelector } from '@/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector'; +import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu'; +import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; +import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { isDefined } from 'twenty-shared/utils'; +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; + import { CommandMenuItemDisplay } from './CommandMenuItemDisplay'; export const HeadlessCommandMenuItem = ({ - isMounted, - commandMenuItemId, - onClick, + item, }: { - isMounted: boolean; - commandMenuItemId: string; - onClick: () => void; + item: CommandMenuItemFieldsFragment; }) => { const commandMenuItemConfig = useContext(CommandConfigContext); + const mountCommand = useMountCommand(); + + const contextStoreInstanceId = useAvailableComponentInstanceIdOrThrow( + ContextStoreComponentInstanceContext, + ); + + const isMounted = useAtomFamilySelectorValue( + isEngineCommandMountedFamilySelector, + item.id, + ); const { closeCommandMenu } = useCloseCommandMenu({ closeSidePanelOnShowPageOptionsExecution: false, @@ -25,20 +38,29 @@ export const HeadlessCommandMenuItem = ({ const commandMenuItemProgress = useAtomFamilyStateValue( commandMenuItemProgressFamilyState, - commandMenuItemId, + item.id, ); if (!isDefined(commandMenuItemConfig)) { return null; } - const handleClick = () => { + const handleClick = async () => { if (isMounted) { return; } closeCommandMenu(); - onClick(); + + await mountCommand({ + engineCommandId: item.id, + contextStoreInstanceId, + engineComponentKey: item.engineComponentKey, + frontComponentId: item.frontComponentId ?? undefined, + workflowVersionId: item.workflowVersionId ?? undefined, + availabilityType: item.availabilityType, + availabilityObjectMetadataId: item.availabilityObjectMetadataId, + }); }; return ( diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessFrontComponentCommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessFrontComponentCommandMenuItem.tsx deleted file mode 100644 index 0e48277256..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessFrontComponentCommandMenuItem.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { useMountHeadlessFrontComponent } from '@/front-components/hooks/useMountHeadlessFrontComponent'; -import { isHeadlessFrontComponentMountedFamilySelector } from '@/front-components/selectors/isHeadlessFrontComponentMountedFamilySelector'; -import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; - -import { HeadlessCommandMenuItem } from './HeadlessCommandMenuItem'; - -// TODO: Some code is duplicated in this component and FrontComponentCommandMenuItem -// This will be refactored because the logic will differ between headless and non-headless front components. -export const HeadlessFrontComponentCommandMenuItem = ({ - frontComponentId, - commandMenuItemId, -}: { - frontComponentId: string; - commandMenuItemId: string; -}) => { - const mountHeadlessFrontComponent = useMountHeadlessFrontComponent(); - - const isMounted = useAtomFamilySelectorValue( - isHeadlessFrontComponentMountedFamilySelector, - frontComponentId, - ); - - const contextStoreTargetedRecordsRule = useAtomComponentStateValue( - contextStoreTargetedRecordsRuleComponentState, - ); - - const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue( - contextStoreCurrentObjectMetadataItemIdComponentState, - ); - - const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector); - - const currentObjectMetadataItem = objectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.id === contextStoreCurrentObjectMetadataItemId, - ); - - const selectedRecordIds = - contextStoreTargetedRecordsRule.mode === 'selection' - ? contextStoreTargetedRecordsRule.selectedRecordIds - : []; - - const recordId = - selectedRecordIds.length === 1 ? selectedRecordIds[0] : undefined; - - const objectNameSingular = currentObjectMetadataItem?.nameSingular; - - const handleClick = () => { - mountHeadlessFrontComponent(frontComponentId, { - commandMenuItemId, - recordId, - objectNameSingular, - }); - }; - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/WorkflowCommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/WorkflowCommandMenuItem.tsx deleted file mode 100644 index 4455816d53..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/WorkflowCommandMenuItem.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Command } from '@/command-menu-item/display/components/Command'; -import { isBulkRecordsManualTrigger } from '@/command-menu-item/record/utils/isBulkRecordsManualTrigger'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; -import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; -import { type WorkflowVersion } from '@/workflow/types/Workflow'; -import { useStore } from 'jotai'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; -import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; -import { isDefined } from 'twenty-shared/utils'; -import { - type CommandMenuItemAvailabilityType, - CommandMenuItemAvailabilityType as CommandMenuItemAvailabilityTypeEnum, -} from '~/generated-metadata/graphql'; - -export const WorkflowCommandMenuItem = ({ - workflowVersionId, - availabilityType, - availabilityObjectMetadataId, -}: { - workflowVersionId: string; - availabilityType: CommandMenuItemAvailabilityType; - availabilityObjectMetadataId?: string | null; -}) => { - const store = useStore(); - const { runWorkflowVersion } = useRunWorkflowVersion(); - - const { record: workflowVersion } = useFindOneRecord< - Pick - >({ - objectNameSingular: CoreObjectNameSingular.WorkflowVersion, - objectRecordId: workflowVersionId, - recordGqlFields: { id: true, workflowId: true, trigger: true }, - }); - - const contextStoreTargetedRecordsRule = useAtomComponentStateValue( - contextStoreTargetedRecordsRuleComponentState, - ); - - const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector); - - const selectedRecordIds = - contextStoreTargetedRecordsRule.mode === 'selection' - ? contextStoreTargetedRecordsRule.selectedRecordIds - : []; - - const handleClick = async () => { - if (!isDefined(workflowVersion)) { - return; - } - - switch (availabilityType) { - case CommandMenuItemAvailabilityTypeEnum.RECORD_SELECTION: { - if (selectedRecordIds.length === 0) { - return; - } - - const limitedSelectedRecordIds = selectedRecordIds.slice( - 0, - QUERY_MAX_RECORDS, - ); - - const objectMetadataItem = objectMetadataItems.find( - (metadata) => metadata.id === availabilityObjectMetadataId, - ); - - if ( - isDefined(workflowVersion.trigger) && - isBulkRecordsManualTrigger(workflowVersion.trigger) - ) { - const selectedRecords = limitedSelectedRecordIds - .map((recordId) => - store.get(recordStoreFamilyState.atomFamily(recordId)), - ) - .filter(isDefined); - - await runWorkflowVersion({ - workflowId: workflowVersion.workflowId, - workflowVersionId: workflowVersion.id, - payload: isDefined(objectMetadataItem) - ? { [objectMetadataItem.namePlural]: selectedRecords } - : undefined, - }); - - return; - } - - for (const selectedRecordId of limitedSelectedRecordIds) { - const selectedRecord = store.get( - recordStoreFamilyState.atomFamily(selectedRecordId), - ); - - if (!isDefined(selectedRecord)) { - continue; - } - - await runWorkflowVersion({ - workflowId: workflowVersion.workflowId, - workflowVersionId: workflowVersion.id, - payload: selectedRecord, - }); - } - - return; - } - case CommandMenuItemAvailabilityTypeEnum.GLOBAL: - case CommandMenuItemAvailabilityTypeEnum.FALLBACK: { - await runWorkflowVersion({ - workflowId: workflowVersion.workflowId, - workflowVersionId: workflowVersion.id, - }); - - return; - } - } - }; - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/CommandRunner.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/CommandRunner.tsx new file mode 100644 index 0000000000..b2fa1acefb --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/CommandRunner.tsx @@ -0,0 +1,38 @@ +import { CommandMenuItemErrorBoundary } from '@/command-menu-item/display/components/CommandMenuItemErrorBoundary'; +import { ENGINE_COMPONENT_KEY_COMPONENT_MAP } from '@/command-menu-item/engine-command/constants/EngineComponentKeyHeadlessComponentMap'; +import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand'; +import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext'; +import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; + +export const CommandRunner = () => { + const headlessCommandContextApis = useAtomStateValue( + headlessCommandContextApisState, + ); + const unmountCommand = useUnmountCommand(); + + return ( + <> + {[...headlessCommandContextApis.entries()].map( + ([commandMenuItemId, context]) => ( + + + unmountCommand(commandMenuItemId)} + > + {ENGINE_COMPONENT_KEY_COMPONENT_MAP[context.engineComponentKey]} + + + + ), + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect.tsx index a069c3b1e5..61e22b885e 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect.tsx @@ -4,8 +4,8 @@ import { type ReactNode, useEffect } from 'react'; import { COMMAND_MENU_CONFIRMATION_MODAL_RESULT_BROWSER_EVENT_NAME } from '@/command-menu-item/confirmation-modal/constants/CommandMenuItemConfirmationModalResultBrowserEventName'; import { useCommandMenuConfirmationModal } from '@/command-menu-item/confirmation-modal/hooks/useCommandMenuConfirmationModal'; import { type CommandMenuConfirmationModalResultBrowserEventDetail } from '@/command-menu-item/confirmation-modal/types/CommandMenuConfirmationModalResultBrowserEventDetail'; -import { useUnmountEngineCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand'; -import { EngineCommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext'; +import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand'; +import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { type ButtonAccent } from 'twenty-ui/input'; @@ -26,10 +26,11 @@ export const HeadlessConfirmationModalEngineCommandEffect = ({ }: HeadlessConfirmationModalEngineCommandEffectProps) => { const { isInitializedRef, setIsInitialized } = useIsHeadlessEngineCommandEffectInitialized(); - const engineCommandId = useAvailableComponentInstanceIdOrThrow( - EngineCommandComponentInstanceContext, + + const commandMenuItemId = useAvailableComponentInstanceIdOrThrow( + CommandComponentInstanceContext, ); - const unmountEngineCommand = useUnmountEngineCommand(); + const unmountCommand = useUnmountCommand(); const { openConfirmationModal } = useCommandMenuConfirmationModal(); useEffect(() => { @@ -40,7 +41,7 @@ export const HeadlessConfirmationModalEngineCommandEffect = ({ setIsInitialized(true); openConfirmationModal({ - caller: { type: 'engineCommand', engineCommandId }, + caller: { type: 'commandMenuItem', commandMenuItemId }, title, subtitle, confirmButtonText, @@ -49,7 +50,7 @@ export const HeadlessConfirmationModalEngineCommandEffect = ({ }, [ isInitializedRef, setIsInitialized, - engineCommandId, + commandMenuItemId, openConfirmationModal, title, subtitle, @@ -65,8 +66,8 @@ export const HeadlessConfirmationModalEngineCommandEffect = ({ const caller = customEvent.detail.caller; if ( - caller.type !== 'engineCommand' || - caller.engineCommandId !== engineCommandId + caller.type !== 'commandMenuItem' || + caller.commandMenuItemId !== commandMenuItemId ) { return; } @@ -75,7 +76,7 @@ export const HeadlessConfirmationModalEngineCommandEffect = ({ await execute(); } - unmountEngineCommand(engineCommandId); + unmountCommand(commandMenuItemId); }; window.addEventListener( @@ -89,7 +90,7 @@ export const HeadlessConfirmationModalEngineCommandEffect = ({ handleConfirmationResult, ); }; - }, [engineCommandId, execute, unmountEngineCommand]); + }, [execute, commandMenuItemId, unmountCommand]); return null; }; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandMountRoot.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandMountRoot.tsx deleted file mode 100644 index d5983f842f..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandMountRoot.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { CommandMenuItemErrorBoundary } from '@/command-menu-item/display/components/CommandMenuItemErrorBoundary'; -import { ENGINE_COMPONENT_KEY_HEADLESS_COMPONENT_MAP } from '@/command-menu-item/engine-command/constants/EngineComponentKeyHeadlessComponentMap'; -import { useUnmountEngineCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand'; -import { EngineCommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext'; -import { mountedEngineCommandsState } from '@/command-menu-item/engine-command/states/mountedEngineCommandsState'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; - -export const HeadlessEngineCommandMountRoot = () => { - const mountedEngineCommands = useAtomStateValue(mountedEngineCommandsState); - const unmountEngineCommand = useUnmountEngineCommand(); - - return ( - <> - {[...mountedEngineCommands.entries()].map( - ([engineCommandId, mountContext]) => ( - unmountEngineCommand(engineCommandId)} - > - - - { - ENGINE_COMPONENT_KEY_HEADLESS_COMPONENT_MAP[ - mountContext.engineComponentKey - ] - } - - - - ), - )} - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect.tsx index ae9d04840a..710c897c7a 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect.tsx @@ -1,6 +1,6 @@ import { useIsHeadlessEngineCommandEffectInitialized } from '@/command-menu-item/engine-command/hooks/useIsHeadlessEngineCommandEffectInitialized'; -import { useUnmountEngineCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand'; -import { EngineCommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext'; +import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand'; +import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useEffect } from 'react'; @@ -17,11 +17,11 @@ export const HeadlessEngineCommandWrapperEffect = ({ const { isInitializedRef, setIsInitialized } = useIsHeadlessEngineCommandEffectInitialized(); - const engineCommandId = useAvailableComponentInstanceIdOrThrow( - EngineCommandComponentInstanceContext, + const commandMenuItemId = useAvailableComponentInstanceIdOrThrow( + CommandComponentInstanceContext, ); - const unmountEngineCommand = useUnmountEngineCommand(); + const unmountCommand = useUnmountCommand(); const { enqueueErrorSnackBar } = useSnackBar(); @@ -35,7 +35,7 @@ export const HeadlessEngineCommandWrapperEffect = ({ const run = async () => { await execute(); - unmountEngineCommand(engineCommandId); + unmountCommand(commandMenuItemId); }; run(); @@ -44,8 +44,8 @@ export const HeadlessEngineCommandWrapperEffect = ({ ready, isInitializedRef, setIsInitialized, - engineCommandId, - unmountEngineCommand, + commandMenuItemId, + unmountCommand, enqueueErrorSnackBar, ]); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessFrontComponentRendererEngineCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessFrontComponentRendererEngineCommand.tsx new file mode 100644 index 0000000000..19f949e894 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/components/HeadlessFrontComponentRendererEngineCommand.tsx @@ -0,0 +1,60 @@ +import { Suspense, lazy } from 'react'; + +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; +import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext'; +import { isHeadlessFrontComponentCommandContextApi } from '@/command-menu-item/engine-command/utils/isHeadlessFrontComponentCommandContextApi'; +import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { isDefined } from 'twenty-shared/utils'; +import { PageLayoutType } from '~/generated-metadata/graphql'; + +const FrontComponentRenderer = lazy(() => + import('@/front-components/components/FrontComponentRenderer').then( + (module) => ({ default: module.FrontComponentRenderer }), + ), +); + +export const HeadlessFrontComponentRendererEngineCommand = () => { + const commandMenuItemId = useAvailableComponentInstanceIdOrThrow( + CommandComponentInstanceContext, + ); + + const context = useHeadlessCommandContextApi(); + + if (!isHeadlessFrontComponentCommandContextApi(context)) { + throw new Error( + 'Context is not a headless front component command context API', + ); + } + + const objectNameSingular = context.objectMetadataItem?.nameSingular; + + const recordId = + context.selectedRecords.length === 1 + ? context.selectedRecords[0].id + : undefined; + + // TODO: Remove layout rendering provider once we have refactored FrontComponentRenderer to have one headless renderer and a standard renderer + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/constants/EngineComponentKeyHeadlessComponentMap.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/constants/EngineComponentKeyHeadlessComponentMap.tsx index e50cd6b420..c85a0c8ba9 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/constants/EngineComponentKeyHeadlessComponentMap.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/constants/EngineComponentKeyHeadlessComponentMap.tsx @@ -42,13 +42,15 @@ import { SeeRunsWorkflowSingleRecordCommand } from '@/command-menu-item/engine-c import { SeeVersionsWorkflowSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand'; import { TestWorkflowSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/workflow/components/TestWorkflowSingleRecordCommand'; import { TidyUpWorkflowSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand'; +import { HeadlessFrontComponentRendererEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessFrontComponentRendererEngineCommand'; +import { TriggerWorkflowVersionEngineCommand } from '@/command-menu-item/engine-command/record/components/TriggerWorkflowVersionEngineCommand'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { msg } from '@lingui/core/macro'; import { AppPath, SettingsPath, SidePanelPages } from 'twenty-shared/types'; import { IconHistory, IconSearch, IconSparkles } from 'twenty-ui/display'; import { EngineComponentKey } from '~/generated-metadata/graphql'; -export const ENGINE_COMPONENT_KEY_HEADLESS_COMPONENT_MAP: Record< +export const ENGINE_COMPONENT_KEY_COMPONENT_MAP: Record< EngineComponentKey, React.ReactNode > = { @@ -251,4 +253,10 @@ export const ENGINE_COMPONENT_KEY_HEADLESS_COMPONENT_MAP: Record< [EngineComponentKey.SEE_VERSIONS_WORKFLOW_VERSION]: ( ), + [EngineComponentKey.TRIGGER_WORKFLOW_VERSION]: ( + + ), + [EngineComponentKey.FRONT_COMPONENT_RENDERER]: ( + + ), }; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.ts new file mode 100644 index 0000000000..6af0c39c04 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation.ts @@ -0,0 +1,132 @@ +import { useCallback } from 'react'; + +import { + type HeadlessCommandContextApi, + type HeadlessEngineCommandContextApi, +} from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi'; +import { buildTriggerWorkflowVersionPayloads } from '@/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads'; +import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; +import { useLazyFindOneRecord } from '@/object-record/hooks/useLazyFindOneRecord'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { type WorkflowVersion } from '@/workflow/types/Workflow'; +import { t } from '@lingui/core/macro'; +import { useStore } from 'jotai'; +import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; +import { isDefined, isNonEmptyArray } from 'twenty-shared/utils'; +import { + CommandMenuItemAvailabilityType as CommandMenuItemAvailabilityTypeEnum, + type CommandMenuItemAvailabilityType, +} from '~/generated-metadata/graphql'; + +type WorkflowVersionRecord = Pick< + WorkflowVersion, + 'id' | 'workflowId' | 'trigger' | '__typename' +>; + +type BuildTriggerWorkflowVersionCommandStateParams = { + headlessEngineCommandContextApi: HeadlessEngineCommandContextApi; + workflowVersionId: string; + availabilityType: CommandMenuItemAvailabilityType; + availabilityObjectMetadataId?: string | null; +}; + +export const useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation = + () => { + const store = useStore(); + const { enqueueWarningSnackBar } = useSnackBar(); + + const { findOneRecord: findOneWorkflowVersion } = + useLazyFindOneRecord({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + recordGqlFields: { id: true, workflowId: true, trigger: true }, + }); + + const fetchWorkflowVersion = useCallback( + async (versionId: string): Promise => { + let record: WorkflowVersionRecord | undefined; + + await findOneWorkflowVersion({ + objectRecordId: versionId, + onCompleted: (data) => { + record = data; + }, + }); + + return record; + }, + [findOneWorkflowVersion], + ); + + const enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation = + useCallback( + async ({ + headlessEngineCommandContextApi, + workflowVersionId, + availabilityType, + availabilityObjectMetadataId, + }: BuildTriggerWorkflowVersionCommandStateParams): Promise< + HeadlessCommandContextApi | undefined + > => { + const workflowVersion = await fetchWorkflowVersion(workflowVersionId); + + if (!isDefined(workflowVersion)) { + return undefined; + } + + const selectedRecordIds = + headlessEngineCommandContextApi.targetedRecordsRule.mode === + 'selection' + ? headlessEngineCommandContextApi.targetedRecordsRule + .selectedRecordIds + : []; + + 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 objectMetadataItems = store.get( + objectMetadataItemsSelector.atom, + ); + + const payloads = buildTriggerWorkflowVersionPayloads({ + store, + trigger: workflowVersion.trigger, + availabilityType, + availabilityObjectMetadataId, + objectMetadataItems, + selectedRecordIds, + }); + + if ( + availabilityType === + CommandMenuItemAvailabilityTypeEnum.RECORD_SELECTION && + !isNonEmptyArray(payloads) + ) { + return undefined; + } + + return { + ...headlessEngineCommandContextApi, + workflowId: workflowVersion.workflowId, + workflowVersionId: workflowVersion.id, + payloads, + }; + }, + [store, fetchWorkflowVersion, enqueueWarningSnackBar], + ); + + return { + enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation, + }; + }; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi.ts new file mode 100644 index 0000000000..abac26488c --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi.ts @@ -0,0 +1,25 @@ +import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext'; +import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { isDefined } from 'twenty-shared/utils'; + +export const useHeadlessCommandContextApi = () => { + const engineCommandId = useAvailableComponentInstanceIdOrThrow( + CommandComponentInstanceContext, + ); + + const headlessCommandContextApis = useAtomStateValue( + headlessCommandContextApisState, + ); + const headlessCommandContextApi = + headlessCommandContextApis.get(engineCommandId); + + if (!isDefined(headlessCommandContextApi)) { + throw new Error( + 'Headless command context API not found. Make sure the command was mounted via the command mount flow.', + ); + } + + return headlessCommandContextApi; +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts new file mode 100644 index 0000000000..8a083573d9 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountCommand.ts @@ -0,0 +1,78 @@ +import { useCallback } from 'react'; + +import { useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation } from '@/command-menu-item/engine-command/hooks/useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation'; +import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState'; +import { buildHeadlessCommandContextApi } from '@/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi'; +import { useStore } from 'jotai'; +import { isDefined } from 'twenty-shared/utils'; +import { + type CommandMenuItemAvailabilityType, + type EngineComponentKey, +} from '~/generated-metadata/graphql'; + +type MountCommandParams = { + engineCommandId: string; + contextStoreInstanceId: string; + engineComponentKey: EngineComponentKey; + frontComponentId?: string; + workflowVersionId?: string; + availabilityType?: CommandMenuItemAvailabilityType; + availabilityObjectMetadataId?: string | null; +}; + +export const useMountCommand = () => { + const store = useStore(); + + const { + enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation, + } = useEnrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation(); + + const mountCommand = useCallback( + async ({ + engineCommandId, + contextStoreInstanceId, + engineComponentKey, + frontComponentId, + workflowVersionId, + availabilityType, + availabilityObjectMetadataId, + }: MountCommandParams) => { + const headlessEngineCommandContextApi = buildHeadlessCommandContextApi({ + store, + contextStoreInstanceId, + engineComponentKey, + }); + + const commandState = isDefined(frontComponentId) + ? { ...headlessEngineCommandContextApi, frontComponentId } + : isDefined(workflowVersionId) && isDefined(availabilityType) + ? await enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation( + { + headlessEngineCommandContextApi, + workflowVersionId, + availabilityType, + availabilityObjectMetadataId, + }, + ) + : headlessEngineCommandContextApi; + + if (!isDefined(commandState)) { + return; + } + + store.set(headlessCommandContextApisState.atom, (previousMap) => { + const newMap = new Map(previousMap); + + newMap.set(engineCommandId, commandState); + + return newMap; + }); + }, + [ + store, + enrichHeadlessCommandContextApiWithWorkflowVersionTriggerInformation, + ], + ); + + return mountCommand; +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountEngineCommand.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountEngineCommand.ts deleted file mode 100644 index 00f7370414..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountEngineCommand.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { useCallback } from 'react'; - -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { mountedEngineCommandsState } from '@/command-menu-item/engine-command/states/mountedEngineCommandsState'; -import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState'; -import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -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 { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; -import { useStore } from 'jotai'; -import { isDefined } from 'twenty-shared/utils'; -import { type EngineComponentKey } from '~/generated-metadata/graphql'; - -export const useMountEngineCommand = () => { - const store = useStore(); - - const mountEngineCommand = useCallback( - ( - engineCommandId: string, - contextStoreInstanceId: string, - engineComponentKey: EngineComponentKey, - ) => { - const objectMetadataItemId = store.get( - contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({ - instanceId: contextStoreInstanceId, - }), - ); - - const objectMetadataItems = store.get(objectMetadataItemsSelector.atom); - const objectMetadataItem = objectMetadataItems.find( - (item) => item.id === objectMetadataItemId, - ); - - const currentViewId = store.get( - contextStoreCurrentViewIdComponentState.atomFamily({ - instanceId: contextStoreInstanceId, - }), - ); - - const targetedRecordsRule = store.get( - contextStoreTargetedRecordsRuleComponentState.atomFamily({ - instanceId: contextStoreInstanceId, - }), - ); - - const selectedRecords = ( - targetedRecordsRule.mode === 'selection' - ? targetedRecordsRule.selectedRecordIds - : [] - ) - .map((id) => store.get(recordStoreFamilyState.atomFamily(id))) - .filter(isDefined); - - const filters = store.get( - contextStoreFiltersComponentState.atomFamily({ - instanceId: contextStoreInstanceId, - }), - ); - - const filterGroups = store.get( - contextStoreFilterGroupsComponentState.atomFamily({ - instanceId: contextStoreInstanceId, - }), - ); - - const anyFieldFilterValue = store.get( - contextStoreAnyFieldFilterValueComponentState.atomFamily({ - instanceId: contextStoreInstanceId, - }), - ); - - const currentWorkspaceMember = store.get( - currentWorkspaceMemberState.atom, - ); - const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const userTimezone = - currentWorkspaceMember?.timeZone !== 'system' - ? (currentWorkspaceMember?.timeZone ?? systemTimeZone) - : systemTimeZone; - - const graphqlFilter = isDefined(objectMetadataItem) - ? computeContextStoreFilters({ - contextStoreTargetedRecordsRule: targetedRecordsRule, - contextStoreFilters: filters, - contextStoreFilterGroups: filterGroups, - objectMetadataItem, - filterValueDependencies: { - currentWorkspaceMemberId: currentWorkspaceMember?.id, - timeZone: userTimezone, - }, - contextStoreAnyFieldFilterValue: anyFieldFilterValue, - }) - : null; - - const recordIndexId = - objectMetadataItem && currentViewId - ? getRecordIndexIdFromObjectNamePluralAndViewId( - objectMetadataItem.namePlural, - currentViewId, - ) - : null; - - store.set(mountedEngineCommandsState.atom, (previousMap) => { - const newMap = new Map(previousMap); - - newMap.set(engineCommandId, { - engineComponentKey, - contextStoreInstanceId, - objectMetadataItem: objectMetadataItem ?? null, - currentViewId, - recordIndexId, - targetedRecordsRule, - selectedRecords, - graphqlFilter, - }); - - return newMap; - }); - }, - [store], - ); - - return mountEngineCommand; -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountedEngineCommandContext.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountedEngineCommandContext.ts deleted file mode 100644 index 39f144ae91..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useMountedEngineCommandContext.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { EngineCommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext'; -import { mountedEngineCommandsState } from '@/command-menu-item/engine-command/states/mountedEngineCommandsState'; -import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { isDefined } from 'twenty-shared/utils'; - -export const useMountedEngineCommandContext = () => { - const engineCommandId = useAvailableComponentInstanceIdOrThrow( - EngineCommandComponentInstanceContext, - ); - - const mountedEngineCommands = useAtomStateValue(mountedEngineCommandsState); - const context = mountedEngineCommands.get(engineCommandId); - - if (!isDefined(context)) { - throw new Error( - 'Engine command mount context not found. Make sure the command was mounted via the engine command mount flow.', - ); - } - - return context; -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useUnmountEngineCommand.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useUnmountEngineCommand.ts index 777a950784..1e06ad2b5f 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useUnmountEngineCommand.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/hooks/useUnmountEngineCommand.ts @@ -1,15 +1,15 @@ import { useCallback } from 'react'; -import { mountedEngineCommandsState } from '@/command-menu-item/engine-command/states/mountedEngineCommandsState'; +import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState'; import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; import { useStore } from 'jotai'; -export const useUnmountEngineCommand = () => { +export const useUnmountCommand = () => { const store = useStore(); - const unmountEngineCommand = useCallback( + const unmountCommand = useCallback( (engineCommandId: string) => { - store.set(mountedEngineCommandsState.atom, (previousMap) => { + store.set(headlessCommandContextApisState.atom, (previousMap) => { const newMap = new Map(previousMap); newMap.delete(engineCommandId); @@ -25,5 +25,5 @@ export const useUnmountEngineCommand = () => { [store], ); - return unmountEngineCommand; + return unmountCommand; }; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/TriggerWorkflowVersionEngineCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/TriggerWorkflowVersionEngineCommand.tsx new file mode 100644 index 0000000000..d713208c40 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/components/TriggerWorkflowVersionEngineCommand.tsx @@ -0,0 +1,39 @@ +import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; +import { isHeadlessTriggerWorkflowVersionCommandContextApi } from '@/command-menu-item/engine-command/utils/isHeadlessTriggerWorkflowVersionCommandContextApi'; +import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; +import { useCallback } from 'react'; +import { isNonEmptyArray } from 'twenty-shared/utils'; + +export const TriggerWorkflowVersionEngineCommand = () => { + const mountedCommandState = useHeadlessCommandContextApi(); + + const { runWorkflowVersion } = useRunWorkflowVersion(); + + const execute = useCallback(async () => { + if ( + !isHeadlessTriggerWorkflowVersionCommandContextApi(mountedCommandState) + ) { + return; + } + + if (!isNonEmptyArray(mountedCommandState.payloads)) { + await runWorkflowVersion({ + workflowId: mountedCommandState.workflowId, + workflowVersionId: mountedCommandState.workflowVersionId, + }); + + return; + } + + for (const payload of mountedCommandState.payloads) { + await runWorkflowVersion({ + workflowId: mountedCommandState.workflowId, + workflowVersionId: mountedCommandState.workflowVersionId, + payload, + }); + } + }, [runWorkflowVersion, mountedCommandState]); + + return ; +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx index 5a24591a0a..9c3b92ff20 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DeleteMultipleRecordsCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; 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'; @@ -8,7 +8,7 @@ import { isDefined } from 'twenty-shared/utils'; export const DeleteMultipleRecordsCommand = () => { const { recordIndexId, objectMetadataItem, graphqlFilter } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); if ( !isDefined(recordIndexId) || diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx index 42f6db5b3b..13ed419b0e 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/DestroyMultipleRecordsCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessConfirmationModalEngineCommandEffect } from '@/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; 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'; @@ -10,7 +10,7 @@ import { isDefined } from 'twenty-shared/utils'; export const DestroyMultipleRecordsCommand = () => { const { recordIndexId, objectMetadataItem, graphqlFilter } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); if ( !isDefined(recordIndexId) || diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/ExportMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/ExportMultipleRecordsCommand.tsx index c6814ca0f9..fc1260dfa6 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/ExportMultipleRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/ExportMultipleRecordsCommand.tsx @@ -1,6 +1,6 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; -import { EngineCommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; +import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext'; import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem'; import { useRecordIndexExportRecords } from '@/object-record/record-index/export/hooks/useRecordIndexExportRecords'; @@ -43,11 +43,10 @@ const ExportMultipleRecordsCommandContent = ({ }; export const ExportMultipleRecordsCommand = () => { - const { objectMetadataItem, recordIndexId } = - useMountedEngineCommandContext(); + const { objectMetadataItem, recordIndexId } = useHeadlessCommandContextApi(); const engineCommandId = useAvailableComponentInstanceIdOrThrow( - EngineCommandComponentInstanceContext, + CommandComponentInstanceContext, ); const setCommandMenuItemProgress = useSetAtomFamilyState( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/MergeMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/MergeMultipleRecordsCommand.tsx index 8d2b992283..22750fba95 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/MergeMultipleRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/MergeMultipleRecordsCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useOpenMergeRecordsPageInSidePanel } from '@/side-panel/hooks/useOpenMergeRecordsPageInSidePanel'; import { isDefined } from 'twenty-shared/utils'; export const MergeMultipleRecordsCommand = () => { const { objectMetadataItem, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const selectedRecordIds = selectedRecords.map((record) => record.id); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx index 4ec0ac2d48..174b864f88 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/RestoreMultipleRecordsCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessConfirmationModalEngineCommandEffect } from '@/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize'; import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords'; @@ -11,7 +11,7 @@ import { isDefined } from 'twenty-shared/utils'; export const RestoreMultipleRecordsCommand = () => { const { recordIndexId, objectMetadataItem, graphqlFilter } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); if ( !isDefined(recordIndexId) || diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx index 6ad8a2cc76..d1f631f22b 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/multiple-records/components/UpdateMultipleRecordsCommand.tsx @@ -1,9 +1,9 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useOpenUpdateMultipleRecordsPageInSidePanel } from '@/side-panel/hooks/useOpenUpdateMultipleRecordsPageInSidePanel'; export const UpdateMultipleRecordsCommand = () => { - const { contextStoreInstanceId } = useMountedEngineCommandContext(); + const { contextStoreInstanceId } = useHeadlessCommandContextApi(); const { openUpdateMultipleRecordsPageInSidePanel } = useOpenUpdateMultipleRecordsPageInSidePanel({ diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx index 9cf9b38964..0abaefb09e 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand.tsx @@ -1,11 +1,10 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { isDefined } from 'twenty-shared/utils'; export const CreateNewIndexRecordNoSelectionRecordCommand = () => { - const { objectMetadataItem, recordIndexId } = - useMountedEngineCommandContext(); + const { objectMetadataItem, recordIndexId } = useHeadlessCommandContextApi(); if (!isDefined(objectMetadataItem) || !isDefined(recordIndexId)) { throw new Error( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx index b0666f0ccc..69a29fea0e 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/CreateNewViewNoSelectionRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId'; @@ -8,7 +8,7 @@ import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/sta import { isDefined } from 'twenty-shared/utils'; export const CreateNewViewNoSelectionRecordCommand = () => { - const { currentViewId, recordIndexId } = useMountedEngineCommandContext(); + const { currentViewId, recordIndexId } = useHeadlessCommandContextApi(); const { openDropdown } = useOpenDropdown(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx index a6ace7cad9..82e13b4de8 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/HideDeletedRecordsNoSelectionRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; 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'; @@ -8,8 +8,7 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use import { isDefined } from 'twenty-shared/utils'; export const HideDeletedRecordsNoSelectionRecordCommand = () => { - const { objectMetadataItem, recordIndexId } = - useMountedEngineCommandContext(); + const { objectMetadataItem, recordIndexId } = useHeadlessCommandContextApi(); if (!isDefined(objectMetadataItem) || !isDefined(recordIndexId)) { throw new Error( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx index 0433b0fffc..abbcd9fb97 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/ImportRecordsNoSelectionRecordCommand.tsx @@ -1,10 +1,10 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useOpenObjectRecordsSpreadsheetImportDialog } from '@/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog'; import { isDefined } from 'twenty-shared/utils'; export const ImportRecordsNoSelectionRecordCommand = () => { - const { objectMetadataItem } = useMountedEngineCommandContext(); + const { objectMetadataItem } = useHeadlessCommandContextApi(); if (!isDefined(objectMetadataItem)) { throw new Error('Object metadata item is required to import records'); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx index 2f2a5aacc6..f7c710cebe 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/no-selection/components/SeeDeletedRecordsNoSelectionRecordCommand.tsx @@ -1,11 +1,10 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; import { isDefined } from 'twenty-shared/utils'; export const SeeDeletedRecordsNoSelectionRecordCommand = () => { - const { objectMetadataItem, recordIndexId } = - useMountedEngineCommandContext(); + const { objectMetadataItem, recordIndexId } = useHeadlessCommandContextApi(); if (!isDefined(objectMetadataItem) || !isDefined(recordIndexId)) { throw new Error( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx index eb4c38c27a..945b622716 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/AddToFavoritesSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useCreateNavigationMenuItem } from '@/navigation-menu-item/common/hooks/useCreateNavigationMenuItem'; import { isDefined } from 'twenty-shared/utils'; export const AddToFavoritesSingleRecordCommand = () => { const { objectMetadataItem, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DeleteSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DeleteSingleRecordCommand.tsx index 75d1969fa4..75f84b29ac 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DeleteSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DeleteSingleRecordCommand.tsx @@ -1,7 +1,7 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; -import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useRemoveNavigationMenuItemByTargetRecordId } from '@/navigation-menu-item/common/hooks/useRemoveNavigationMenuItemByTargetRecordId'; +import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; @@ -9,7 +9,7 @@ import { isDefined } from 'twenty-shared/utils'; export const DeleteSingleRecordCommand = () => { const { recordIndexId, objectMetadataItem, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DestroySingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DestroySingleRecordCommand.tsx index 890c817bdd..822ead630a 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DestroySingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/DestroySingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessConfirmationModalEngineCommandEffect } from '@/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; @@ -10,7 +10,7 @@ import { useNavigateApp } from '~/hooks/useNavigateApp'; export const DestroySingleRecordCommand = () => { const { recordIndexId, objectMetadataItem, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportNoteSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportNoteSingleRecordCommand.tsx index 4da3425668..a0b66c5739 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportNoteSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportNoteSingleRecordCommand.tsx @@ -1,9 +1,9 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { isDefined } from 'twenty-shared/utils'; export const ExportNoteSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; const recordId = selectedRecord?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportSingleRecordCommand.tsx index c1a5968674..6b3d34cb03 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/ExportSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useExportSingleRecord } from '@/object-record/record-show/hooks/useExportSingleRecord'; import { isDefined } from 'twenty-shared/utils'; export const ExportSingleRecordCommand = () => { const { objectMetadataItem, currentViewId, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx index cee4b730c4..3590491116 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToNextRecordSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination'; import { isDefined } from 'twenty-shared/utils'; export const NavigateToNextRecordSingleRecordCommand = () => { const { objectMetadataItem, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx index 27e6684576..a7ce6dabca 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/NavigateToPreviousRecordSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination'; import { isDefined } from 'twenty-shared/utils'; export const NavigateToPreviousRecordSingleRecordCommand = () => { const { objectMetadataItem, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx index 15d6258939..d0983b2918 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RemoveFromFavoritesSingleRecordCommand.tsx @@ -1,12 +1,12 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/common/hooks/useDeleteNavigationMenuItem'; import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData'; import { isDefined } from 'twenty-shared/utils'; export const RemoveFromFavoritesSingleRecordCommand = () => { const { selectedRecords, objectMetadataItem } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RestoreSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RestoreSingleRecordCommand.tsx index b22e03c1bd..a4fcd70878 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RestoreSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/components/RestoreSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessConfirmationModalEngineCommandEffect } from '@/command-menu-item/engine-command/components/HeadlessConfirmationModalEngineCommandEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords'; import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; @@ -8,7 +8,7 @@ import { isDefined } from 'twenty-shared/utils'; export const RestoreSingleRecordCommand = () => { const { recordIndexId, objectMetadataItem, selectedRecords } = - useMountedEngineCommandContext(); + useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx index c150cf2b91..c70e80a394 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/CancelDashboardSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useResetDraftPageLayoutToPersistedPageLayout } from '@/page-layout/hooks/useResetDraftPageLayoutToPersistedPageLayout'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; @@ -8,7 +8,7 @@ import { isDefined } from 'twenty-shared/utils'; import { PageLayoutType } from '~/generated-metadata/graphql'; export const CancelDashboardSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; if (!isDefined(selectedRecord)) { diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx index 9279a8e28c..61a5bdd207 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/DuplicateDashboardSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useDuplicateDashboard } from '@/dashboards/hooks/useDuplicateDashboard'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useLingui } from '@lingui/react/macro'; @@ -9,7 +9,7 @@ import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const DuplicateDashboardSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const { duplicateDashboard } = useDuplicateDashboard(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx index 5e374aca65..37f820c561 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/EditDashboardSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; import { isDefined } from 'twenty-shared/utils'; import { useResetLocationHash } from 'twenty-ui/utilities'; export const EditDashboardSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; if (!isDefined(selectedRecord)) { diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx index fb26507faa..5bd3744f84 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/dashboard/components/SaveDashboardSingleRecordCommand.tsx @@ -1,12 +1,12 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { isDefined } from 'twenty-shared/utils'; export const SaveDashboardSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; if (!isDefined(selectedRecord)) { 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 index 5706bb89fe..52fb1a0d0d 100644 --- 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 @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +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'; @@ -10,7 +10,7 @@ import { isDefined } from 'twenty-shared/utils'; import { PageLayoutType } from '~/generated-metadata/graphql'; export const CancelRecordPageLayoutSingleRecordCommand = () => { - const { objectMetadataItem } = useMountedEngineCommandContext(); + const { objectMetadataItem } = useHeadlessCommandContextApi(); if (!isDefined(objectMetadataItem)) { throw new Error( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx index 05032e916b..1a02679264 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/record-page-layout/components/EditRecordPageLayoutSingleRecordCommand.tsx @@ -1,12 +1,12 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; import { isDefined } from 'twenty-shared/utils'; import { useResetLocationHash } from 'twenty-ui/utilities'; export const EditRecordPageLayoutSingleRecordCommand = () => { - const { objectMetadataItem } = useMountedEngineCommandContext(); + const { objectMetadataItem } = useHeadlessCommandContextApi(); if (!isDefined(objectMetadataItem)) { throw new Error( 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 index e121fff299..0c7758208b 100644 --- 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 @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useRecordPageLayoutIdFromRecordStoreOrThrow } from '@/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow'; import { useSaveFieldsWidgetGroups } from '@/page-layout/hooks/useSaveFieldsWidgetGroups'; import { useSavePageLayout } from '@/page-layout/hooks/useSavePageLayout'; @@ -8,7 +8,7 @@ import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { isDefined } from 'twenty-shared/utils'; export const SaveRecordPageLayoutSingleRecordCommand = () => { - const { objectMetadataItem } = useMountedEngineCommandContext(); + const { objectMetadataItem } = useHeadlessCommandContextApi(); if (!isDefined(objectMetadataItem)) { throw new Error( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx index 75ce2dcd88..52bde380d9 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeVersionWorkflowRunSingleRecordCommand.tsx @@ -1,10 +1,10 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeVersionWorkflowRunSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; if ( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx index 38c8536e04..2b31fde2cd 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/SeeWorkflowWorkflowRunSingleRecordCommand.tsx @@ -1,10 +1,10 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; -import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; +import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeWorkflowWorkflowRunSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; if (!isDefined(selectedRecord) || !isDefined(selectedRecord?.workflow?.id)) { diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx index e6c07295a8..91a912d42a 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-runs/components/StopWorkflowRunSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize'; import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; import { useStopWorkflowRun } from '@/workflow/hooks/useStopWorkflowRun'; @@ -7,8 +7,7 @@ import { CoreObjectNameSingular } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const StopWorkflowRunSingleRecordCommand = () => { - const { targetedRecordsRule, graphqlFilter } = - useMountedEngineCommandContext(); + const { targetedRecordsRule, graphqlFilter } = useHeadlessCommandContextApi(); const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({ objectNameSingular: CoreObjectNameSingular.WorkflowRun, diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx index 7174f5b55e..0caf574065 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeRunsWorkflowVersionSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; @@ -37,7 +37,7 @@ const SeeRunsWorkflowVersionSingleRecordCommandContent = ({ }; export const SeeRunsWorkflowVersionSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const selectedRecord = selectedRecords[0]; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx index 83d66ac0ae..dbd76ee570 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeVersionsWorkflowVersionSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; @@ -30,7 +30,7 @@ const SeeVersionsWorkflowVersionSingleRecordCommandContent = ({ }; export const SeeVersionsWorkflowVersionSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; if (!isDefined(selectedRecord) || !isDefined(selectedRecord.workflowId)) { diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx index bc730b144e..1ff86ae93d 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/SeeWorkflowWorkflowVersionSingleRecordCommand.tsx @@ -1,10 +1,10 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeWorkflowWorkflowVersionSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const selectedRecord = selectedRecords[0]; if (!isDefined(selectedRecord) || !isDefined(selectedRecord?.workflow?.id)) { diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx index c0b93c58ea..d7cfda85ca 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow-versions/components/UseAsDraftWorkflowVersionSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; 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'; @@ -66,7 +66,7 @@ const UseAsDraftWorkflowVersionSingleRecordCommandContent = ({ }; export const UseAsDraftWorkflowVersionSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const workflowVersion = useWorkflowVersion(recordId ?? ''); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx index 8a7cbf9f13..2931853940 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/ActivateWorkflowSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-shared/utils'; export const ActivateWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const { activateWorkflowVersion } = useActivateWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx index 9788bde43c..7efbbc4b30 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/AddNodeWorkflowSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; 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 { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx index 1bdbbf4f4f..c5e96507e8 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DeactivateWorkflowSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-shared/utils'; export const DeactivateWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx index aaeee32e7f..6f9e2ace89 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DiscardDraftWorkflowSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-shared/utils'; export const DiscardDraftWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx index 437c2d7000..89779f799f 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/DuplicateWorkflowSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useDuplicateWorkflow } from '@/workflow/hooks/useDuplicateWorkflow'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; @@ -10,7 +10,7 @@ import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const DuplicateWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const workflow = useWorkflowWithCurrentVersion(recordId ?? ''); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx index 16cec564c0..32aad6a179 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeActiveVersionWorkflowSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion'; import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeActiveVersionWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx index c02f76c22b..3f01cbf8b9 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeRunsWorkflowSingleRecordCommand.tsx @@ -1,12 +1,12 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeRunsWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx index b77b3723df..490bb70d8e 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/SeeVersionsWorkflowSingleRecordCommand.tsx @@ -1,12 +1,12 @@ import { HeadlessNavigateEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessNavigateEngineCommand'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeVersionsWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const workflowWithCurrentVersion = useWorkflowWithCurrentVersion( diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx index dc0f3b6c4d..2cddfd1ca3 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TestWorkflowSingleRecordCommand.tsx @@ -1,11 +1,11 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { isDefined } from 'twenty-shared/utils'; export const TestWorkflowSingleRecordCommand = () => { - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const { runWorkflowVersion } = useRunWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx index df4843e0af..cd4abf430b 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand.tsx @@ -1,5 +1,5 @@ import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect'; -import { useMountedEngineCommandContext } from '@/command-menu-item/engine-command/hooks/useMountedEngineCommandContext'; +import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi'; import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow'; import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId'; import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState'; @@ -9,7 +9,7 @@ import { isDefined } from 'twenty-shared/utils'; export const TidyUpWorkflowSingleRecordCommand = () => { const store = useStore(); - const { selectedRecords } = useMountedEngineCommandContext(); + const { selectedRecords } = useHeadlessCommandContextApi(); const recordId = selectedRecords[0]?.id; const { tidyUpWorkflowVersion } = useTidyUpWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector.ts index a2d9558132..32820b8fa9 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector.ts @@ -1,4 +1,4 @@ -import { mountedEngineCommandsState } from '@/command-menu-item/engine-command/states/mountedEngineCommandsState'; +import { headlessCommandContextApisState } from '@/command-menu-item/engine-command/states/headlessCommandContextApisState'; import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; export const isEngineCommandMountedFamilySelector = createAtomFamilySelector< @@ -9,8 +9,8 @@ export const isEngineCommandMountedFamilySelector = createAtomFamilySelector< get: (engineCommandId: string) => ({ get }) => { - const mountedMap = get(mountedEngineCommandsState); + const headlessCommandContextApis = get(headlessCommandContextApisState); - return mountedMap.has(engineCommandId); + return headlessCommandContextApis.has(engineCommandId); }, }); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext.ts similarity index 58% rename from packages/twenty-front/src/modules/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext.ts rename to packages/twenty-front/src/modules/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext.ts index a2a1c93c95..afd904cf8c 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/states/contexts/EngineCommandComponentInstanceContext.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext.ts @@ -1,4 +1,3 @@ import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; -export const EngineCommandComponentInstanceContext = - createComponentInstanceContext(); +export const CommandComponentInstanceContext = createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/states/headlessCommandContextApisState.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/states/headlessCommandContextApisState.ts new file mode 100644 index 0000000000..a897fe1b67 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/states/headlessCommandContextApisState.ts @@ -0,0 +1,9 @@ +import { type HeadlessCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi'; +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; + +export const headlessCommandContextApisState = createAtomState< + Map +>({ + key: 'headlessCommandContextApisState', + defaultValue: new Map(), +}); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/states/mountedEngineCommandsState.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/states/mountedEngineCommandsState.ts deleted file mode 100644 index 8036b58641..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/states/mountedEngineCommandsState.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type MountedEngineCommandState } from '@/command-menu-item/engine-command/types/MountedEngineCommandContext'; -import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; - -export const mountedEngineCommandsState = createAtomState< - Map ->({ - key: 'mountedEngineCommandsState', - defaultValue: new Map(), -}); diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/types/MountedEngineCommandContext.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/types/HeadlessCommandContextApi.ts similarity index 59% rename from packages/twenty-front/src/modules/command-menu-item/engine-command/types/MountedEngineCommandContext.ts rename to packages/twenty-front/src/modules/command-menu-item/engine-command/types/HeadlessCommandContextApi.ts index 5e1fd8f45a..fa3e6fd25a 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/types/MountedEngineCommandContext.ts +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/types/HeadlessCommandContextApi.ts @@ -7,7 +7,7 @@ import { } from 'twenty-shared/types'; import { type EngineComponentKey } from '~/generated-metadata/graphql'; -export type MountedEngineCommandState = { +export type HeadlessEngineCommandContextApi = { engineComponentKey: EngineComponentKey; contextStoreInstanceId: string; objectMetadataItem: Nullable; @@ -17,3 +17,20 @@ export type MountedEngineCommandState = { selectedRecords: ObjectRecord[]; graphqlFilter: Nullable; }; + +export type HeadlessFrontComponentCommandContextApi = + HeadlessEngineCommandContextApi & { + frontComponentId: string; + }; + +export type HeadlessTriggerWorkflowVersionCommandContextApi = + HeadlessEngineCommandContextApi & { + workflowId: string; + workflowVersionId: string; + payloads: Record[]; + }; + +export type HeadlessCommandContextApi = + | HeadlessEngineCommandContextApi + | HeadlessFrontComponentCommandContextApi + | HeadlessTriggerWorkflowVersionCommandContextApi; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts new file mode 100644 index 0000000000..593e0d83be --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildHeadlessCommandContextApi.ts @@ -0,0 +1,119 @@ +import type { Store } from 'jotai/vanilla/store'; + +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { type HeadlessEngineCommandContextApi } from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi'; +import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState'; +import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; +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 { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; +import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; +import { isDefined } from 'twenty-shared/utils'; +import { type EngineComponentKey } from '~/generated-metadata/graphql'; + +export const buildHeadlessCommandContextApi = ({ + store, + contextStoreInstanceId, + engineComponentKey, +}: { + store: Store; + contextStoreInstanceId: string; + engineComponentKey: EngineComponentKey; +}): HeadlessEngineCommandContextApi => { + const objectMetadataItemId = store.get( + contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({ + instanceId: contextStoreInstanceId, + }), + ); + + const objectMetadataItems = store.get(objectMetadataItemsSelector.atom); + + const objectMetadataItem = objectMetadataItems.find( + (item: EnrichedObjectMetadataItem) => item.id === objectMetadataItemId, + ); + + const currentViewId = store.get( + contextStoreCurrentViewIdComponentState.atomFamily({ + instanceId: contextStoreInstanceId, + }), + ); + + const targetedRecordsRule = store.get( + contextStoreTargetedRecordsRuleComponentState.atomFamily({ + instanceId: contextStoreInstanceId, + }), + ); + + const selectedRecords = ( + targetedRecordsRule.mode === 'selection' + ? targetedRecordsRule.selectedRecordIds + : [] + ) + .map((id: string) => store.get(recordStoreFamilyState.atomFamily(id))) + .filter(isDefined); + + const filters = store.get( + contextStoreFiltersComponentState.atomFamily({ + instanceId: contextStoreInstanceId, + }), + ); + + const filterGroups = store.get( + contextStoreFilterGroupsComponentState.atomFamily({ + instanceId: contextStoreInstanceId, + }), + ); + + const anyFieldFilterValue = store.get( + contextStoreAnyFieldFilterValueComponentState.atomFamily({ + instanceId: contextStoreInstanceId, + }), + ); + + const currentWorkspaceMember = store.get(currentWorkspaceMemberState.atom); + + const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + const userTimezone = + currentWorkspaceMember?.timeZone !== 'system' + ? (currentWorkspaceMember?.timeZone ?? systemTimeZone) + : systemTimeZone; + + const graphqlFilter = isDefined(objectMetadataItem) + ? computeContextStoreFilters({ + contextStoreTargetedRecordsRule: targetedRecordsRule, + contextStoreFilters: filters, + contextStoreFilterGroups: filterGroups, + objectMetadataItem, + filterValueDependencies: { + currentWorkspaceMemberId: currentWorkspaceMember?.id, + timeZone: userTimezone, + }, + contextStoreAnyFieldFilterValue: anyFieldFilterValue, + }) + : null; + + const recordIndexId = + objectMetadataItem && currentViewId + ? getRecordIndexIdFromObjectNamePluralAndViewId( + objectMetadataItem.namePlural, + currentViewId, + ) + : null; + + return { + engineComponentKey, + contextStoreInstanceId, + objectMetadataItem: objectMetadataItem ?? null, + currentViewId, + recordIndexId, + targetedRecordsRule, + selectedRecords, + graphqlFilter, + }; +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts new file mode 100644 index 0000000000..c42c76cf68 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/buildTriggerWorkflowVersionPayloads.ts @@ -0,0 +1,84 @@ +import type { Store } from 'jotai/vanilla/store'; + +import { isBulkRecordsManualTrigger } from '@/command-menu-item/record/utils/isBulkRecordsManualTrigger'; +import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { type WorkflowTrigger } from '@/workflow/types/Workflow'; +import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; +import { isDefined } from 'twenty-shared/utils'; +import { + type CommandMenuItemAvailabilityType, + CommandMenuItemAvailabilityType as CommandMenuItemAvailabilityTypeEnum, +} from '~/generated-metadata/graphql'; + +export const buildTriggerWorkflowVersionPayloads = ({ + store, + trigger, + availabilityType, + availabilityObjectMetadataId, + objectMetadataItems, + selectedRecordIds, +}: { + store: Store; + trigger: WorkflowTrigger | null; + availabilityType: CommandMenuItemAvailabilityType; + availabilityObjectMetadataId?: string | null; + objectMetadataItems: EnrichedObjectMetadataItem[]; + selectedRecordIds: string[]; +}): Record[] => { + const payloads: Record[] = []; + + switch (availabilityType) { + case CommandMenuItemAvailabilityTypeEnum.RECORD_SELECTION: { + if (selectedRecordIds.length === 0) { + return payloads; + } + + const limitedSelectedRecordIds = selectedRecordIds.slice( + 0, + QUERY_MAX_RECORDS, + ); + + const objectMetadataItem = objectMetadataItems.find( + (metadata) => metadata.id === availabilityObjectMetadataId, + ); + + if (isDefined(trigger) && isBulkRecordsManualTrigger(trigger)) { + const selectedRecords = limitedSelectedRecordIds + .map((recordId) => + store.get(recordStoreFamilyState.atomFamily(recordId)), + ) + .filter(isDefined); + + if (isDefined(objectMetadataItem)) { + payloads.push({ + [objectMetadataItem.namePlural]: selectedRecords, + }); + } + + return payloads; + } + + for (const selectedRecordId of limitedSelectedRecordIds) { + const selectedRecord = store.get( + recordStoreFamilyState.atomFamily(selectedRecordId), + ); + + if (!isDefined(selectedRecord)) { + continue; + } + + payloads.push(selectedRecord); + } + + return payloads; + } + case CommandMenuItemAvailabilityTypeEnum.GLOBAL: + case CommandMenuItemAvailabilityTypeEnum.FALLBACK: { + return payloads; + } + default: { + return payloads; + } + } +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessFrontComponentCommandContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessFrontComponentCommandContextApi.ts new file mode 100644 index 0000000000..c425e3ac81 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessFrontComponentCommandContextApi.ts @@ -0,0 +1,9 @@ +import { + type HeadlessCommandContextApi, + type HeadlessFrontComponentCommandContextApi, +} from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi'; + +export const isHeadlessFrontComponentCommandContextApi = ( + state: HeadlessCommandContextApi, +): state is HeadlessFrontComponentCommandContextApi => + 'frontComponentId' in state; diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessTriggerWorkflowVersionCommandContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessTriggerWorkflowVersionCommandContextApi.ts new file mode 100644 index 0000000000..a4bc8a1c32 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/utils/isHeadlessTriggerWorkflowVersionCommandContextApi.ts @@ -0,0 +1,9 @@ +import { + type HeadlessCommandContextApi, + type HeadlessTriggerWorkflowVersionCommandContextApi, +} from '@/command-menu-item/engine-command/types/HeadlessCommandContextApi'; + +export const isHeadlessTriggerWorkflowVersionCommandContextApi = ( + state: HeadlessCommandContextApi, +): state is HeadlessTriggerWorkflowVersionCommandContextApi => + 'workflowId' in state; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx index 8e3e28d43c..057a28b8d0 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/server-items/hooks/useConvertBackendItemToCommandMenuItemConfig.tsx @@ -1,7 +1,5 @@ -import { EngineCommandMenuItem } from '@/command-menu-item/display/components/EngineCommandMenuItem'; import { FrontComponentCommandMenuItem } from '@/command-menu-item/display/components/FrontComponentCommandMenuItem'; -import { HeadlessFrontComponentCommandMenuItem } from '@/command-menu-item/display/components/HeadlessFrontComponentCommandMenuItem'; -import { WorkflowCommandMenuItem } from '@/command-menu-item/display/components/WorkflowCommandMenuItem'; +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'; @@ -19,6 +17,7 @@ import { useIcons } from 'twenty-ui/display'; import { CommandMenuItemAvailabilityType, type CommandMenuItemFieldsFragment, + EngineComponentKey, } from '~/generated-metadata/graphql'; const resolveScope = ( @@ -36,16 +35,15 @@ const resolveType = ( if (item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK) { return CommandMenuItemType.Fallback; } - if (isDefined(item.engineComponentKey)) { - return CommandMenuItemType.Standard; + + switch (item.engineComponentKey) { + case EngineComponentKey.FRONT_COMPONENT_RENDERER: + return CommandMenuItemType.FrontComponent; + case EngineComponentKey.TRIGGER_WORKFLOW_VERSION: + return CommandMenuItemType.WorkflowRun; + default: + return CommandMenuItemType.Standard; } - if (isDefined(item.frontComponentId)) { - return CommandMenuItemType.FrontComponent; - } - if (isDefined(item.workflowVersionId)) { - return CommandMenuItemType.WorkflowRun; - } - return CommandMenuItemType.Standard; }; // TODO: Remove this hook once we finish refactoring and we use @@ -101,33 +99,22 @@ export const useConvertBackendItemToCommandMenuItemConfig = () => { context: commandMenuContextApi, }); - const component = isDefined(item.engineComponentKey) ? ( - - ) : isDefined(item.frontComponentId) ? ( - item.frontComponent?.isHeadless === true ? ( - - ) : ( - - ) - ) : isDefined(item.workflowVersionId) ? ( - - ) : null; + const component = (() => { + if ( + item.engineComponentKey === + EngineComponentKey.FRONT_COMPONENT_RENDERER && + isDefined(item.frontComponentId) && + item.frontComponent?.isHeadless !== true + ) { + return ( + + ); + } - if (!isDefined(component)) { - return; - } + return ; + })(); return { type: resolveType(item), diff --git a/packages/twenty-front/src/modules/front-components/components/HeadlessFrontComponentMountRoot.tsx b/packages/twenty-front/src/modules/front-components/components/HeadlessFrontComponentMountRoot.tsx deleted file mode 100644 index e2674f4fab..0000000000 --- a/packages/twenty-front/src/modules/front-components/components/HeadlessFrontComponentMountRoot.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Suspense, lazy } from 'react'; - -import { CommandMenuItemErrorBoundary } from '@/command-menu-item/display/components/CommandMenuItemErrorBoundary'; -import { useUnmountHeadlessFrontComponent } from '@/front-components/hooks/useUnmountHeadlessFrontComponent'; -import { mountedHeadlessFrontComponentMapsState } from '@/front-components/states/mountedHeadlessFrontComponentMapsState'; -import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { isDefined } from 'twenty-shared/utils'; -import { PageLayoutType } from '~/generated-metadata/graphql'; - -const FrontComponentRenderer = lazy(() => - import('@/front-components/components/FrontComponentRenderer').then( - (module) => ({ default: module.FrontComponentRenderer }), - ), -); - -export const HeadlessFrontComponentMountRoot = () => { - const mountedHeadlessFrontComponentMaps = useAtomStateValue( - mountedHeadlessFrontComponentMapsState, - ); - - const unmountHeadlessFrontComponent = useUnmountHeadlessFrontComponent(); - - return ( - <> - {[...mountedHeadlessFrontComponentMaps.entries()].map( - ([frontComponentId, mountContext]) => ( - unmountHeadlessFrontComponent(frontComponentId)} - > - - - - - - - ), - )} - - ); -}; diff --git a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts index b7a0e08885..3d3cc2730c 100644 --- a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts +++ b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts @@ -7,9 +7,9 @@ import { type AppPath, type EnqueueSnackbarParams } from 'twenty-shared/types'; import { currentUserState } from '@/auth/states/currentUserState'; import { useCommandMenuConfirmationModal } from '@/command-menu-item/confirmation-modal/hooks/useCommandMenuConfirmationModal'; +import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand'; import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; import { useRequestApplicationTokenRefresh } from '@/front-components/hooks/useRequestApplicationTokenRefresh'; -import { useUnmountHeadlessFrontComponent } from '@/front-components/hooks/useUnmountHeadlessFrontComponent'; import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState'; @@ -40,7 +40,7 @@ export const useFrontComponentExecutionContext = ({ const { navigateSidePanel } = useNavigateSidePanel(); const setSidePanelSearch = useSetAtomState(sidePanelSearchState); const { getIcon } = useIcons(); - const unmountHeadlessFrontComponent = useUnmountHeadlessFrontComponent(); + const unmountEngineCommand = useUnmountCommand(); const { enqueueSuccessSnackBar, enqueueErrorSnackBar, @@ -133,7 +133,9 @@ export const useFrontComponentExecutionContext = ({ const unmountFrontComponent: FrontComponentHostCommunicationApi['unmountFrontComponent'] = async () => { - unmountHeadlessFrontComponent(frontComponentId); + if (isDefined(commandMenuItemId)) { + unmountEngineCommand(commandMenuItemId); + } }; const closeSidePanel: FrontComponentHostCommunicationApi['closeSidePanel'] = diff --git a/packages/twenty-front/src/modules/front-components/hooks/useMountHeadlessFrontComponent.ts b/packages/twenty-front/src/modules/front-components/hooks/useMountHeadlessFrontComponent.ts deleted file mode 100644 index a40ba9b33d..0000000000 --- a/packages/twenty-front/src/modules/front-components/hooks/useMountHeadlessFrontComponent.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useCallback } from 'react'; - -import { - type HeadlessFrontComponentMountContext, - mountedHeadlessFrontComponentMapsState, -} from '@/front-components/states/mountedHeadlessFrontComponentMapsState'; -import { useStore } from 'jotai'; - -export const useMountHeadlessFrontComponent = () => { - const store = useStore(); - const mountHeadlessFrontComponent = useCallback( - (frontComponentId: string, context: HeadlessFrontComponentMountContext) => { - store.set(mountedHeadlessFrontComponentMapsState.atom, (previousMap) => { - const next = new Map(previousMap); - next.set(frontComponentId, context); - return next; - }); - }, - [store], - ); - - return mountHeadlessFrontComponent; -}; diff --git a/packages/twenty-front/src/modules/front-components/hooks/useUnmountHeadlessFrontComponent.ts b/packages/twenty-front/src/modules/front-components/hooks/useUnmountHeadlessFrontComponent.ts deleted file mode 100644 index a36e312ac7..0000000000 --- a/packages/twenty-front/src/modules/front-components/hooks/useUnmountHeadlessFrontComponent.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useCallback } from 'react'; - -import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; -import { mountedHeadlessFrontComponentMapsState } from '@/front-components/states/mountedHeadlessFrontComponentMapsState'; -import { useStore } from 'jotai'; - -export const useUnmountHeadlessFrontComponent = () => { - const store = useStore(); - const unmountHeadlessFrontComponent = useCallback( - (frontComponentId: string) => { - const currentMap = store.get(mountedHeadlessFrontComponentMapsState.atom); - const mountContext = currentMap.get(frontComponentId); - - store.set(mountedHeadlessFrontComponentMapsState.atom, (previousMap) => { - const next = new Map(previousMap); - next.delete(frontComponentId); - return next; - }); - - if (mountContext) { - store.set( - commandMenuItemProgressFamilyState.atomFamily( - mountContext.commandMenuItemId, - ), - undefined, - ); - } - }, - [store], - ); - - return unmountHeadlessFrontComponent; -}; diff --git a/packages/twenty-front/src/modules/front-components/selectors/isHeadlessFrontComponentMountedFamilySelector.ts b/packages/twenty-front/src/modules/front-components/selectors/isHeadlessFrontComponentMountedFamilySelector.ts deleted file mode 100644 index 8bea98e372..0000000000 --- a/packages/twenty-front/src/modules/front-components/selectors/isHeadlessFrontComponentMountedFamilySelector.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { mountedHeadlessFrontComponentMapsState } from '@/front-components/states/mountedHeadlessFrontComponentMapsState'; -import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector'; - -export const isHeadlessFrontComponentMountedFamilySelector = - createAtomFamilySelector({ - key: 'isHeadlessFrontComponentMountedFamilySelector', - get: - (frontComponentId: string) => - ({ get }) => { - const mountedMap = get(mountedHeadlessFrontComponentMapsState); - - return mountedMap.has(frontComponentId); - }, - }); diff --git a/packages/twenty-front/src/modules/front-components/states/mountedHeadlessFrontComponentMapsState.ts b/packages/twenty-front/src/modules/front-components/states/mountedHeadlessFrontComponentMapsState.ts deleted file mode 100644 index 5bada25950..0000000000 --- a/packages/twenty-front/src/modules/front-components/states/mountedHeadlessFrontComponentMapsState.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; - -export type HeadlessFrontComponentMountContext = { - commandMenuItemId: string; - recordId?: string; - objectNameSingular?: string; -}; - -export const mountedHeadlessFrontComponentMapsState = createAtomState< - Map ->({ - key: 'mountedHeadlessFrontComponentMapsState', - defaultValue: new Map(), -}); diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql index f86e936f62..a7bb4159b1 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql @@ -2067,7 +2067,7 @@ type CommandMenuItem { workflowVersionId: UUID frontComponentId: UUID frontComponent: FrontComponent - engineComponentKey: EngineComponentKey + engineComponentKey: EngineComponentKey! label: String! icon: String shortLabel: String @@ -2140,6 +2140,8 @@ enum EngineComponentKey { SEARCH_RECORDS_FALLBACK ASK_AI VIEW_PREVIOUS_AI_CHATS + TRIGGER_WORKFLOW_VERSION + FRONT_COMPONENT_RENDERER } enum CommandMenuItemAvailabilityType { @@ -3896,7 +3898,7 @@ input UpdateLogicFunctionFromSourceInputUpdates { input CreateCommandMenuItemInput { workflowVersionId: UUID frontComponentId: UUID - engineComponentKey: EngineComponentKey + engineComponentKey: EngineComponentKey! label: String! icon: String shortLabel: String diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts index 0925f45145..6e1b2ec6af 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts @@ -1788,7 +1788,7 @@ export interface CommandMenuItem { workflowVersionId?: Scalars['UUID'] frontComponentId?: Scalars['UUID'] frontComponent?: FrontComponent - engineComponentKey?: EngineComponentKey + engineComponentKey: EngineComponentKey label: Scalars['String'] icon?: Scalars['String'] shortLabel?: Scalars['String'] @@ -1804,7 +1804,7 @@ export interface CommandMenuItem { __typename: 'CommandMenuItem' } -export type EngineComponentKey = 'NAVIGATE_TO_NEXT_RECORD' | 'NAVIGATE_TO_PREVIOUS_RECORD' | 'CREATE_NEW_RECORD' | 'DELETE_SINGLE_RECORD' | 'DELETE_MULTIPLE_RECORDS' | 'RESTORE_SINGLE_RECORD' | 'RESTORE_MULTIPLE_RECORDS' | 'DESTROY_SINGLE_RECORD' | 'DESTROY_MULTIPLE_RECORDS' | 'ADD_TO_FAVORITES' | 'REMOVE_FROM_FAVORITES' | 'EXPORT_NOTE_TO_PDF' | 'EXPORT_FROM_RECORD_INDEX' | 'EXPORT_FROM_RECORD_SHOW' | 'UPDATE_MULTIPLE_RECORDS' | 'MERGE_MULTIPLE_RECORDS' | 'EXPORT_MULTIPLE_RECORDS' | 'IMPORT_RECORDS' | 'EXPORT_VIEW' | 'SEE_DELETED_RECORDS' | 'CREATE_NEW_VIEW' | 'HIDE_DELETED_RECORDS' | 'GO_TO_PEOPLE' | 'GO_TO_COMPANIES' | 'GO_TO_DASHBOARDS' | 'GO_TO_OPPORTUNITIES' | 'GO_TO_SETTINGS' | 'GO_TO_TASKS' | 'GO_TO_NOTES' | 'EDIT_RECORD_PAGE_LAYOUT' | 'EDIT_DASHBOARD_LAYOUT' | 'SAVE_DASHBOARD_LAYOUT' | 'CANCEL_DASHBOARD_LAYOUT' | 'DUPLICATE_DASHBOARD' | 'GO_TO_WORKFLOWS' | 'ACTIVATE_WORKFLOW' | 'DEACTIVATE_WORKFLOW' | 'DISCARD_DRAFT_WORKFLOW' | 'TEST_WORKFLOW' | 'SEE_ACTIVE_VERSION_WORKFLOW' | 'SEE_RUNS_WORKFLOW' | 'SEE_VERSIONS_WORKFLOW' | 'ADD_NODE_WORKFLOW' | 'TIDY_UP_WORKFLOW' | 'DUPLICATE_WORKFLOW' | 'GO_TO_RUNS' | 'SEE_VERSION_WORKFLOW_RUN' | 'SEE_WORKFLOW_WORKFLOW_RUN' | 'STOP_WORKFLOW_RUN' | 'SEE_RUNS_WORKFLOW_VERSION' | 'SEE_WORKFLOW_WORKFLOW_VERSION' | 'USE_AS_DRAFT_WORKFLOW_VERSION' | 'SEE_VERSIONS_WORKFLOW_VERSION' | 'SEARCH_RECORDS' | 'SEARCH_RECORDS_FALLBACK' | 'ASK_AI' | 'VIEW_PREVIOUS_AI_CHATS' +export type EngineComponentKey = 'NAVIGATE_TO_NEXT_RECORD' | 'NAVIGATE_TO_PREVIOUS_RECORD' | 'CREATE_NEW_RECORD' | 'DELETE_SINGLE_RECORD' | 'DELETE_MULTIPLE_RECORDS' | 'RESTORE_SINGLE_RECORD' | 'RESTORE_MULTIPLE_RECORDS' | 'DESTROY_SINGLE_RECORD' | 'DESTROY_MULTIPLE_RECORDS' | 'ADD_TO_FAVORITES' | 'REMOVE_FROM_FAVORITES' | 'EXPORT_NOTE_TO_PDF' | 'EXPORT_FROM_RECORD_INDEX' | 'EXPORT_FROM_RECORD_SHOW' | 'UPDATE_MULTIPLE_RECORDS' | 'MERGE_MULTIPLE_RECORDS' | 'EXPORT_MULTIPLE_RECORDS' | 'IMPORT_RECORDS' | 'EXPORT_VIEW' | 'SEE_DELETED_RECORDS' | 'CREATE_NEW_VIEW' | 'HIDE_DELETED_RECORDS' | 'GO_TO_PEOPLE' | 'GO_TO_COMPANIES' | 'GO_TO_DASHBOARDS' | 'GO_TO_OPPORTUNITIES' | 'GO_TO_SETTINGS' | 'GO_TO_TASKS' | 'GO_TO_NOTES' | 'EDIT_RECORD_PAGE_LAYOUT' | 'EDIT_DASHBOARD_LAYOUT' | 'SAVE_DASHBOARD_LAYOUT' | 'CANCEL_DASHBOARD_LAYOUT' | 'DUPLICATE_DASHBOARD' | 'GO_TO_WORKFLOWS' | 'ACTIVATE_WORKFLOW' | 'DEACTIVATE_WORKFLOW' | 'DISCARD_DRAFT_WORKFLOW' | 'TEST_WORKFLOW' | 'SEE_ACTIVE_VERSION_WORKFLOW' | 'SEE_RUNS_WORKFLOW' | 'SEE_VERSIONS_WORKFLOW' | 'ADD_NODE_WORKFLOW' | 'TIDY_UP_WORKFLOW' | 'DUPLICATE_WORKFLOW' | 'GO_TO_RUNS' | 'SEE_VERSION_WORKFLOW_RUN' | 'SEE_WORKFLOW_WORKFLOW_RUN' | 'STOP_WORKFLOW_RUN' | 'SEE_RUNS_WORKFLOW_VERSION' | 'SEE_WORKFLOW_WORKFLOW_VERSION' | 'USE_AS_DRAFT_WORKFLOW_VERSION' | 'SEE_VERSIONS_WORKFLOW_VERSION' | 'SEARCH_RECORDS' | 'SEARCH_RECORDS_FALLBACK' | 'ASK_AI' | 'VIEW_PREVIOUS_AI_CHATS' | 'TRIGGER_WORKFLOW_VERSION' | 'FRONT_COMPONENT_RENDERER' export type CommandMenuItemAvailabilityType = 'GLOBAL' | 'RECORD_SELECTION' | 'FALLBACK' @@ -6391,7 +6391,7 @@ update: UpdateLogicFunctionFromSourceInputUpdates} export interface UpdateLogicFunctionFromSourceInputUpdates {name?: (Scalars['String'] | null),description?: (Scalars['String'] | null),timeoutSeconds?: (Scalars['Float'] | null),sourceHandlerCode?: (Scalars['String'] | null),toolInputSchema?: (Scalars['JSON'] | null),handlerName?: (Scalars['String'] | null),sourceHandlerPath?: (Scalars['String'] | null),isTool?: (Scalars['Boolean'] | null),cronTriggerSettings?: (Scalars['JSON'] | null),databaseEventTriggerSettings?: (Scalars['JSON'] | null),httpRouteTriggerSettings?: (Scalars['JSON'] | null)} -export interface CreateCommandMenuItemInput {workflowVersionId?: (Scalars['UUID'] | null),frontComponentId?: (Scalars['UUID'] | null),engineComponentKey?: (EngineComponentKey | null),label: Scalars['String'],icon?: (Scalars['String'] | null),shortLabel?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isPinned?: (Scalars['Boolean'] | null),availabilityType?: (CommandMenuItemAvailabilityType | null),hotKeys?: (Scalars['String'][] | null),conditionalAvailabilityExpression?: (Scalars['String'] | null),availabilityObjectMetadataId?: (Scalars['UUID'] | null)} +export interface CreateCommandMenuItemInput {workflowVersionId?: (Scalars['UUID'] | null),frontComponentId?: (Scalars['UUID'] | null),engineComponentKey: EngineComponentKey,label: Scalars['String'],icon?: (Scalars['String'] | null),shortLabel?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isPinned?: (Scalars['Boolean'] | null),availabilityType?: (CommandMenuItemAvailabilityType | null),hotKeys?: (Scalars['String'][] | null),conditionalAvailabilityExpression?: (Scalars['String'] | null),availabilityObjectMetadataId?: (Scalars['UUID'] | null)} export interface UpdateCommandMenuItemInput {id: Scalars['UUID'],label?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),shortLabel?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isPinned?: (Scalars['Boolean'] | null),availabilityType?: (CommandMenuItemAvailabilityType | null),availabilityObjectMetadataId?: (Scalars['UUID'] | null),engineComponentKey?: (EngineComponentKey | null),hotKeys?: (Scalars['String'][] | null)} @@ -9176,7 +9176,9 @@ export const enumEngineComponentKey = { SEARCH_RECORDS: 'SEARCH_RECORDS' as const, SEARCH_RECORDS_FALLBACK: 'SEARCH_RECORDS_FALLBACK' as const, ASK_AI: 'ASK_AI' as const, - VIEW_PREVIOUS_AI_CHATS: 'VIEW_PREVIOUS_AI_CHATS' as const + VIEW_PREVIOUS_AI_CHATS: 'VIEW_PREVIOUS_AI_CHATS' as const, + TRIGGER_WORKFLOW_VERSION: 'TRIGGER_WORKFLOW_VERSION' as const, + FRONT_COMPONENT_RENDERER: 'FRONT_COMPONENT_RENDERER' as const } export const enumCommandMenuItemAvailabilityType = { diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command.ts index f701091909..671aed09e0 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command.ts @@ -13,6 +13,7 @@ import { ApplicationService } from 'src/engine/core-modules/application/applicat import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; 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'; import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; @@ -293,7 +294,7 @@ export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaces workflowVersionId: workflowVersion.id, frontComponentId: null, frontComponentUniversalIdentifier: null, - engineComponentKey: null, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label, shortLabel: label, icon: trigger.settings.icon ?? null, diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1774363913813-convert-engine-component-key-to-varchar.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1774363913813-convert-engine-component-key-to-varchar.ts new file mode 100644 index 0000000000..0ea79da207 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1774363913813-convert-engine-component-key-to-varchar.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ConvertEngineComponentKeyToVarchar1774363913813 + implements MigrationInterface +{ + name = 'ConvertEngineComponentKeyToVarchar1774363913813'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" DROP CONSTRAINT "CHK_CMD_MENU_ITEM_WF_OR_FC_OR_ENGINE_KEY"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "engineComponentKey" TYPE character varying USING "engineComponentKey"::text`, + ); + await queryRunner.query( + `DROP TYPE "core"."commandMenuItem_enginecomponentkey_enum"`, + ); + await queryRunner.query( + `UPDATE "core"."commandMenuItem" SET "engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' WHERE "workflowVersionId" IS NOT NULL AND "engineComponentKey" IS NULL`, + ); + await queryRunner.query( + `UPDATE "core"."commandMenuItem" SET "engineComponentKey" = 'FRONT_COMPONENT_RENDERER' WHERE "frontComponentId" IS NOT NULL AND "engineComponentKey" IS NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE" CHECK (("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL))`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "engineComponentKey" SET NOT NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "engineComponentKey" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" DROP CONSTRAINT "CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE"`, + ); + await queryRunner.query( + `UPDATE "core"."commandMenuItem" SET "engineComponentKey" = NULL WHERE "engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL`, + ); + await queryRunner.query( + `UPDATE "core"."commandMenuItem" SET "engineComponentKey" = NULL WHERE "engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL`, + ); + await queryRunner.query( + `CREATE TYPE "core"."commandMenuItem_enginecomponentkey_enum" AS ENUM('ACTIVATE_WORKFLOW', 'ADD_NODE_WORKFLOW', 'ADD_TO_FAVORITES', 'ASK_AI', 'CANCEL_DASHBOARD_LAYOUT', 'CREATE_NEW_RECORD', 'CREATE_NEW_VIEW', 'DEACTIVATE_WORKFLOW', 'DELETE_MULTIPLE_RECORDS', 'DELETE_SINGLE_RECORD', 'DESTROY_MULTIPLE_RECORDS', 'DESTROY_SINGLE_RECORD', 'DISCARD_DRAFT_WORKFLOW', 'DUPLICATE_DASHBOARD', 'DUPLICATE_WORKFLOW', 'EDIT_DASHBOARD_LAYOUT', 'EDIT_RECORD_PAGE_LAYOUT', 'EXPORT_FROM_RECORD_INDEX', 'EXPORT_FROM_RECORD_SHOW', 'EXPORT_MULTIPLE_RECORDS', 'EXPORT_NOTE_TO_PDF', 'EXPORT_VIEW', 'FRONT_COMPONENT_RENDERER', 'GO_TO_COMPANIES', 'GO_TO_DASHBOARDS', 'GO_TO_NOTES', 'GO_TO_OPPORTUNITIES', 'GO_TO_PEOPLE', 'GO_TO_RUNS', 'GO_TO_SETTINGS', 'GO_TO_TASKS', 'GO_TO_WORKFLOWS', 'HIDE_DELETED_RECORDS', 'IMPORT_RECORDS', 'MERGE_MULTIPLE_RECORDS', 'NAVIGATE_TO_NEXT_RECORD', 'NAVIGATE_TO_PREVIOUS_RECORD', 'REMOVE_FROM_FAVORITES', 'RESTORE_MULTIPLE_RECORDS', 'RESTORE_SINGLE_RECORD', 'SAVE_DASHBOARD_LAYOUT', 'SEARCH_RECORDS', 'SEARCH_RECORDS_FALLBACK', 'SEE_ACTIVE_VERSION_WORKFLOW', 'SEE_DELETED_RECORDS', 'SEE_RUNS_WORKFLOW', 'SEE_RUNS_WORKFLOW_VERSION', 'SEE_VERSIONS_WORKFLOW', 'SEE_VERSIONS_WORKFLOW_VERSION', 'SEE_VERSION_WORKFLOW_RUN', 'SEE_WORKFLOW_WORKFLOW_RUN', 'SEE_WORKFLOW_WORKFLOW_VERSION', 'STOP_WORKFLOW_RUN', 'TEST_WORKFLOW', 'TIDY_UP_WORKFLOW', 'TRIGGER_WORKFLOW_VERSION', 'UPDATE_MULTIPLE_RECORDS', 'USE_AS_DRAFT_WORKFLOW_VERSION', 'VIEW_PREVIOUS_AI_CHATS')`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ALTER COLUMN "engineComponentKey" TYPE "core"."commandMenuItem_enginecomponentkey_enum" USING "engineComponentKey"::"core"."commandMenuItem_enginecomponentkey_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "CHK_CMD_MENU_ITEM_WF_OR_FC_OR_ENGINE_KEY" CHECK (((("workflowVersionId" IS NOT NULL) AND ("frontComponentId" IS NULL) AND ("engineComponentKey" IS NULL)) OR (("workflowVersionId" IS NULL) AND ("frontComponentId" IS NOT NULL) AND ("engineComponentKey" IS NULL)) OR (("workflowVersionId" IS NULL) AND ("frontComponentId" IS NULL) AND ("engineComponentKey" IS NOT NULL))))`, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts index 68b4fb3081..656990dda0 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util.ts @@ -1,6 +1,7 @@ import { type CommandMenuItemManifest } from 'twenty-shared/application'; 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'; import { type UniversalFlatCommandMenuItem } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-command-menu-item.type'; const AVAILABILITY_TYPE_MAP: Record< @@ -38,7 +39,7 @@ export const fromCommandMenuItemManifestToUniversalFlatCommandMenuItem = ({ commandMenuItemManifest.frontComponentUniversalIdentifier, availabilityObjectMetadataUniversalIdentifier: commandMenuItemManifest.availabilityObjectUniversalIdentifier ?? null, - engineComponentKey: null, + engineComponentKey: EngineComponentKey.FRONT_COMPONENT_RENDERER, hotKeys: null, workflowVersionId: null, createdAt: now, diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts index db9792ed75..4ae577c0a8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto.ts @@ -37,9 +37,9 @@ export class CommandMenuItemDTO { frontComponent?: FrontComponentDTO | null; @IsEnum(EngineComponentKey) - @IsOptional() - @Field(() => EngineComponentKey, { nullable: true }) - engineComponentKey?: EngineComponentKey; + @IsNotEmpty() + @Field(() => EngineComponentKey) + engineComponentKey: EngineComponentKey; @IsString() @IsNotEmpty() diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts index 516d1c4368..12309e97ec 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input.ts @@ -27,9 +27,9 @@ export class CreateCommandMenuItemInput { frontComponentId?: string; @IsEnum(EngineComponentKey) - @IsOptional() - @Field(() => EngineComponentKey, { nullable: true }) - engineComponentKey?: EngineComponentKey; + @IsNotEmpty() + @Field(() => EngineComponentKey) + engineComponentKey: EngineComponentKey; @IsString() @IsNotEmpty() diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts index 04cac6fa99..9368a252a4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts @@ -30,8 +30,8 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti 'availabilityObjectMetadataId', ]) @Check( - 'CHK_CMD_MENU_ITEM_WF_OR_FC_OR_ENGINE_KEY', - '("workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "engineComponentKey" IS NULL) OR ("workflowVersionId" IS NULL AND "frontComponentId" IS NOT NULL AND "engineComponentKey" IS NULL) OR ("workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "engineComponentKey" IS NOT NULL)', + 'CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE', + `("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL)`, ) export class CommandMenuItemEntity extends SyncableEntity @@ -53,12 +53,8 @@ export class CommandMenuItemEntity @JoinColumn({ name: 'frontComponentId' }) frontComponent: Relation | null; - @Column({ - type: 'enum', - enum: Object.values(EngineComponentKey), - nullable: true, - }) - engineComponentKey: EngineComponentKey | null; + @Column({ type: 'varchar', nullable: false }) + engineComponentKey: EngineComponentKey; @Column({ nullable: false }) label: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum.ts index 4b6ffc48bd..6509fd1fd6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum.ts @@ -58,6 +58,8 @@ export enum EngineComponentKey { SEARCH_RECORDS_FALLBACK = 'SEARCH_RECORDS_FALLBACK', ASK_AI = 'ASK_AI', VIEW_PREVIOUS_AI_CHATS = 'VIEW_PREVIOUS_AI_CHATS', + TRIGGER_WORKFLOW_VERSION = 'TRIGGER_WORKFLOW_VERSION', + FRONT_COMPONENT_RENDERER = 'FRONT_COMPONENT_RENDERER', } registerEnumType(EngineComponentKey, { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts index 9555c9d4b5..f9df4cef04 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-create-command-menu-item-input-to-flat-command-menu-item-to-create.util.ts @@ -1,11 +1,6 @@ -import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; -import { - CommandMenuItemException, - CommandMenuItemExceptionCode, -} from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception'; import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input'; import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum'; import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type'; @@ -26,29 +21,6 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({ AllFlatEntityMaps, 'flatObjectMetadataMaps' | 'flatFrontComponentMaps' >): FlatCommandMenuItem => { - const hasWorkflowVersionId = isDefined( - createCommandMenuItemInput.workflowVersionId, - ); - const hasFrontComponentId = isDefined( - createCommandMenuItemInput.frontComponentId, - ); - const hasEngineComponentKey = isDefined( - createCommandMenuItemInput.engineComponentKey, - ); - - const sourceCount = [ - hasWorkflowVersionId, - hasFrontComponentId, - hasEngineComponentKey, - ].filter(Boolean).length; - - if (sourceCount !== 1) { - throw new CommandMenuItemException( - 'Exactly one of workflowVersionId, frontComponentId or engineComponentKey is required', - CommandMenuItemExceptionCode.WORKFLOW_OR_FRONT_COMPONENT_REQUIRED, - ); - } - const id = uuidv4(); const now = new Date().toISOString(); @@ -71,7 +43,7 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({ workflowVersionId: createCommandMenuItemInput.workflowVersionId ?? null, frontComponentId: createCommandMenuItemInput.frontComponentId ?? null, frontComponentUniversalIdentifier, - engineComponentKey: createCommandMenuItemInput.engineComponentKey ?? null, + engineComponentKey: createCommandMenuItemInput.engineComponentKey, label: createCommandMenuItemInput.label, icon: createCommandMenuItemInput.icon ?? null, shortLabel: createCommandMenuItemInput.shortLabel ?? null, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts index c5d9acb4a5..5e58b22318 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/from-flat-command-menu-item-to-command-menu-item-dto.util.ts @@ -7,7 +7,7 @@ export const fromFlatCommandMenuItemToCommandMenuItemDto = ( id: flatCommandMenuItem.id, workflowVersionId: flatCommandMenuItem.workflowVersionId ?? undefined, frontComponentId: flatCommandMenuItem.frontComponentId ?? undefined, - engineComponentKey: flatCommandMenuItem.engineComponentKey ?? undefined, + engineComponentKey: flatCommandMenuItem.engineComponentKey, label: flatCommandMenuItem.label, icon: flatCommandMenuItem.icon ?? undefined, shortLabel: flatCommandMenuItem.shortLabel ?? undefined, diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items.ts index efebd1de9a..1ccfd9d16f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflow-command-menu-items.ts @@ -1,6 +1,7 @@ import { type EntityManager } from 'typeorm'; 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'; import { QUICK_LEAD_WORKFLOW_VERSION_ID } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows'; import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; @@ -52,7 +53,7 @@ export const prefillWorkflowCommandMenuItems = async ( applicationId: applicationRow.app_id, workflowVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID, frontComponentId: null, - engineComponentKey: null, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Quick Lead', icon: 'IconUserPlus', shortLabel: 'Quick Lead', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-command-menu-item-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-command-menu-item-validator.service.ts index 5be8fa807d..f28e50e935 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-command-menu-item-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-command-menu-item-validator.service.ts @@ -6,6 +6,7 @@ import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; import { CommandMenuItemExceptionCode } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.exception'; +import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type'; import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util'; @@ -35,30 +36,22 @@ export class FlatCommandMenuItemValidatorService { }); } - const hasWorkflowVersionId = isDefined( - flatCommandMenuItem.workflowVersionId, - ); - const hasFrontComponentUniversalIdentifier = isDefined( - flatCommandMenuItem.frontComponentUniversalIdentifier, - ); - const hasEngineComponentKey = isDefined( - flatCommandMenuItem.engineComponentKey, - ); - - const sourceCount = [ - hasWorkflowVersionId, - hasFrontComponentUniversalIdentifier, - hasEngineComponentKey, - ].filter(Boolean).length; - - if (sourceCount !== 1) { + if (!isDefined(flatCommandMenuItem.engineComponentKey)) { validationResult.errors.push({ - code: CommandMenuItemExceptionCode.WORKFLOW_OR_FRONT_COMPONENT_REQUIRED, - message: t`Exactly one of workflowVersionId, frontComponentUniversalIdentifier or engineComponentKey is required`, - userFriendlyMessage: msg`Exactly one of workflow version, front component or engine component key is required`, + code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT, + message: t`engineComponentKey is required`, + userFriendlyMessage: msg`Engine component key is required`, }); } + this.validateEngineComponentKeyCoherence({ + engineComponentKey: flatCommandMenuItem.engineComponentKey, + workflowVersionId: flatCommandMenuItem.workflowVersionId, + frontComponentUniversalIdentifier: + flatCommandMenuItem.frontComponentUniversalIdentifier, + validationResult, + }); + return validationResult; } @@ -136,6 +129,97 @@ export class FlatCommandMenuItemValidatorService { }); } + const engineComponentKey = + flatEntityUpdate.engineComponentKey ?? + fromFlatCommandMenuItem.engineComponentKey; + + this.validateEngineComponentKeyCoherence({ + engineComponentKey, + workflowVersionId: fromFlatCommandMenuItem.workflowVersionId, + frontComponentUniversalIdentifier: + fromFlatCommandMenuItem.frontComponentUniversalIdentifier, + validationResult, + }); + return validationResult; } + + private validateEngineComponentKeyCoherence({ + engineComponentKey, + workflowVersionId, + frontComponentUniversalIdentifier, + validationResult, + }: { + engineComponentKey: EngineComponentKey | null; + workflowVersionId: string | null; + frontComponentUniversalIdentifier: string | null; + validationResult: FailedFlatEntityValidation< + 'commandMenuItem', + 'create' | 'update' + >; + }): void { + if (!isDefined(engineComponentKey)) { + return; + } + + switch (engineComponentKey) { + case EngineComponentKey.TRIGGER_WORKFLOW_VERSION: { + if (!isNonEmptyString(workflowVersionId)) { + validationResult.errors.push({ + code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT, + message: t`workflowVersionId is required when engineComponentKey is TRIGGER_WORKFLOW_VERSION`, + userFriendlyMessage: msg`Workflow version is required for workflow trigger items`, + }); + } + + if (isNonEmptyString(frontComponentUniversalIdentifier)) { + validationResult.errors.push({ + code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT, + message: t`frontComponentId must not be set when engineComponentKey is TRIGGER_WORKFLOW_VERSION`, + userFriendlyMessage: msg`Front component must not be set for workflow trigger items`, + }); + } + + break; + } + case EngineComponentKey.FRONT_COMPONENT_RENDERER: { + if (!isNonEmptyString(frontComponentUniversalIdentifier)) { + validationResult.errors.push({ + code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT, + message: t`frontComponentId is required when engineComponentKey is FRONT_COMPONENT_RENDERER`, + userFriendlyMessage: msg`Front component is required for front component renderer items`, + }); + } + + if (isNonEmptyString(workflowVersionId)) { + validationResult.errors.push({ + code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT, + message: t`workflowVersionId must not be set when engineComponentKey is FRONT_COMPONENT_RENDERER`, + userFriendlyMessage: msg`Workflow version must not be set for front component renderer items`, + }); + } + + break; + } + default: { + if (isNonEmptyString(workflowVersionId)) { + validationResult.errors.push({ + code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT, + message: t`workflowVersionId must not be set for engine component key ${engineComponentKey}`, + userFriendlyMessage: msg`Workflow version must not be set for this item type`, + }); + } + + if (isNonEmptyString(frontComponentUniversalIdentifier)) { + validationResult.errors.push({ + code: CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT, + message: t`frontComponentId must not be set for engine component key ${engineComponentKey}`, + userFriendlyMessage: msg`Front component must not be set for this item type`, + }); + } + + break; + } + } + } } 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 c9dac38499..82899d1ab2 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 @@ -6,6 +6,7 @@ import { type ActorMetadata, FeatureFlagKey } 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'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; @@ -432,6 +433,7 @@ export class WorkflowTriggerWorkspaceService { await this.commandMenuItemService.create( { workflowVersionId: workflowVersion.id, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label, shortLabel: label, icon: trigger.settings.icon, diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap index e9da8cec0d..fba1b7e1e0 100644 --- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap @@ -1,17 +1,5 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`CommandMenuItem creation should fail when creating with both workflowVersionId and frontComponentId 1`] = ` -{ - "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "WORKFLOW_OR_FRONT_COMPONENT_REQUIRED", - "userFriendlyMessage": "Either workflow version or front component is required.", - }, - "message": "Exactly one of workflowVersionId, frontComponentId or engineComponentKey is required", - "name": "UserInputError", -} -`; - exports[`CommandMenuItem creation should fail when creating with empty label 1`] = ` { "extensions": { @@ -47,6 +35,34 @@ exports[`CommandMenuItem creation should fail when creating with empty label 1`] } `; +exports[`CommandMenuItem creation should fail when creating with missing label 1`] = ` +{ + "extensions": { + "code": "BAD_USER_INPUT", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + }, + "message": "Field "label" of required type "String!" was not provided.", + "name": "GraphQLError", +} +`; + +exports[`CommandMenuItem creation should fail when creating with missing engineComponentKey 1`] = ` +{ + "extensions": { + "code": "BAD_USER_INPUT", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + }, + "message": "Field "engineComponentKey" of required type "EngineComponentKey!" was not provided.", + "name": "GraphQLError", +} +`; + exports[`CommandMenuItem creation should fail when creating with empty workflowVersionId 1`] = ` { "extensions": { @@ -77,28 +93,143 @@ exports[`CommandMenuItem creation should fail when creating with invalid workflo } `; -exports[`CommandMenuItem creation should fail when creating with missing label 1`] = ` +exports[`CommandMenuItem creation should fail when creating TRIGGER_WORKFLOW_VERSION without workflowVersionId 1`] = ` { "extensions": { - "code": "BAD_USER_INPUT", - "http": { - "status": 400, + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "commandMenuItem": [ + { + "errors": [ + { + "code": "INVALID_COMMAND_MENU_ITEM_INPUT", + "message": "workflowVersionId is required when engineComponentKey is TRIGGER_WORKFLOW_VERSION", + "userFriendlyMessage": "Workflow version is required for workflow trigger items", + }, + ], + "flatEntityMinimalInformation": { + "universalIdentifier": Any, + }, + "metadataName": "commandMenuItem", + "status": "fail", + "type": "create", + }, + ], }, - "userFriendlyMessage": "An error occurred.", + "message": "Validation failed for 1 commandMenuItem", + "summary": { + "commandMenuItem": 1, + "totalErrors": 1, + }, + "userFriendlyMessage": "Metadata validation failed", }, - "message": "Field "label" of required type "String!" was not provided.", + "message": "Multiple validation errors occurred while creating command menu item", "name": "GraphQLError", } `; -exports[`CommandMenuItem creation should fail when creating with missing workflowVersionId 1`] = ` +exports[`CommandMenuItem creation should fail when creating TRIGGER_WORKFLOW_VERSION with frontComponentId 1`] = ` { "extensions": { - "code": "BAD_USER_INPUT", - "subCode": "WORKFLOW_OR_FRONT_COMPONENT_REQUIRED", - "userFriendlyMessage": "Either workflow version or front component is required.", + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", }, - "message": "Exactly one of workflowVersionId, frontComponentId or engineComponentKey is required", - "name": "UserInputError", + "message": "Could not find frontComponent for given frontComponentId", + "name": "NotFoundError", +} +`; + +exports[`CommandMenuItem creation should fail when creating FRONT_COMPONENT_RENDERER without frontComponentId 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "commandMenuItem": [ + { + "errors": [ + { + "code": "INVALID_COMMAND_MENU_ITEM_INPUT", + "message": "frontComponentId is required when engineComponentKey is FRONT_COMPONENT_RENDERER", + "userFriendlyMessage": "Front component is required for front component renderer items", + }, + ], + "flatEntityMinimalInformation": { + "universalIdentifier": Any, + }, + "metadataName": "commandMenuItem", + "status": "fail", + "type": "create", + }, + ], + }, + "message": "Validation failed for 1 commandMenuItem", + "summary": { + "commandMenuItem": 1, + "totalErrors": 1, + }, + "userFriendlyMessage": "Metadata validation failed", + }, + "message": "Multiple validation errors occurred while creating command menu item", + "name": "GraphQLError", +} +`; + +exports[`CommandMenuItem creation should fail when creating FRONT_COMPONENT_RENDERER with workflowVersionId 1`] = ` +{ + "extensions": { + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", + }, + "message": "Could not find frontComponent for given frontComponentId", + "name": "NotFoundError", +} +`; + +exports[`CommandMenuItem creation should fail when creating standard key with workflowVersionId 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "commandMenuItem": [ + { + "errors": [ + { + "code": "INVALID_COMMAND_MENU_ITEM_INPUT", + "message": "workflowVersionId must not be set for engine component key GO_TO_PEOPLE", + "userFriendlyMessage": "Workflow version must not be set for this item type", + }, + ], + "flatEntityMinimalInformation": { + "universalIdentifier": Any, + }, + "metadataName": "commandMenuItem", + "status": "fail", + "type": "create", + }, + ], + }, + "message": "Validation failed for 1 commandMenuItem", + "summary": { + "commandMenuItem": 1, + "totalErrors": 1, + }, + "userFriendlyMessage": "Metadata validation failed", + }, + "message": "Multiple validation errors occurred while creating command menu item", + "name": "GraphQLError", +} +`; + +exports[`CommandMenuItem creation should fail when creating standard key with frontComponentId 1`] = ` +{ + "extensions": { + "code": "NOT_FOUND", + "subCode": "RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", + }, + "message": "Could not find frontComponent for given frontComponentId", + "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap index 5077ce60da..7443952c4f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap @@ -90,3 +90,38 @@ exports[`CommandMenuItem update should fail when updating with missing id 1`] = "name": "GraphQLError", } `; + +exports[`CommandMenuItem update should fail when changing engineComponentKey to a standard key on an item with workflowVersionId 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "commandMenuItem": [ + { + "errors": [ + { + "code": "INVALID_COMMAND_MENU_ITEM_INPUT", + "message": "workflowVersionId must not be set for engine component key GO_TO_PEOPLE", + "userFriendlyMessage": "Workflow version must not be set for this item type", + }, + ], + "flatEntityMinimalInformation": { + "universalIdentifier": Any, + }, + "metadataName": "commandMenuItem", + "status": "fail", + "type": "update", + }, + ], + }, + "message": "Validation failed for 1 commandMenuItem", + "summary": { + "commandMenuItem": 1, + "totalErrors": 1, + }, + "userFriendlyMessage": "Metadata validation failed", + }, + "message": "Multiple validation errors occurred while updating command menu item", + "name": "GraphQLError", +} +`; 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 bd60e82e2b..04253c7ddc 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 @@ -9,6 +9,7 @@ import { 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'; type TestContext = { input: CreateCommandMenuItemInput; @@ -21,6 +22,7 @@ const failingCommandMenuItemCreationTestCases: EachTestingContext[] context: { input: { workflowVersionId: faker.string.uuid(), + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: '', }, }, @@ -30,11 +32,12 @@ const failingCommandMenuItemCreationTestCases: EachTestingContext[] context: { input: { workflowVersionId: faker.string.uuid(), + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, } as CreateCommandMenuItemInput, }, }, { - title: 'when creating with missing workflowVersionId', + title: 'when creating with missing engineComponentKey', context: { input: { label: 'Test Label', @@ -46,6 +49,7 @@ const failingCommandMenuItemCreationTestCases: EachTestingContext[] context: { input: { workflowVersionId: '', + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Test Label', }, }, @@ -55,16 +59,71 @@ const failingCommandMenuItemCreationTestCases: EachTestingContext[] context: { input: { workflowVersionId: 'not-a-valid-uuid', + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Test Label', }, }, }, { - title: 'when creating with both workflowVersionId and frontComponentId', + title: + 'when creating TRIGGER_WORKFLOW_VERSION without workflowVersionId', + context: { + input: { + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, + label: 'Test Label', + }, + }, + }, + { + title: + 'when creating TRIGGER_WORKFLOW_VERSION with frontComponentId', context: { input: { workflowVersionId: faker.string.uuid(), frontComponentId: faker.string.uuid(), + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, + label: 'Test Label', + }, + }, + }, + { + title: + 'when creating FRONT_COMPONENT_RENDERER without frontComponentId', + context: { + input: { + engineComponentKey: EngineComponentKey.FRONT_COMPONENT_RENDERER, + label: 'Test Label', + }, + }, + }, + { + title: + 'when creating FRONT_COMPONENT_RENDERER with workflowVersionId', + context: { + input: { + frontComponentId: faker.string.uuid(), + workflowVersionId: faker.string.uuid(), + engineComponentKey: EngineComponentKey.FRONT_COMPONENT_RENDERER, + label: 'Test Label', + }, + }, + }, + { + title: 'when creating standard key with workflowVersionId', + context: { + input: { + workflowVersionId: faker.string.uuid(), + engineComponentKey: EngineComponentKey.GO_TO_PEOPLE, + label: 'Test Label', + }, + }, + }, + { + title: 'when creating standard key with frontComponentId', + context: { + input: { + frontComponentId: faker.string.uuid(), + engineComponentKey: EngineComponentKey.GO_TO_PEOPLE, label: 'Test Label', }, }, 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 92ffc244f8..dfed3e29e1 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 @@ -10,6 +10,7 @@ import { } 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'; type TestContext = { @@ -44,6 +45,7 @@ describe('CommandMenuItem update should fail', () => { expectToFail: false, input: { workflowVersionId: faker.string.uuid(), + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Test Command Menu Item To Update', icon: 'IconOriginal', isPinned: false, @@ -109,6 +111,16 @@ describe('CommandMenuItem update should fail', () => { }), }, }, + { + title: + 'when changing engineComponentKey to a standard key on an item with workflowVersionId', + context: { + input: (testSetup) => ({ + id: testSetup.testCommandMenuItemId, + engineComponentKey: EngineComponentKey.GO_TO_PEOPLE, + }), + }, + }, ]; it.each(eachTestingContextFilter(failingCommandMenuItemUpdateTestCases))( 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 dc4b32734c..c9ff53c779 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 @@ -10,6 +10,7 @@ import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util 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'; describe('CommandMenuItem creation should succeed', () => { let createdCommandMenuItemId: string; @@ -93,6 +94,7 @@ describe('CommandMenuItem creation should succeed', () => { expectToFail: false, input: { workflowVersionId, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Test Command Menu Item', }, }); @@ -117,6 +119,7 @@ describe('CommandMenuItem creation should succeed', () => { expectToFail: false, input: { workflowVersionId, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Full Command Menu Item', icon: 'IconSparkles', isPinned: true, @@ -145,6 +148,7 @@ describe('CommandMenuItem creation should succeed', () => { expectToFail: false, input: { workflowVersionId, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Bulk Records Command', availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, availabilityObjectMetadataId: personObjectMetadataId, @@ -169,6 +173,7 @@ describe('CommandMenuItem creation should succeed', () => { expectToFail: false, input: { workflowVersionId, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Global Command', }, }); @@ -202,6 +207,7 @@ describe('CommandMenuItem creation should succeed', () => { expectToFail: false, input: { frontComponentId: createdFrontComponentId, + engineComponentKey: EngineComponentKey.FRONT_COMPONENT_RENDERER, label: 'Front Component Command', }, }); 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 702804c778..cd21cc7d91 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 @@ -5,6 +5,8 @@ import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command- 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({ @@ -29,6 +31,7 @@ describe('CommandMenuItem deletion should succeed', () => { expectToFail: false, input: { workflowVersionId, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Command Menu Item To Delete', }, }); 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 b5a6f72611..2e721656c5 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 @@ -8,6 +8,7 @@ import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util 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'; describe('CommandMenuItem update should succeed', () => { let createdCommandMenuItemId: string; @@ -64,6 +65,7 @@ describe('CommandMenuItem update should succeed', () => { expectToFail: false, input: { workflowVersionId, + engineComponentKey: EngineComponentKey.TRIGGER_WORKFLOW_VERSION, label: 'Original Label', icon: 'IconOriginal', isPinned: false, diff --git a/packages/twenty-shared/src/types/ConfirmationModalCaller.ts b/packages/twenty-shared/src/types/ConfirmationModalCaller.ts index d075ba1100..53ece39e85 100644 --- a/packages/twenty-shared/src/types/ConfirmationModalCaller.ts +++ b/packages/twenty-shared/src/types/ConfirmationModalCaller.ts @@ -1,3 +1,3 @@ export type ConfirmationModalCaller = | { type: 'frontComponent'; frontComponentId: string } - | { type: 'engineCommand'; engineCommandId: string }; + | { type: 'commandMenuItem'; commandMenuItemId: string };