Create a right drawer for viewing steps in workflow runs (#10366)
- Improve the type-safety of the objects mapping the id of a right drawer or side panel view to a React component - Improve the types of the `useTabList` hook to type the available tab identifiers strictly - Create a specialized `WorkflowRunDiagramCanvas` component to render a `WorkflowRunDiagramCanvasEffect` component that opens `RightDrawerPages.WorkflowRunStepView` when a step is selected - Create a new side panel view specifically for workflow run step details - Create tab list in the new side panel; all the tabs are `Node`, `Input` and `Output` - Create a hook `useWorkflowSelectedNodeOrThrow` not to duplicate throwing mechanisms Closes https://github.com/twentyhq/core-team-issues/issues/432 ## Demo https://github.com/user-attachments/assets/8d5df7dc-0b99-49a2-9a54-d3eaee80a8e6
This commit is contained in:
committed by
GitHub
parent
694553608b
commit
f74e4bedc4
@ -6,6 +6,7 @@ import { CommandMenuSearchRecordsPage } from '@/command-menu/pages/components/Co
|
|||||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||||
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
|
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
|
||||||
import { RightDrawerWorkflowEditStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStep';
|
import { RightDrawerWorkflowEditStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStep';
|
||||||
|
import { RightDrawerWorkflowRunViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep';
|
||||||
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
|
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
|
||||||
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
|
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
|
||||||
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
|
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
|
||||||
@ -29,5 +30,6 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
|
|||||||
],
|
],
|
||||||
[CommandMenuPages.WorkflowStepEdit, <RightDrawerWorkflowEditStep />],
|
[CommandMenuPages.WorkflowStepEdit, <RightDrawerWorkflowEditStep />],
|
||||||
[CommandMenuPages.WorkflowStepView, <RightDrawerWorkflowViewStep />],
|
[CommandMenuPages.WorkflowStepView, <RightDrawerWorkflowViewStep />],
|
||||||
|
[CommandMenuPages.WorkflowRunStepView, <RightDrawerWorkflowRunViewStep />],
|
||||||
[CommandMenuPages.SearchRecords, <CommandMenuSearchRecordsPage />],
|
[CommandMenuPages.SearchRecords, <CommandMenuSearchRecordsPage />],
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -8,5 +8,6 @@ export enum CommandMenuPages {
|
|||||||
WorkflowStepSelectAction = 'workflow-step-select-action',
|
WorkflowStepSelectAction = 'workflow-step-select-action',
|
||||||
WorkflowStepView = 'workflow-step-view',
|
WorkflowStepView = 'workflow-step-view',
|
||||||
WorkflowStepEdit = 'workflow-step-edit',
|
WorkflowStepEdit = 'workflow-step-edit',
|
||||||
|
WorkflowRunStepView = 'workflow-run-step-view',
|
||||||
SearchRecords = 'search-records',
|
SearchRecords = 'search-records',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isR
|
|||||||
|
|
||||||
import { RightDrawerContainer } from '@/ui/layout/right-drawer/components/RightDrawerContainer';
|
import { RightDrawerContainer } from '@/ui/layout/right-drawer/components/RightDrawerContainer';
|
||||||
import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar';
|
import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar';
|
||||||
import { ComponentByRightDrawerPage } from '@/ui/layout/right-drawer/types/ComponentByRightDrawerPage';
|
|
||||||
import { RightDrawerWorkflowEditStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStep';
|
import { RightDrawerWorkflowEditStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStep';
|
||||||
|
import { RightDrawerWorkflowRunViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep';
|
||||||
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
|
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
|
||||||
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
|
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
|
||||||
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
|
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
|
||||||
@ -28,7 +28,7 @@ const StyledRightDrawerBody = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const RIGHT_DRAWER_PAGES_CONFIG: ComponentByRightDrawerPage = {
|
const RIGHT_DRAWER_PAGES_CONFIG = {
|
||||||
[RightDrawerPages.ViewEmailThread]: <RightDrawerEmailThread />,
|
[RightDrawerPages.ViewEmailThread]: <RightDrawerEmailThread />,
|
||||||
[RightDrawerPages.ViewCalendarEvent]: <RightDrawerCalendarEvent />,
|
[RightDrawerPages.ViewCalendarEvent]: <RightDrawerCalendarEvent />,
|
||||||
[RightDrawerPages.ViewRecord]: <RightDrawerRecord />,
|
[RightDrawerPages.ViewRecord]: <RightDrawerRecord />,
|
||||||
@ -41,7 +41,8 @@ const RIGHT_DRAWER_PAGES_CONFIG: ComponentByRightDrawerPage = {
|
|||||||
),
|
),
|
||||||
[RightDrawerPages.WorkflowStepEdit]: <RightDrawerWorkflowEditStep />,
|
[RightDrawerPages.WorkflowStepEdit]: <RightDrawerWorkflowEditStep />,
|
||||||
[RightDrawerPages.WorkflowStepView]: <RightDrawerWorkflowViewStep />,
|
[RightDrawerPages.WorkflowStepView]: <RightDrawerWorkflowViewStep />,
|
||||||
};
|
[RightDrawerPages.WorkflowRunStepView]: <RightDrawerWorkflowRunViewStep />,
|
||||||
|
} satisfies Record<RightDrawerPages, JSX.Element>;
|
||||||
|
|
||||||
export const RightDrawerRouter = () => {
|
export const RightDrawerRouter = () => {
|
||||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||||
|
|||||||
@ -9,4 +9,5 @@ export const RIGHT_DRAWER_PAGE_ICONS = {
|
|||||||
[RightDrawerPages.WorkflowStepSelectAction]: 'IconSparkles',
|
[RightDrawerPages.WorkflowStepSelectAction]: 'IconSparkles',
|
||||||
[RightDrawerPages.WorkflowStepEdit]: 'IconSparkles',
|
[RightDrawerPages.WorkflowStepEdit]: 'IconSparkles',
|
||||||
[RightDrawerPages.WorkflowStepView]: 'IconSparkles',
|
[RightDrawerPages.WorkflowStepView]: 'IconSparkles',
|
||||||
};
|
[RightDrawerPages.WorkflowRunStepView]: 'IconSparkles',
|
||||||
|
} satisfies Record<RightDrawerPages, string>;
|
||||||
|
|||||||
@ -9,4 +9,5 @@ export const RIGHT_DRAWER_PAGE_TITLES = {
|
|||||||
[RightDrawerPages.WorkflowStepSelectAction]: 'Workflow',
|
[RightDrawerPages.WorkflowStepSelectAction]: 'Workflow',
|
||||||
[RightDrawerPages.WorkflowStepEdit]: 'Workflow',
|
[RightDrawerPages.WorkflowStepEdit]: 'Workflow',
|
||||||
[RightDrawerPages.WorkflowStepView]: 'Workflow',
|
[RightDrawerPages.WorkflowStepView]: 'Workflow',
|
||||||
};
|
[RightDrawerPages.WorkflowRunStepView]: 'Workflow',
|
||||||
|
} satisfies Record<RightDrawerPages, string>;
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
|
||||||
|
|
||||||
export type ComponentByRightDrawerPage = {
|
|
||||||
[componentName in RightDrawerPages]?: JSX.Element;
|
|
||||||
};
|
|
||||||
@ -7,4 +7,5 @@ export enum RightDrawerPages {
|
|||||||
WorkflowStepSelectAction = 'workflow-step-select-action',
|
WorkflowStepSelectAction = 'workflow-step-select-action',
|
||||||
WorkflowStepView = 'workflow-step-view',
|
WorkflowStepView = 'workflow-step-view',
|
||||||
WorkflowStepEdit = 'workflow-step-edit',
|
WorkflowStepEdit = 'workflow-step-edit',
|
||||||
|
WorkflowRunStepView = 'workflow-run-step-view',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,24 +4,25 @@ import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPage
|
|||||||
export const mapRightDrawerPageToCommandMenuPage = (
|
export const mapRightDrawerPageToCommandMenuPage = (
|
||||||
rightDrawerPage: RightDrawerPages,
|
rightDrawerPage: RightDrawerPages,
|
||||||
) => {
|
) => {
|
||||||
switch (rightDrawerPage) {
|
const rightDrawerPagesToCommandMenuPages: Record<
|
||||||
case RightDrawerPages.ViewRecord:
|
RightDrawerPages,
|
||||||
return CommandMenuPages.ViewRecord;
|
CommandMenuPages
|
||||||
case RightDrawerPages.ViewEmailThread:
|
> = {
|
||||||
return CommandMenuPages.ViewEmailThread;
|
[RightDrawerPages.ViewRecord]: CommandMenuPages.ViewRecord,
|
||||||
case RightDrawerPages.ViewCalendarEvent:
|
[RightDrawerPages.ViewEmailThread]: CommandMenuPages.ViewEmailThread,
|
||||||
return CommandMenuPages.ViewCalendarEvent;
|
[RightDrawerPages.ViewCalendarEvent]: CommandMenuPages.ViewCalendarEvent,
|
||||||
case RightDrawerPages.Copilot:
|
[RightDrawerPages.Copilot]: CommandMenuPages.Copilot,
|
||||||
return CommandMenuPages.Copilot;
|
[RightDrawerPages.WorkflowStepSelectTriggerType]:
|
||||||
case RightDrawerPages.WorkflowStepSelectTriggerType:
|
CommandMenuPages.WorkflowStepSelectTriggerType,
|
||||||
return CommandMenuPages.WorkflowStepSelectTriggerType;
|
[RightDrawerPages.WorkflowStepSelectAction]:
|
||||||
case RightDrawerPages.WorkflowStepSelectAction:
|
CommandMenuPages.WorkflowStepSelectAction,
|
||||||
return CommandMenuPages.WorkflowStepSelectAction;
|
[RightDrawerPages.WorkflowStepView]: CommandMenuPages.WorkflowStepView,
|
||||||
case RightDrawerPages.WorkflowStepView:
|
[RightDrawerPages.WorkflowRunStepView]:
|
||||||
return CommandMenuPages.WorkflowStepView;
|
CommandMenuPages.WorkflowRunStepView,
|
||||||
case RightDrawerPages.WorkflowStepEdit:
|
[RightDrawerPages.WorkflowStepEdit]: CommandMenuPages.WorkflowStepEdit,
|
||||||
return CommandMenuPages.WorkflowStepEdit;
|
};
|
||||||
default:
|
|
||||||
return CommandMenuPages.Root;
|
return (
|
||||||
}
|
rightDrawerPagesToCommandMenuPages[rightDrawerPage] ?? CommandMenuPages.Root
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
|
|||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||||
|
import { ShowPageSubContainerTabListContainer } from '@/ui/layout/show-page/components/ShowPageSubContainerTabListContainer';
|
||||||
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
@ -26,14 +27,8 @@ const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTabListContainer = styled.div<{ shouldDisplay: boolean }>`
|
const StyledTabListContainer = styled.div<{ shouldDisplay: boolean }>`
|
||||||
align-items: center;
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`};
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: ${({ shouldDisplay }) => (shouldDisplay ? 'flex' : 'none')};
|
display: ${({ shouldDisplay }) => (shouldDisplay ? 'flex' : 'none')};
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
`.withComponent(ShowPageSubContainerTabListContainer);
|
||||||
height: 40px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>`
|
const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledTabListContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`};
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
height: 40px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledTabListContainer as ShowPageSubContainerTabListContainer };
|
||||||
@ -9,10 +9,10 @@ import { useEffect } from 'react';
|
|||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from 'twenty-ui';
|
||||||
import { Tab } from './Tab';
|
import { Tab } from './Tab';
|
||||||
|
|
||||||
export type SingleTabProps = {
|
export type SingleTabProps<T extends string = string> = {
|
||||||
title: string;
|
title: string;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
id: string;
|
id: T;
|
||||||
hide?: boolean;
|
hide?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
pill?: string | React.ReactElement;
|
pill?: string | React.ReactElement;
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
import { RecoilState, useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
|
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
|
||||||
|
|
||||||
export const useTabList = (tabListId?: string) => {
|
export const useTabList = <T extends string>(tabListId?: string) => {
|
||||||
const { activeTabIdState } = useTabListStates({
|
const { activeTabIdState } = useTabListStates({
|
||||||
tabListScopeId: tabListId,
|
tabListScopeId: tabListId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [activeTabId, setActiveTabId] = useRecoilState(activeTabIdState);
|
const [activeTabId, setActiveTabId] = useRecoilState(
|
||||||
|
activeTabIdState as RecoilState<T | null>,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeTabId,
|
activeTabId,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
|
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
|
||||||
import { WorkflowRun } from '@/workflow/types/Workflow';
|
import { WorkflowRun } from '@/workflow/types/Workflow';
|
||||||
import { WorkflowDiagramCanvasReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly';
|
import { WorkflowRunDiagramCanvas } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas';
|
||||||
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
|
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ export const WorkflowRunVisualizerContent = ({
|
|||||||
<>
|
<>
|
||||||
<WorkflowRunVisualizerEffect workflowRun={workflowRun} />
|
<WorkflowRunVisualizerEffect workflowRun={workflowRun} />
|
||||||
|
|
||||||
<WorkflowDiagramCanvasReadonly versionStatus={workflowVersion.status} />
|
<WorkflowRunDiagramCanvas versionStatus={workflowVersion.status} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
|
||||||
|
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||||
|
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
||||||
|
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
||||||
|
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
||||||
|
import { WorkflowRunDiagramCanvasEffect } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect';
|
||||||
|
import { ReactFlowProvider } from '@xyflow/react';
|
||||||
|
|
||||||
|
export const WorkflowRunDiagramCanvas = ({
|
||||||
|
versionStatus,
|
||||||
|
}: {
|
||||||
|
versionStatus: WorkflowVersionStatus;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ReactFlowProvider>
|
||||||
|
<WorkflowDiagramCanvasBase
|
||||||
|
status={versionStatus}
|
||||||
|
nodeTypes={{
|
||||||
|
default: WorkflowDiagramStepNodeReadonly,
|
||||||
|
}}
|
||||||
|
edgeTypes={{
|
||||||
|
default: WorkflowDiagramDefaultEdge,
|
||||||
|
success: WorkflowDiagramSuccessEdge,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<WorkflowRunDiagramCanvasEffect />
|
||||||
|
</ReactFlowProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
|
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||||
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
||||||
|
import {
|
||||||
|
WorkflowDiagramNode,
|
||||||
|
WorkflowDiagramStepNodeData,
|
||||||
|
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||||
|
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
|
||||||
|
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const WorkflowRunDiagramCanvasEffect = () => {
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
|
||||||
|
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
||||||
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
const { closeCommandMenu } = useCommandMenu();
|
||||||
|
|
||||||
|
const handleSelectionChange = useCallback(
|
||||||
|
({ nodes }: OnSelectionChangeParams) => {
|
||||||
|
const selectedNode = nodes[0] as WorkflowDiagramNode;
|
||||||
|
const isClosingStep = isDefined(selectedNode) === false;
|
||||||
|
|
||||||
|
if (isClosingStep) {
|
||||||
|
closeRightDrawer();
|
||||||
|
closeCommandMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkflowSelectedNode(selectedNode.id);
|
||||||
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
|
|
||||||
|
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
|
||||||
|
|
||||||
|
openRightDrawer(RightDrawerPages.WorkflowRunStepView, {
|
||||||
|
title: selectedNodeData.name,
|
||||||
|
Icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[
|
||||||
|
setWorkflowSelectedNode,
|
||||||
|
setHotkeyScope,
|
||||||
|
openRightDrawer,
|
||||||
|
closeRightDrawer,
|
||||||
|
closeCommandMenu,
|
||||||
|
getIcon,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
useOnSelectionChange({
|
||||||
|
onChange: handleSelectionChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
|
export const useWorkflowSelectedNodeOrThrow = () => {
|
||||||
|
const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState);
|
||||||
|
|
||||||
|
if (!isDefined(workflowSelectedNode)) {
|
||||||
|
throw new Error(
|
||||||
|
'Expected a node to be selected. A node must have been selected before running this code.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return workflowSelectedNode;
|
||||||
|
};
|
||||||
@ -1,11 +1,9 @@
|
|||||||
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||||
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||||
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
||||||
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
|
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
|
||||||
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
|
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const RightDrawerWorkflowEditStepContent = ({
|
export const RightDrawerWorkflowEditStepContent = ({
|
||||||
workflow,
|
workflow,
|
||||||
@ -13,13 +11,7 @@ export const RightDrawerWorkflowEditStepContent = ({
|
|||||||
workflow: WorkflowWithCurrentVersion;
|
workflow: WorkflowWithCurrentVersion;
|
||||||
}) => {
|
}) => {
|
||||||
const flow = useFlowOrThrow();
|
const flow = useFlowOrThrow();
|
||||||
|
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
||||||
const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState);
|
|
||||||
if (!isDefined(workflowSelectedNode)) {
|
|
||||||
throw new Error(
|
|
||||||
'Expected a node to be selected. Selecting a node is mandatory to edit it.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
|
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
|
||||||
const { updateStep } = useUpdateStep({
|
const { updateStep } = useUpdateStep({
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { ShowPageSubContainerTabListContainer } from '@/ui/layout/show-page/components/ShowPageSubContainerTabListContainer';
|
||||||
|
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
|
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||||
|
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||||
|
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 styled from '@emotion/styled';
|
||||||
|
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui';
|
||||||
|
|
||||||
|
const StyledTabListContainer = styled(ShowPageSubContainerTabListContainer)`
|
||||||
|
background-color: ${({ theme }) => theme.background.secondary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type TabId = 'node' | 'input' | 'output';
|
||||||
|
|
||||||
|
export const RightDrawerWorkflowRunViewStep = () => {
|
||||||
|
const flow = useFlowOrThrow();
|
||||||
|
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
||||||
|
|
||||||
|
const { activeTabId } = useTabList<TabId>(
|
||||||
|
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabs: SingleTabProps<TabId>[] = [
|
||||||
|
{ id: 'node', title: 'Node', Icon: IconStepInto },
|
||||||
|
{ id: 'input', title: 'Input', Icon: IconLogin2 },
|
||||||
|
{ id: 'output', title: 'Output', Icon: IconLogout },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledTabListContainer>
|
||||||
|
<TabList
|
||||||
|
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
|
||||||
|
tabs={tabs}
|
||||||
|
behaveAsLinks={false}
|
||||||
|
/>
|
||||||
|
</StyledTabListContainer>
|
||||||
|
|
||||||
|
{activeTabId === 'node' ? (
|
||||||
|
<WorkflowStepDetail
|
||||||
|
readonly
|
||||||
|
stepId={workflowSelectedNode}
|
||||||
|
trigger={flow.trigger}
|
||||||
|
steps={flow.steps}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,18 +1,10 @@
|
|||||||
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||||
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const RightDrawerWorkflowViewStep = () => {
|
export const RightDrawerWorkflowViewStep = () => {
|
||||||
const flow = useFlowOrThrow();
|
const flow = useFlowOrThrow();
|
||||||
|
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
||||||
const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState);
|
|
||||||
if (!isDefined(workflowSelectedNode)) {
|
|
||||||
throw new Error(
|
|
||||||
'Expected a node to be selected. Selecting a node is mandatory to view its details.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowStepDetail
|
<WorkflowStepDetail
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
const StyledWorkflowStepBody = styled.div`
|
const StyledWorkflowStepBody = styled.div`
|
||||||
|
background: ${({ theme }) => theme.background.primary};
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
padding: ${({ theme }) => theme.spacing(4)};
|
padding: ${({ theme }) => theme.spacing(4)};
|
||||||
row-gap: ${({ theme }) => theme.spacing(6)};
|
row-gap: ${({ theme }) => theme.spacing(6)};
|
||||||
flex: 1 1 auto;
|
|
||||||
height: 100%;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledWorkflowStepBody as WorkflowStepBody };
|
export { StyledWorkflowStepBody as WorkflowStepBody };
|
||||||
|
|||||||
@ -21,6 +21,14 @@ const WorkflowEditActionFormServerlessFunction = lazy(() =>
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const WorkflowReadonlyActionFormServerlessFunction = lazy(() =>
|
||||||
|
import(
|
||||||
|
'@/workflow/workflow-steps/workflow-actions/components/WorkflowReadonlyActionFormServerlessFunction'
|
||||||
|
).then((module) => ({
|
||||||
|
default: module.WorkflowReadonlyActionFormServerlessFunction,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
type WorkflowStepDetailProps = {
|
type WorkflowStepDetailProps = {
|
||||||
stepId: string;
|
stepId: string;
|
||||||
trigger: WorkflowTrigger | null;
|
trigger: WorkflowTrigger | null;
|
||||||
@ -50,6 +58,7 @@ export const WorkflowStepDetail = ({
|
|||||||
trigger,
|
trigger,
|
||||||
steps,
|
steps,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isDefined(stepDefinition) || !isDefined(stepDefinition.definition)) {
|
if (!isDefined(stepDefinition) || !isDefined(stepDefinition.definition)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -60,6 +69,7 @@ export const WorkflowStepDetail = ({
|
|||||||
case 'DATABASE_EVENT': {
|
case 'DATABASE_EVENT': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditTriggerDatabaseEventForm
|
<WorkflowEditTriggerDatabaseEventForm
|
||||||
|
key={stepId}
|
||||||
trigger={stepDefinition.definition}
|
trigger={stepDefinition.definition}
|
||||||
triggerOptions={props}
|
triggerOptions={props}
|
||||||
/>
|
/>
|
||||||
@ -68,6 +78,7 @@ export const WorkflowStepDetail = ({
|
|||||||
case 'MANUAL': {
|
case 'MANUAL': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditTriggerManualForm
|
<WorkflowEditTriggerManualForm
|
||||||
|
key={stepId}
|
||||||
trigger={stepDefinition.definition}
|
trigger={stepDefinition.definition}
|
||||||
triggerOptions={props}
|
triggerOptions={props}
|
||||||
/>
|
/>
|
||||||
@ -76,6 +87,7 @@ export const WorkflowStepDetail = ({
|
|||||||
case 'CRON': {
|
case 'CRON': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditTriggerCronForm
|
<WorkflowEditTriggerCronForm
|
||||||
|
key={stepId}
|
||||||
trigger={stepDefinition.definition}
|
trigger={stepDefinition.definition}
|
||||||
triggerOptions={props}
|
triggerOptions={props}
|
||||||
/>
|
/>
|
||||||
@ -93,11 +105,18 @@ export const WorkflowStepDetail = ({
|
|||||||
case 'CODE': {
|
case 'CODE': {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<RightDrawerSkeletonLoader />}>
|
<Suspense fallback={<RightDrawerSkeletonLoader />}>
|
||||||
<WorkflowEditActionFormServerlessFunction
|
{props.readonly ? (
|
||||||
key={stepId}
|
<WorkflowReadonlyActionFormServerlessFunction
|
||||||
action={stepDefinition.definition}
|
key={stepId}
|
||||||
actionOptions={props}
|
action={stepDefinition.definition}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<WorkflowEditActionFormServerlessFunction
|
||||||
|
key={stepId}
|
||||||
|
action={stepDefinition.definition}
|
||||||
|
actionOptions={props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -150,8 +169,6 @@ export const WorkflowStepDetail = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,24 +43,38 @@ const StyledHeaderIconContainer = styled.div`
|
|||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
type WorkflowStepHeaderProps = {
|
||||||
|
Icon: IconComponent;
|
||||||
|
iconColor: string;
|
||||||
|
initialTitle: string;
|
||||||
|
headerType: string;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
disabled: true;
|
||||||
|
onTitleChange?: never;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
disabled?: boolean;
|
||||||
|
onTitleChange: (newTitle: string) => void;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const WorkflowStepHeader = ({
|
export const WorkflowStepHeader = ({
|
||||||
onTitleChange,
|
|
||||||
Icon,
|
Icon,
|
||||||
iconColor,
|
iconColor,
|
||||||
initialTitle,
|
initialTitle,
|
||||||
headerType,
|
headerType,
|
||||||
disabled,
|
disabled,
|
||||||
}: {
|
onTitleChange,
|
||||||
onTitleChange: (newTitle: string) => void;
|
}: WorkflowStepHeaderProps) => {
|
||||||
Icon: IconComponent;
|
|
||||||
iconColor: string;
|
|
||||||
initialTitle: string;
|
|
||||||
headerType: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [title, setTitle] = useState(initialTitle);
|
const [title, setTitle] = useState(initialTitle);
|
||||||
const debouncedOnTitleChange = useDebouncedCallback(onTitleChange, 100);
|
|
||||||
|
const debouncedOnTitleChange = useDebouncedCallback((newTitle: string) => {
|
||||||
|
onTitleChange?.(newTitle);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
const handleChange = (newTitle: string) => {
|
const handleChange = (newTitle: string) => {
|
||||||
setTitle(newTitle);
|
setTitle(newTitle);
|
||||||
debouncedOnTitleChange(newTitle);
|
debouncedOnTitleChange(newTitle);
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export const WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID =
|
||||||
|
'workflow-run-step-side-panel-tab-list';
|
||||||
@ -17,6 +17,7 @@ import { getFunctionOutputSchema } from '@/serverless-functions/utils/getFunctio
|
|||||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/serverless-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
|
import { mergeDefaultFunctionInputAndFunctionInput } from '@/serverless-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||||
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||||
|
import { ShowPageSubContainerTabListContainer } from '@/ui/layout/show-page/components/ShowPageSubContainerTabListContainer';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
||||||
@ -48,14 +49,7 @@ const StyledCodeEditorContainer = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTabListContainer = styled.div`
|
const StyledTabListContainer = styled(ShowPageSubContainerTabListContainer)`
|
||||||
align-items: center;
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`};
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
|
||||||
height: ${({ theme }) => theme.spacing(10)};
|
|
||||||
background-color: ${({ theme }) => theme.background.secondary};
|
background-color: ${({ theme }) => theme.background.secondary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -5,57 +5,50 @@ import { InputLabel } from '@/ui/input/components/InputLabel';
|
|||||||
import { FunctionInput } from '@/workflow/workflow-steps/workflow-actions/types/FunctionInput';
|
import { FunctionInput } from '@/workflow/workflow-steps/workflow-actions/types/FunctionInput';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isObject } from '@sniptt/guards';
|
import { isObject } from '@sniptt/guards';
|
||||||
import { ReactNode } from 'react';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
type WorkflowEditActionFormServerlessFunctionFieldsProps = {
|
||||||
|
functionInput: FunctionInput;
|
||||||
|
path?: string[];
|
||||||
|
readonly?: boolean;
|
||||||
|
onInputChange?: (value: any, path: string[]) => void;
|
||||||
|
VariablePicker?: VariablePickerComponent;
|
||||||
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormServerlessFunctionFields = ({
|
export const WorkflowEditActionFormServerlessFunctionFields = ({
|
||||||
functionInput,
|
functionInput,
|
||||||
path = [],
|
path = [],
|
||||||
VariablePicker,
|
readonly,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
readonly = false,
|
VariablePicker,
|
||||||
}: {
|
}: WorkflowEditActionFormServerlessFunctionFieldsProps) => {
|
||||||
functionInput: FunctionInput;
|
return (
|
||||||
path?: string[];
|
<>
|
||||||
VariablePicker?: VariablePickerComponent;
|
{Object.entries(functionInput).map(([inputKey, inputValue]) => {
|
||||||
onInputChange: (value: any, path: string[]) => void;
|
const currentPath = [...path, inputKey];
|
||||||
readonly?: boolean;
|
const pathKey = currentPath.join('.');
|
||||||
}) => {
|
|
||||||
const renderFields = ({
|
if (inputValue !== null && isObject(inputValue)) {
|
||||||
functionInput,
|
return (
|
||||||
path = [],
|
<StyledContainer key={pathKey}>
|
||||||
VariablePicker,
|
<InputLabel>{inputKey}</InputLabel>
|
||||||
onInputChange,
|
<FormNestedFieldInputContainer>
|
||||||
readonly = false,
|
<WorkflowEditActionFormServerlessFunctionFields
|
||||||
}: {
|
functionInput={inputValue}
|
||||||
functionInput: FunctionInput;
|
path={currentPath}
|
||||||
path?: string[];
|
readonly={readonly}
|
||||||
VariablePicker?: VariablePickerComponent;
|
onInputChange={onInputChange}
|
||||||
onInputChange: (value: any, path: string[]) => void;
|
VariablePicker={VariablePicker}
|
||||||
readonly?: boolean;
|
/>
|
||||||
}): ReactNode[] => {
|
</FormNestedFieldInputContainer>
|
||||||
return Object.entries(functionInput).map(([inputKey, inputValue]) => {
|
</StyledContainer>
|
||||||
const currentPath = [...path, inputKey];
|
);
|
||||||
const pathKey = currentPath.join('.');
|
}
|
||||||
if (inputValue !== null && isObject(inputValue)) {
|
|
||||||
return (
|
|
||||||
<StyledContainer key={pathKey}>
|
|
||||||
<InputLabel>{inputKey}</InputLabel>
|
|
||||||
<FormNestedFieldInputContainer>
|
|
||||||
{renderFields({
|
|
||||||
functionInput: inputValue,
|
|
||||||
path: currentPath,
|
|
||||||
VariablePicker,
|
|
||||||
onInputChange,
|
|
||||||
})}
|
|
||||||
</FormNestedFieldInputContainer>
|
|
||||||
</StyledContainer>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
return (
|
||||||
<FormTextFieldInput
|
<FormTextFieldInput
|
||||||
key={pathKey}
|
key={pathKey}
|
||||||
@ -63,22 +56,10 @@ export const WorkflowEditActionFormServerlessFunctionFields = ({
|
|||||||
placeholder="Enter value"
|
placeholder="Enter value"
|
||||||
defaultValue={inputValue ? `${inputValue}` : ''}
|
defaultValue={inputValue ? `${inputValue}` : ''}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
onPersist={(value) => onInputChange(value, currentPath)}
|
onPersist={(value) => onInputChange?.(value, currentPath)}
|
||||||
VariablePicker={VariablePicker}
|
VariablePicker={VariablePicker}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{renderFields({
|
|
||||||
functionInput,
|
|
||||||
path,
|
|
||||||
VariablePicker,
|
|
||||||
onInputChange,
|
|
||||||
readonly,
|
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,104 @@
|
|||||||
|
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
|
||||||
|
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||||
|
import { WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||||
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
|
|
||||||
|
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
|
||||||
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
|
import { WorkflowEditActionFormServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunctionFields';
|
||||||
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
|
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { Monaco } from '@monaco-editor/react';
|
||||||
|
import { editor } from 'monaco-editor';
|
||||||
|
import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { CodeEditor, useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCodeEditorContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type WorkflowReadonlyActionFormServerlessFunctionProps = {
|
||||||
|
action: WorkflowCodeAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkflowReadonlyActionFormServerlessFunction = ({
|
||||||
|
action,
|
||||||
|
}: WorkflowReadonlyActionFormServerlessFunctionProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
||||||
|
const serverlessFunctionVersion =
|
||||||
|
action.settings.input.serverlessFunctionVersion;
|
||||||
|
|
||||||
|
const { availablePackages } = useGetAvailablePackages({
|
||||||
|
id: serverlessFunctionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { formValues, loading } = useServerlessFunctionUpdateFormState({
|
||||||
|
serverlessFunctionId,
|
||||||
|
serverlessFunctionVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleEditorDidMount = async (
|
||||||
|
editor: editor.IStandaloneCodeEditor,
|
||||||
|
monaco: Monaco,
|
||||||
|
) => {
|
||||||
|
await AutoTypings.create(editor, {
|
||||||
|
monaco,
|
||||||
|
preloadPackages: true,
|
||||||
|
onlySpecifiedPackages: true,
|
||||||
|
versions: availablePackages,
|
||||||
|
debounceDuration: 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerTitle = isDefined(action.name)
|
||||||
|
? action.name
|
||||||
|
: 'Code - Serverless Function';
|
||||||
|
const headerIcon = getActionIcon(action.type);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<WorkflowStepHeader
|
||||||
|
Icon={getIcon(headerIcon)}
|
||||||
|
iconColor={theme.color.orange}
|
||||||
|
initialTitle={headerTitle}
|
||||||
|
headerType="Code"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<WorkflowStepBody>
|
||||||
|
<WorkflowEditActionFormServerlessFunctionFields
|
||||||
|
functionInput={action.settings.input.serverlessFunctionInput}
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<StyledCodeEditorContainer>
|
||||||
|
<CodeEditor
|
||||||
|
height={343}
|
||||||
|
value={formValues.code?.[INDEX_FILE_PATH]}
|
||||||
|
language={'typescript'}
|
||||||
|
onMount={handleEditorDidMount}
|
||||||
|
setMarkers={getWrongExportedFunctionMarkers}
|
||||||
|
options={{
|
||||||
|
readOnly: true,
|
||||||
|
domReadOnly: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledCodeEditorContainer>
|
||||||
|
</WorkflowStepBody>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -3,7 +3,7 @@ import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithC
|
|||||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||||
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
||||||
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
||||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||||
import {
|
import {
|
||||||
@ -24,10 +24,10 @@ export const useAvailableVariablesInWorkflowStep = ({
|
|||||||
}): StepOutputSchema[] => {
|
}): StepOutputSchema[] => {
|
||||||
const workflowId = useRecoilValue(workflowIdState);
|
const workflowId = useRecoilValue(workflowIdState);
|
||||||
const workflow = useWorkflowWithCurrentVersion(workflowId);
|
const workflow = useWorkflowWithCurrentVersion(workflowId);
|
||||||
const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState);
|
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
||||||
const flow = useFlowOrThrow();
|
const flow = useFlowOrThrow();
|
||||||
|
|
||||||
if (!isDefined(workflowSelectedNode) || !isDefined(workflow)) {
|
if (!isDefined(workflow)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,9 @@ export {
|
|||||||
IconClockPlay,
|
IconClockPlay,
|
||||||
IconClockShare,
|
IconClockShare,
|
||||||
IconCode,
|
IconCode,
|
||||||
|
IconStepInto,
|
||||||
|
IconLogin2,
|
||||||
|
IconLogout,
|
||||||
IconCodeCircle,
|
IconCodeCircle,
|
||||||
IconCoins,
|
IconCoins,
|
||||||
IconColorSwatch,
|
IconColorSwatch,
|
||||||
|
|||||||
Reference in New Issue
Block a user