Workflow command menu fixes (#15234)

- Move trash button to command menu footer
<img width="132" height="102" alt="Capture d’écran 2025-10-21 à 18 12
19"
src="https://github.com/user-attachments/assets/ad6a9374-a28f-4498-b8f3-ca576981693c"
/>

- Add footer to triggers + on missing steps
- Catch step body errors so the user can still delete the step when an
error happens
<img width="529" height="419" alt="Capture d’écran 2025-10-21 à 18 13
17"
src="https://github.com/user-attachments/assets/0ac07511-f4ad-40c4-98f1-afb53c0f7a89"
/>
This commit is contained in:
Thomas Trompette
2025-10-22 10:17:06 +02:00
committed by GitHub
parent c5564d9bd0
commit bf3c3fc5a5
32 changed files with 136 additions and 92 deletions
@@ -17,7 +17,7 @@ import { WorkflowEditActionFilter } from '@/workflow/workflow-steps/workflow-act
import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords';
import { WorkflowEditActionFormFiller } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFiller';
import { WorkflowEditActionHttpRequest } from '@/workflow/workflow-steps/workflow-actions/http-request-action/components/WorkflowEditActionHttpRequest';
import { WorkflowEditActionIterator } from '@/workflow/workflow-steps/workflow-actions/iterator-action/WorkflowEditActionIterator';
import { WorkflowEditActionIterator } from '@/workflow/workflow-steps/workflow-actions/iterator-action/components/WorkflowEditActionIterator';
import { WorkflowEditTriggerCronForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm';
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm';
import { WorkflowEditTriggerManual } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManual';
@@ -1,3 +1,6 @@
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { AppErrorDisplay } from '@/error-handler/components/internal/AppErrorDisplay';
import { type AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps';
import styled from '@emotion/styled';
const StyledWorkflowStepBody = styled.div`
@@ -12,4 +15,29 @@ const StyledWorkflowStepBody = styled.div`
row-gap: ${({ theme }) => theme.spacing(4)};
`;
export { StyledWorkflowStepBody as WorkflowStepBody };
export const WorkflowStepBody = ({
children,
}: {
children: React.ReactNode;
}) => {
return (
<StyledWorkflowStepBody>
<AppErrorBoundary
resetOnLocationChange={true}
FallbackComponent={({
error,
resetErrorBoundary,
title,
}: AppErrorDisplayProps) => (
<AppErrorDisplay
error={error}
resetErrorBoundary={resetErrorBoundary}
title={title}
/>
)}
>
{children}
</AppErrorBoundary>
</StyledWorkflowStepBody>
);
};
@@ -16,7 +16,7 @@ import { WorkflowEditActionFilter } from '@/workflow/workflow-steps/workflow-act
import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords';
import { WorkflowEditActionFormBuilder } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder';
import { WorkflowEditActionHttpRequest } from '@/workflow/workflow-steps/workflow-actions/http-request-action/components/WorkflowEditActionHttpRequest';
import { WorkflowEditActionIterator } from '@/workflow/workflow-steps/workflow-actions/iterator-action/WorkflowEditActionIterator';
import { WorkflowEditActionIterator } from '@/workflow/workflow-steps/workflow-actions/iterator-action/components/WorkflowEditActionIterator';
import { WorkflowEditTriggerCronForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm';
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm';
import { WorkflowEditTriggerManual } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManual';
@@ -6,16 +6,18 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { useDuplicateStep } from '@/workflow/workflow-steps/hooks/useDuplicateStep';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useId } from 'react';
import { IconCopyPlus, IconPencil } from 'twenty-ui/display';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { IconCopyPlus, IconPencil, IconTrash } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
import { getOsControlSymbol } from 'twenty-ui/utilities';
export const WorkflowActionFooter = ({
export const WorkflowStepFooter = ({
stepId,
additionalActions,
}: {
@@ -28,7 +30,11 @@ export const WorkflowActionFooter = ({
const { duplicateStep } = useDuplicateStep();
const { closeDropdown } = useCloseDropdown();
const workflowId = useCommandMenuWorkflowIdOrThrow();
const { openWorkflowEditStepTypeInCommandMenu } = useWorkflowCommandMenu();
const {
openWorkflowEditStepTypeInCommandMenu,
openWorkflowTriggerTypeInCommandMenu,
} = useWorkflowCommandMenu();
const { deleteStep } = useDeleteStep();
const OptionsDropdown = (
<Dropdown
@@ -54,18 +60,30 @@ export const WorkflowActionFooter = ({
<MenuItem
onClick={() => {
closeDropdown(dropdownId);
openWorkflowEditStepTypeInCommandMenu(workflowId);
stepId === TRIGGER_STEP_ID
? openWorkflowTriggerTypeInCommandMenu(workflowId)
: openWorkflowEditStepTypeInCommandMenu(workflowId);
}}
text={t`Change node type`}
LeftIcon={IconPencil}
/>
{stepId !== TRIGGER_STEP_ID && (
<MenuItem
onClick={() => {
closeDropdown(dropdownId);
duplicateStep({ stepId });
}}
text={t`Duplicate node`}
LeftIcon={IconCopyPlus}
/>
)}
<MenuItem
onClick={() => {
closeDropdown(dropdownId);
duplicateStep({ stepId });
deleteStep(stepId);
}}
text={t`Duplicate node`}
LeftIcon={IconCopyPlus}
text={t`Delete node`}
LeftIcon={IconTrash}
/>
</SelectableList>
</DropdownMenuItemsContainer>
@@ -4,8 +4,8 @@ import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
import { Select } from '@/ui/input/components/Select';
import { type WorkflowAiAgentAction } from '@/workflow/types/Workflow';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { type AiAgentOutputSchema } from '@/workflow/workflow-variables/types/AiAgentOutputSchema';
@@ -146,7 +146,7 @@ export const WorkflowEditActionAiAgent = ({
readonly={actionOptions.readonly}
/>
</WorkflowStepBody>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -38,7 +38,8 @@ import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { SOURCE_FOLDER_NAME } from '@/serverless-functions/constants/SourceFolderName';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { type Monaco } from '@monaco-editor/react';
import { type editor } from 'monaco-editor';
import { AutoTypings } from 'monaco-editor-auto-typings';
@@ -51,7 +52,6 @@ import { IconCode, IconPlayerPlay, useIcons } from 'twenty-ui/display';
import { CodeEditor } from 'twenty-ui/input';
import { useIsMobile } from 'twenty-ui/utilities';
import { useDebouncedCallback } from 'use-debounce';
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
const CODE_EDITOR_MIN_HEIGHT = 343;
@@ -488,7 +488,7 @@ export const WorkflowEditActionServerlessFunction = ({
)}
</WorkflowStepBody>
{!actionOptions.readonly && (
<WorkflowActionFooter
<WorkflowStepFooter
stepId={action.id}
additionalActions={
activeTabId === WorkflowServerlessFunctionTabId.TEST
@@ -8,8 +8,8 @@ import { Select } from '@/ui/input/components/Select';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews';
import { type WorkflowCreateRecordAction } from '@/workflow/types/Workflow';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { shouldDisplayFormField } from '@/workflow/workflow-steps/workflow-actions/utils/shouldDisplayFormField';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
@@ -272,7 +272,7 @@ export const WorkflowEditActionCreateRecord = ({
);
})}
</WorkflowStepBody>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -6,8 +6,8 @@ import { type WorkflowDeleteRecordAction } from '@/workflow/types/Workflow';
import { useEffect, useState } from 'react';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useTheme } from '@emotion/react';
@@ -180,7 +180,7 @@ export const WorkflowEditActionDeleteRecord = ({
/>
)}
</WorkflowStepBody>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -17,8 +17,8 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { type WorkflowSendEmailAction } from '@/workflow/types/Workflow';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useTheme } from '@emotion/react';
@@ -345,7 +345,7 @@ export const WorkflowEditActionSendEmail = ({
maxWidth={EMAIL_EDITOR_MAX_WIDTH}
/>
</WorkflowStepBody>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
)
);
@@ -1,6 +1,6 @@
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
import { type WorkflowUpdateRecordAction } from '@/workflow/types/Workflow';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { WorkflowUpdateRecordBody } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowUpdateRecordBody';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
@@ -81,7 +81,7 @@ export const WorkflowEditActionUpdateRecord = ({
onUpdate={handleUpdate}
shouldPickRecord={true}
/>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -1,6 +1,6 @@
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
import { type WorkflowUpsertRecordAction } from '@/workflow/types/Workflow';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { WorkflowUpdateRecordBody } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowUpdateRecordBody';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
@@ -79,7 +79,7 @@ export const WorkflowEditActionUpsertRecord = ({
onUpdate={handleUpdate}
shouldPickRecord={false}
/>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -4,8 +4,8 @@ import { FormNumberFieldInput } from '@/object-record/record-field/ui/form-types
import { Select } from '@/ui/input/components/Select';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { type WorkflowDelayAction } from '@/workflow/types/Workflow';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { t } from '@lingui/core/macro';
@@ -219,7 +219,7 @@ export const WorkflowEditActionDelay = ({
)}
</WorkflowStepBody>
<WorkflowActionFooter stepId={action.id} />
<WorkflowStepFooter stepId={action.id} />
</>
);
};
@@ -1,5 +1,6 @@
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
import { type WorkflowFilterAction } from '@/workflow/types/Workflow';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { WorkflowEditActionFilterBody } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilterBody';
import { WorkflowEditActionFilterBodyEffect } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilterBodyEffect';
import { StepFilterGroupsComponentInstanceContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/StepFilterGroupsComponentInstanceContext';
@@ -79,6 +80,7 @@ export const WorkflowEditActionFilter = ({
/>
</StepFilterGroupsComponentInstanceContext.Provider>
</StepFiltersComponentInstanceContext.Provider>
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -13,8 +13,8 @@ import { type RecordFilter } from '@/object-record/record-filter/types/RecordFil
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { WorkflowFindRecordsFilters } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsFilters';
import { WorkflowFindRecordsFiltersEffect } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsFiltersEffect';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
@@ -247,7 +247,7 @@ export const WorkflowEditActionFindRecords = ({
}}
/>
</WorkflowStepBody>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -7,8 +7,8 @@ import { InputLabel } from '@/ui/input/components/InputLabel';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
import { type WorkflowFormAction } from '@/workflow/types/Workflow';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { WorkflowEditActionFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFieldSettings';
import { WorkflowFormEmptyMessage } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormEmptyMessage';
import { type WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
@@ -399,7 +399,7 @@ export const WorkflowEditActionFormBuilder = ({
</StyledAddFieldButtonContainer>
)}
</StyledWorkflowStepBody>
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};
@@ -11,7 +11,7 @@ import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/Gene
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { getBodyTypeFromHeaders } from '@/workflow/workflow-steps/workflow-actions/http-request-action/utils/getBodyTypeFromHeaders';
import { isMethodWithBody } from '@/workflow/workflow-steps/workflow-actions/http-request-action/utils/isMethodWithBody';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
@@ -221,7 +221,7 @@ export const WorkflowEditActionHttpRequest = ({
)}
</WorkflowStepBody>
{!actionOptions.readonly && (
<WorkflowActionFooter
<WorkflowStepFooter
stepId={action.id}
additionalActions={
activeTabId === WorkflowHttpRequestTabId.TEST
@@ -4,6 +4,7 @@ import { type FieldArrayValue } from '@/object-record/record-field/ui/types/Fiel
import { type WorkflowIteratorAction } from '@/workflow/types/Workflow';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useLingui } from '@lingui/react/macro';
@@ -131,6 +132,7 @@ export const WorkflowEditActionIterator = ({
VariablePicker={WorkflowVariablePicker}
/>
</WorkflowStepBody>
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
</>
);
};