584 Refactor Tabs (#11008)

Closes https://github.com/twentyhq/core-team-issues/issues/584

This PR:
- Migrates the component state `activeTabIdComponentState` from the
deprecated V1 version to V2.
- Allows the active tab state to be preserved during navigation inside
the side panel and reset when the side panel is closed.
- Allows the active tab state to be preserved when we open a record in
full page from the side panel


https://github.com/user-attachments/assets/f2329d7a-ea15-4bd8-81dc-e98ce11edbd0


https://github.com/user-attachments/assets/474bffd5-29e0-40ba-97f4-fa5e9be34dc2
This commit is contained in:
Raphaël Bosi
2025-03-19 16:53:22 +01:00
committed by GitHub
parent 0d40126a29
commit cfdb3f5778
37 changed files with 299 additions and 609 deletions

View File

@ -1,5 +1,5 @@
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
@ -24,27 +24,29 @@ export const WorkflowRunDiagramCanvasEffect = () => {
const workflowId = useRecoilValue(workflowIdState);
const { activeTabIdState: workflowRunRightDrawerListActiveTabIdState } =
useTabListStates({
tabListScopeId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
});
const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback(
({ snapshot, set }) =>
() => {
const activeWorkflowRunRightDrawerTab = getSnapshotValue(
snapshot,
workflowRunRightDrawerListActiveTabIdState,
activeTabIdComponentState.atomFamily({
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
}),
) as WorkflowRunTabId | null;
if (
activeWorkflowRunRightDrawerTab === 'input' ||
activeWorkflowRunRightDrawerTab === 'output'
) {
set(workflowRunRightDrawerListActiveTabIdState, 'node');
set(
activeTabIdComponentState.atomFamily({
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
}),
'node',
);
}
},
[workflowRunRightDrawerListActiveTabIdState],
[],
);
const handleSelectionChange = useCallback(

View File

@ -1,23 +0,0 @@
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { RightDrawerWorkflowEditStepContent } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
export const RightDrawerWorkflowEditStep = () => {
const workflowId = useRecoilValue(workflowIdState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return (
<WorkflowStepContextProvider
value={{ workflowVersionId: workflow.currentVersion.id }}
>
<RightDrawerWorkflowEditStepContent workflow={workflow} />
</WorkflowStepContextProvider>
);
};

View File

@ -1,30 +0,0 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
export const RightDrawerWorkflowEditStepContent = ({
workflow,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
const { updateStep } = useUpdateStep({
workflow,
});
return (
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
onActionUpdate={updateStep}
onTriggerUpdate={updateTrigger}
/>
);
};

View File

@ -1,102 +0,0 @@
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui';
const StyledTabList = styled(TabList)`
background-color: ${({ theme }) => theme.background.secondary};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
type TabId = 'node' | 'input' | 'output';
export const RightDrawerWorkflowRunViewStep = () => {
const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId });
const { activeTabId } = useTabList<TabId>(
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
);
const stepExecutionStatus = isDefined(workflowRun)
? getWorkflowRunStepExecutionStatus({
workflowRunOutput: workflowRun.output,
stepId: workflowSelectedNode,
})
: undefined;
const areInputAndOutputTabsDisabled =
workflowSelectedNode === TRIGGER_STEP_ID ||
stepExecutionStatus === 'running' ||
stepExecutionStatus === 'not-executed';
const tabs: SingleTabProps<TabId>[] = [
{ id: 'node', title: 'Node', Icon: IconStepInto },
{
id: 'input',
title: 'Input',
Icon: IconLogin2,
disabled: areInputAndOutputTabsDisabled,
},
{
id: 'output',
title: 'Output',
Icon: IconLogout,
disabled: areInputAndOutputTabsDisabled,
},
];
if (!isDefined(workflowRun)) {
return null;
}
return (
<WorkflowStepContextProvider
value={{ workflowVersionId: flow.workflowVersionId }}
>
<StyledTabList
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
tabs={tabs}
behaveAsLinks={false}
/>
{activeTabId === 'node' ? (
<WorkflowStepDetail
readonly
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
/>
) : null}
{activeTabId === 'input' ? (
<WorkflowRunStepInputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}
{activeTabId === 'output' ? (
<WorkflowRunStepOutputDetail
key={workflowSelectedNode}
stepId={workflowSelectedNode}
/>
) : null}
</WorkflowStepContextProvider>
);
};

View File

@ -1,213 +0,0 @@
import { flowState } from '@/workflow/states/flowState';
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import styled from '@emotion/styled';
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, waitFor, within } from '@storybook/test';
import { HttpResponse, graphql } from 'msw';
import { useSetRecoilState } from 'recoil';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { oneFailedWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run';
import { RightDrawerWorkflowRunViewStep } from '../RightDrawerWorkflowRunViewStep';
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
width: 500px;
`;
const meta: Meta<typeof RightDrawerWorkflowRunViewStep> = {
title: 'Modules/Workflow/RightDrawerWorkflowRunViewStep',
component: RightDrawerWorkflowRunViewStep,
decorators: [
(Story) => (
<StyledWrapper>
<Story />
</StyledWrapper>
),
I18nFrontDecorator,
ComponentDecorator,
(Story) => {
const setFlow = useSetRecoilState(flowState);
const setWorkflowSelectedNode = useSetRecoilState(
workflowSelectedNodeState,
);
const setWorkflowRunId = useSetRecoilState(workflowRunIdState);
setFlow({
workflowVersionId:
oneFailedWorkflowRunQueryResult.workflowRun.workflowVersionId,
trigger:
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.trigger,
steps: oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps,
});
setWorkflowSelectedNode(
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps[0].id,
);
setWorkflowRunId(oneFailedWorkflowRunQueryResult.workflowRun.id);
return <Story />;
},
RouterDecorator,
ObjectMetadataItemsDecorator,
WorkspaceDecorator,
WorkflowStepDecorator,
],
parameters: {
msw: {
handlers: [
graphql.query('FindOneWorkflowRun', () => {
const workflowRunContext =
oneFailedWorkflowRunQueryResult.workflowRun.context;
// Rendering the whole objectMetadata information in the JSON viewer is too long for storybook
// so we remove it for the story
return HttpResponse.json({
data: {
...oneFailedWorkflowRunQueryResult,
workflowRun: {
...oneFailedWorkflowRunQueryResult.workflowRun,
context: {
...workflowRunContext,
trigger: {
...workflowRunContext.trigger,
objectMetadata: undefined,
},
},
},
},
});
}),
...graphqlMocks.handlers,
],
},
},
};
export default meta;
type Story = StoryObj<typeof RightDrawerWorkflowRunViewStep>;
export const NodeTab: Story = {};
export const InputTab: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(await canvas.findByRole('button', { name: 'Input' }));
expect(await canvas.findByText('Trigger')).toBeVisible();
},
};
export const InputTabDisabledForTrigger: Story = {
decorators: [
(Story) => {
const setWorkflowSelectedNode = useSetRecoilState(
workflowSelectedNodeState,
);
setWorkflowSelectedNode(TRIGGER_STEP_ID);
return <Story />;
},
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const inputTab = await canvas.findByRole('button', { name: 'Input' });
expect(inputTab).toBeDisabled();
},
};
export const InputTabNotExecutedStep: Story = {
decorators: [
(Story) => {
const setWorkflowSelectedNode = useSetRecoilState(
workflowSelectedNodeState,
);
setWorkflowSelectedNode(
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps.at(-1)!
.id,
);
return <Story />;
},
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const inputTab = await canvas.findByRole('button', { name: 'Input' });
expect(inputTab).toBeDisabled();
},
};
export const OutputTab: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByRole('button', { name: 'Output' }),
);
await waitFor(() => {
expect(canvas.queryByText('Create Record')).not.toBeInTheDocument();
});
expect(await canvas.findByText('result')).toBeVisible();
},
};
export const OutputTabDisabledForTrigger: Story = {
decorators: [
(Story) => {
const setWorkflowSelectedNode = useSetRecoilState(
workflowSelectedNodeState,
);
setWorkflowSelectedNode(TRIGGER_STEP_ID);
return <Story />;
},
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const outputTab = await canvas.findByRole('button', { name: 'Output' });
expect(outputTab).toBeDisabled();
},
};
export const OutputTabNotExecutedStep: Story = {
decorators: [
(Story) => {
const setWorkflowSelectedNode = useSetRecoilState(
workflowSelectedNodeState,
);
setWorkflowSelectedNode(
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps.at(-1)!
.id,
);
return <Story />;
},
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const outputTab = await canvas.findByRole('button', { name: 'Output' });
expect(outputTab).toBeDisabled();
},
};

View File

@ -1 +1,7 @@
export type WorkflowRunTabId = 'node' | 'input' | 'output';
export type WorkflowRunTabIdType = 'node' | 'input' | 'output';
export enum WorkflowRunTabId {
NODE = 'node',
INPUT = 'input',
OUTPUT = 'output',
}

View File

@ -1,16 +0,0 @@
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { RightDrawerWorkflowSelectActionContent } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectActionContent';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
export const RightDrawerWorkflowSelectAction = () => {
const workflowId = useRecoilValue(workflowIdState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return <RightDrawerWorkflowSelectActionContent workflow={workflow} />;
};

View File

@ -1,45 +0,0 @@
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions';
import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions';
import { MenuItemCommand, useIcons } from 'twenty-ui';
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
export const RightDrawerWorkflowSelectActionContent = ({
workflow,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const { getIcon } = useIcons();
const { createStep } = useCreateStep({
workflow,
});
return (
<RightDrawerStepListContainer>
<RightDrawerWorkflowSelectStepTitle>
Records
</RightDrawerWorkflowSelectStepTitle>
{RECORD_ACTIONS.map((action) => (
<MenuItemCommand
key={action.type}
LeftIcon={getIcon(action.icon)}
text={action.label}
onClick={() => createStep(action.type)}
/>
))}
<RightDrawerWorkflowSelectStepTitle>
Other
</RightDrawerWorkflowSelectStepTitle>
{OTHER_ACTIONS.map((action) => (
<MenuItemCommand
key={action.type}
LeftIcon={getIcon(action.icon)}
text={action.label}
onClick={() => createStep(action.type)}
/>
))}
</RightDrawerStepListContainer>
);
};

View File

@ -19,11 +19,13 @@ import { InputLabel } from '@/ui/input/components/InputLabel';
import { TextArea } from '@/ui/input/components/TextArea';
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunctionFields';
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/types/WorkflowServerlessFunctionTabId';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
@ -77,8 +79,10 @@ export const WorkflowEditActionServerlessFunction = ({
const theme = useTheme();
const { getIcon } = useIcons();
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
const tabListId = `${WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}_${serverlessFunctionId}`;
const { activeTabId } = useTabList(tabListId);
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
);
const { updateOneServerlessFunction } =
useUpdateOneServerlessFunction(serverlessFunctionId);
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
@ -267,8 +271,12 @@ export const WorkflowEditActionServerlessFunction = ({
};
const tabs = [
{ id: 'code', title: 'Code', Icon: IconCode },
{ id: 'test', title: 'Test', Icon: IconPlayerPlay },
{ id: WorkflowServerlessFunctionTabId.CODE, title: 'Code', Icon: IconCode },
{
id: WorkflowServerlessFunctionTabId.TEST,
title: 'Test',
Icon: IconPlayerPlay,
},
];
useEffect(() => {
@ -284,9 +292,11 @@ export const WorkflowEditActionServerlessFunction = ({
!loading && (
<StyledContainer>
<StyledTabList
tabListInstanceId={tabListId}
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID
}
/>
<WorkflowStepHeader
onTitleChange={(newName: string) => {
@ -299,7 +309,7 @@ export const WorkflowEditActionServerlessFunction = ({
disabled={actionOptions.readonly}
/>
<WorkflowStepBody>
{activeTabId === 'code' && (
{activeTabId === WorkflowServerlessFunctionTabId.CODE && (
<>
<WorkflowEditActionServerlessFunctionFields
functionInput={functionInput}
@ -323,7 +333,7 @@ export const WorkflowEditActionServerlessFunction = ({
</StyledCodeEditorContainer>
</>
)}
{activeTabId === 'test' && (
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
<>
<WorkflowEditActionServerlessFunctionFields
functionInput={serverlessFunctionTestData.input}
@ -352,7 +362,7 @@ export const WorkflowEditActionServerlessFunction = ({
</>
)}
</WorkflowStepBody>
{activeTabId === 'test' && (
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
<RightDrawerFooter
actions={[
<CmdEnterActionButton

View File

@ -0,0 +1,6 @@
export type WorkflowServerlessFunctionTabIdType = 'code' | 'test';
export enum WorkflowServerlessFunctionTabId {
CODE = 'code',
TEST = 'test',
}

View File

@ -10,10 +10,11 @@ import {
import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema';
import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath';
import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema';
@ -26,7 +27,6 @@ import {
OverflowingTextWithTooltip,
useIcons,
} from 'twenty-ui';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
type WorkflowVariablesDropdownFieldItemsProps = {
step: StepOutputSchema;
@ -43,8 +43,8 @@ export const WorkflowVariablesDropdownFieldItems = ({
const [searchInputValue, setSearchInputValue] = useState('');
const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { setActiveTabId } = useTabList(
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
const setActiveTabId = useSetRecoilComponentStateV2(
activeTabIdComponentState,
);
const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState(
workflowDiagramTriggerNodeSelectionState,