Fix workflow with manual triggers and no selection not appearing in the command menu (#11544)

Fix workflow with manual triggers and no selection not appearing in the
command menu

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2025-04-14 11:03:29 +02:00
committed by GitHub
parent b5a1d1a895
commit ace515eea7
13 changed files with 86 additions and 70 deletions

View File

@ -23,5 +23,5 @@ export const Action = ({
onClick();
};
return <ActionDisplay action={actionConfig} onClick={handleClick} />;
return <ActionDisplay onClick={handleClick} />;
};

View File

@ -1,8 +1,6 @@
import { ActionDisplay } from '@/action-menu/actions/display/components/ActionDisplay';
import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext';
import { useCloseActionMenu } from '@/action-menu/hooks/useCloseActionMenu';
import { AppPath } from '@/types/AppPath';
import { useContext } from 'react';
import { PathParam } from 'react-router-dom';
import { getAppPath } from '~/utils/navigation/getAppPath';
@ -15,21 +13,9 @@ export const ActionLink = <T extends AppPath>({
params?: { [key in PathParam<T>]: string | null };
queryParams?: Record<string, any>;
}) => {
const actionConfig = useContext(ActionConfigContext);
const { closeActionMenu } = useCloseActionMenu();
if (!actionConfig) {
return null;
}
const path = getAppPath(to, params, queryParams);
return (
<ActionDisplay
action={{ ...actionConfig }}
onClick={closeActionMenu}
to={path}
/>
);
return <ActionDisplay onClick={closeActionMenu} to={path} />;
};

View File

@ -46,7 +46,7 @@ export const ActionModal = ({
return (
<>
<ActionDisplay action={actionConfig} onClick={handleOpen} />
<ActionDisplay onClick={handleOpen} />
{isOpen &&
createPortal(
<ConfirmationModal

View File

@ -44,5 +44,5 @@ export const ActionOpenSidePanelPage = ({
}
};
return <ActionDisplay action={actionConfig} onClick={handleClick} />;
return <ActionDisplay onClick={handleClick} />;
};

View File

@ -1,6 +1,7 @@
import { ActionButton } from '@/action-menu/actions/display/components/ActionButton';
import { ActionDropdownItem } from '@/action-menu/actions/display/components/ActionDropdownItem';
import { ActionListItem } from '@/action-menu/actions/display/components/ActionListItem';
import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { MessageDescriptor } from '@lingui/core';
import { useContext } from 'react';
@ -19,14 +20,13 @@ export type ActionDisplayProps = {
};
export const ActionDisplay = ({
action,
onClick,
to,
}: {
action: ActionDisplayProps;
onClick?: (event?: React.MouseEvent<HTMLElement>) => void;
to?: string;
}) => {
const action = useContext(ActionConfigContext);
const { displayType } = useContext(ActionMenuContext);
if (!action) {

View File

@ -13,10 +13,12 @@ import { useRecoilValue } from 'recoil';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { IconSettingsAutomation } from 'twenty-ui/display';
export const useWorkflowRunRecordActions = ({
export const useRunWorkflowRecordActions = ({
objectMetadataItem,
skip,
}: {
objectMetadataItem: ObjectMetadataItem;
skip?: boolean;
}) => {
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
@ -27,17 +29,14 @@ export const useWorkflowRunRecordActions = ({
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
: undefined;
if (!isDefined(selectedRecordId)) {
throw new Error('Selected record ID is required');
}
const selectedRecord = useRecoilValue(
recordStoreFamilyState(selectedRecordId),
recordStoreFamilyState(selectedRecordId ?? ''),
);
const { records: activeWorkflowVersions } =
useActiveWorkflowVersionsWithManualTrigger({
objectMetadataItem,
skip,
});
const { runWorkflowVersion } = useRunWorkflowVersion();

View File

@ -1,4 +1,5 @@
import { ActionLink } from '@/action-menu/actions/components/ActionLink';
import { ActionDisplay } from '@/action-menu/actions/display/components/ActionDisplay';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { AppPath } from '@/types/AppPath';
@ -7,14 +8,18 @@ import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVers
export const SeeActiveVersionWorkflowSingleRecordAction = () => {
const recordId = useSelectedRecordIdOrThrow();
const workflowActiveVersion = useActiveWorkflowVersion(recordId);
const { workflowVersion, loading } = useActiveWorkflowVersion(recordId);
if (loading) {
return <ActionDisplay />;
}
return (
<ActionLink
to={AppPath.RecordShowPage}
params={{
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
objectRecordId: workflowActiveVersion.id,
objectRecordId: workflowVersion.id,
}}
/>
);

View File

@ -1,28 +1,37 @@
import { Action } from '@/action-menu/actions/components/Action';
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionType } from '@/action-menu/actions/types/ActionType';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { msg } from '@lingui/core/macro';
import { useContext } from 'react';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { IconSettingsAutomation } from 'twenty-ui/display';
import { FeatureFlagKey } from '~/generated/graphql';
export const useRunWorkflowActions = () => {
export const useRunWorkflowRecordAgnosticActions = () => {
const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled,
);
const { actionMenuType } = useContext(ActionMenuContext);
const { records: activeWorkflowVersions } =
useActiveWorkflowVersionsWithManualTrigger({});
useActiveWorkflowVersionsWithManualTrigger({
skip:
actionMenuType !== 'command-menu' &&
actionMenuType !== 'command-menu-show-page-action-menu-dropdown',
});
const { runWorkflowVersion } = useRunWorkflowVersion();
if (!isWorkflowEnabled) {
return { runWorkflowActions: [] };
return [];
}
const runWorkflowActions = activeWorkflowVersions
return activeWorkflowVersions
.map((activeWorkflowVersion, index) => {
if (!isDefined(activeWorkflowVersion.workflow)) {
return undefined;
@ -38,18 +47,16 @@ export const useRunWorkflowActions = () => {
position: index,
Icon: IconSettingsAutomation,
shouldBeRegistered: () => true,
useAction: () => {
return {
onClick: async () => {
await runWorkflowVersion({
component: (
<Action
onClick={() => {
runWorkflowVersion({
workflowVersionId: activeWorkflowVersion.id,
});
},
};
},
}}
/>
),
};
})
.filter(isDefined);
return { runWorkflowActions };
};

View File

@ -1,10 +1,9 @@
import { ActionMenuContextType } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuContextProviderWorkflowObjects } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowObjects';
import { ActionMenuContextProviderWorkflowsEnabled } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowsEnabled';
import { ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection';
import { ActionMenuContextProviderWorkflowsNotEnabled } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowsNotEnabled';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -50,39 +49,30 @@ export const ActionMenuContextProvider = ({
const objectMetadataItem =
localContextStoreObjectMetadataItem ?? mainContextStoreObjectMetadataItem;
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
const isSingleRecordSelection =
contextStoreTargetedRecordsRule.mode === 'selection' &&
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1;
const isWorkflowObject =
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Workflow ||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.WorkflowRun ||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.WorkflowVersion;
if (
isWorkflowEnabled &&
isSingleRecordSelection &&
isDefined(objectMetadataItem) &&
(actionMenuType === 'command-menu' ||
actionMenuType === 'command-menu-show-page-action-menu-dropdown')
) {
if (isWorkflowEnabled && isDefined(objectMetadataItem) && isWorkflowObject) {
return (
<ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection
<ActionMenuContextProviderWorkflowObjects
isInRightDrawer={isInRightDrawer}
displayType={displayType}
actionMenuType={actionMenuType}
objectMetadataItem={objectMetadataItem}
>
{children}
</ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection>
</ActionMenuContextProviderWorkflowObjects>
);
}
if (isWorkflowEnabled && isDefined(objectMetadataItem) && isWorkflowObject) {
if (
isWorkflowEnabled &&
isDefined(objectMetadataItem) &&
(actionMenuType === 'command-menu' ||
actionMenuType === 'command-menu-show-page-action-menu-dropdown')
) {
return (
<ActionMenuContextProviderWorkflowsEnabled
isInRightDrawer={isInRightDrawer}

View File

@ -1,4 +1,4 @@
import { useWorkflowRunRecordActions } from '@/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions';
import { useRunWorkflowRecordAgnosticActions } from '@/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions';
import {
ActionMenuContext,
ActionMenuContextType,
@ -8,7 +8,7 @@ import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShould
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
export const ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection = ({
export const ActionMenuContextProviderWorkflowObjects = ({
objectMetadataItem,
isInRightDrawer,
displayType,
@ -36,9 +36,8 @@ export const ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection = ({
const actions = useRegisteredActions(shouldBeRegisteredParams);
const workflowRunRecordActions = useWorkflowRunRecordActions({
objectMetadataItem,
});
const runWorkflowRecordAgnosticActions =
useRunWorkflowRecordAgnosticActions();
return (
<ActionMenuContext.Provider
@ -46,7 +45,7 @@ export const ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection = ({
isInRightDrawer,
displayType,
actionMenuType,
actions: [...actions, ...workflowRunRecordActions],
actions: [...actions, ...runWorkflowRecordAgnosticActions],
}}
>
{children}

View File

@ -1,10 +1,14 @@
import { useRunWorkflowRecordActions } from '@/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions';
import { useRunWorkflowRecordAgnosticActions } from '@/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions';
import {
ActionMenuContext,
ActionMenuContextType,
} from '@/action-menu/contexts/ActionMenuContext';
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
export const ActionMenuContextProviderWorkflowsEnabled = ({
@ -35,13 +39,33 @@ export const ActionMenuContextProviderWorkflowsEnabled = ({
const actions = useRegisteredActions(shouldBeRegisteredParams);
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
const isSingleRecordSelection =
contextStoreTargetedRecordsRule.mode === 'selection' &&
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1;
const runWorkflowRecordActions = useRunWorkflowRecordActions({
objectMetadataItem,
skip: !isSingleRecordSelection,
});
const runWorkflowRecordAgnosticActions =
useRunWorkflowRecordAgnosticActions();
return (
<ActionMenuContext.Provider
value={{
isInRightDrawer,
displayType,
actionMenuType,
actions,
actions: [
...actions,
...runWorkflowRecordActions,
...runWorkflowRecordAgnosticActions,
],
}}
>
{children}

View File

@ -3,7 +3,7 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { Workflow, WorkflowVersion } from '@/workflow/types/Workflow';
export const useActiveWorkflowVersion = (workflowId: string) => {
const { records: workflowVersions } = useFindManyRecords<
const { records: workflowVersions, loading } = useFindManyRecords<
WorkflowVersion & {
workflow: Omit<Workflow, 'versions'> & {
versions: Array<{ __typename: string }>;
@ -39,5 +39,8 @@ export const useActiveWorkflowVersion = (workflowId: string) => {
},
});
return workflowVersions?.[0];
return {
workflowVersion: workflowVersions?.[0],
loading,
};
};

View File

@ -8,8 +8,10 @@ import { isDefined } from 'twenty-shared/utils';
export const useActiveWorkflowVersionsWithManualTrigger = ({
objectMetadataItem,
skip,
}: {
objectMetadataItem?: ObjectMetadataItem;
skip?: boolean;
}) => {
const filters = [
{
@ -50,6 +52,7 @@ export const useActiveWorkflowVersionsWithManualTrigger = ({
}),
workflow: true,
},
skip,
});
// TODO: refactor when we can use 'not like' in the RawJson filter