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.
This commit is contained in:
Raphaël Bosi
2026-03-24 16:59:52 +01:00
committed by GitHub
parent 27c0ca975f
commit 49afe5dbc4
103 changed files with 1296 additions and 1131 deletions
@@ -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]) => (
<ContextStoreComponentInstanceContext.Provider
key={commandMenuItemId}
value={{ instanceId: context.contextStoreInstanceId }}
>
<CommandComponentInstanceContext.Provider
value={{ instanceId: commandMenuItemId }}
>
<CommandMenuItemErrorBoundary
shouldReportToSentry
onError={() => unmountCommand(commandMenuItemId)}
>
{ENGINE_COMPONENT_KEY_COMPONENT_MAP[context.engineComponentKey]}
</CommandMenuItemErrorBoundary>
</CommandComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
),
)}
</>
);
};
@@ -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;
};
@@ -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]) => (
<CommandMenuItemErrorBoundary
key={engineCommandId}
engineCommandId={engineCommandId}
shouldReportToSentry
onError={() => unmountEngineCommand(engineCommandId)}
>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: mountContext.contextStoreInstanceId }}
>
<EngineCommandComponentInstanceContext.Provider
value={{ instanceId: engineCommandId }}
>
{
ENGINE_COMPONENT_KEY_HEADLESS_COMPONENT_MAP[
mountContext.engineComponentKey
]
}
</EngineCommandComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</CommandMenuItemErrorBoundary>
),
)}
</>
);
};
@@ -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,
]);
@@ -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 (
<Suspense fallback={null}>
<LayoutRenderingProvider
value={{
targetRecordIdentifier:
isDefined(objectNameSingular) && isDefined(recordId)
? {
id: recordId,
targetObjectNameSingular: objectNameSingular,
}
: undefined,
layoutType: PageLayoutType.DASHBOARD,
isInSidePanel: false,
}}
>
<FrontComponentRenderer
frontComponentId={context.frontComponentId}
commandMenuItemId={commandMenuItemId}
/>
</LayoutRenderingProvider>
</Suspense>
);
};
@@ -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]: (
<SeeVersionsWorkflowVersionSingleRecordCommand />
),
[EngineComponentKey.TRIGGER_WORKFLOW_VERSION]: (
<TriggerWorkflowVersionEngineCommand />
),
[EngineComponentKey.FRONT_COMPONENT_RENDERER]: (
<HeadlessFrontComponentRendererEngineCommand />
),
};
@@ -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<WorkflowVersionRecord>({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
recordGqlFields: { id: true, workflowId: true, trigger: true },
});
const fetchWorkflowVersion = useCallback(
async (versionId: string): Promise<WorkflowVersionRecord | undefined> => {
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,
};
};
@@ -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;
};
@@ -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;
};
@@ -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;
};
@@ -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;
};
@@ -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;
};
@@ -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 <HeadlessEngineCommandWrapperEffect execute={execute} />;
};
@@ -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) ||
@@ -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) ||
@@ -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(
@@ -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);
@@ -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) ||
@@ -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({
@@ -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(
@@ -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();
@@ -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(
@@ -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');
@@ -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(
@@ -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];
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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)) {
@@ -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();
@@ -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)) {
@@ -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)) {
@@ -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(
@@ -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(
@@ -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(
@@ -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 (
@@ -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)) {
@@ -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,
@@ -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];
@@ -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)) {
@@ -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)) {
@@ -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 ?? '');
@@ -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();
@@ -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(
@@ -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();
@@ -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();
@@ -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 ?? '');
@@ -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;
@@ -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(
@@ -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(
@@ -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();
@@ -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();
@@ -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);
},
});
@@ -1,4 +1,3 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const EngineCommandComponentInstanceContext =
createComponentInstanceContext();
export const CommandComponentInstanceContext = createComponentInstanceContext();
@@ -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<string, HeadlessCommandContextApi>
>({
key: 'headlessCommandContextApisState',
defaultValue: new Map(),
});
@@ -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<string, MountedEngineCommandState>
>({
key: 'mountedEngineCommandsState',
defaultValue: new Map(),
});
@@ -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<EnrichedObjectMetadataItem>;
@@ -17,3 +17,20 @@ export type MountedEngineCommandState = {
selectedRecords: ObjectRecord[];
graphqlFilter: Nullable<RecordGqlOperationFilter>;
};
export type HeadlessFrontComponentCommandContextApi =
HeadlessEngineCommandContextApi & {
frontComponentId: string;
};
export type HeadlessTriggerWorkflowVersionCommandContextApi =
HeadlessEngineCommandContextApi & {
workflowId: string;
workflowVersionId: string;
payloads: Record<string, any>[];
};
export type HeadlessCommandContextApi =
| HeadlessEngineCommandContextApi
| HeadlessFrontComponentCommandContextApi
| HeadlessTriggerWorkflowVersionCommandContextApi;
@@ -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,
};
};
@@ -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<string, any>[] => {
const payloads: Record<string, any>[] = [];
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;
}
}
};
@@ -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;
@@ -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;