diff --git a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx
index 16832c625..8426c0a33 100644
--- a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx
+++ b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx
@@ -6,6 +6,7 @@ import { CommandMenuSearchRecordsPage } from '@/command-menu/pages/components/Co
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
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 { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
@@ -29,5 +30,6 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
],
[CommandMenuPages.WorkflowStepEdit, ],
[CommandMenuPages.WorkflowStepView, ],
+ [CommandMenuPages.WorkflowRunStepView, ],
[CommandMenuPages.SearchRecords, ],
]);
diff --git a/packages/twenty-front/src/modules/command-menu/types/CommandMenuPages.ts b/packages/twenty-front/src/modules/command-menu/types/CommandMenuPages.ts
index 4911abc81..57bf842fd 100644
--- a/packages/twenty-front/src/modules/command-menu/types/CommandMenuPages.ts
+++ b/packages/twenty-front/src/modules/command-menu/types/CommandMenuPages.ts
@@ -8,5 +8,6 @@ export enum CommandMenuPages {
WorkflowStepSelectAction = 'workflow-step-select-action',
WorkflowStepView = 'workflow-step-view',
WorkflowStepEdit = 'workflow-step-edit',
+ WorkflowRunStepView = 'workflow-run-step-view',
SearchRecords = 'search-records',
}
diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx
index a2d5dbcc1..957c01de0 100644
--- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx
@@ -9,8 +9,8 @@ import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isR
import { RightDrawerContainer } from '@/ui/layout/right-drawer/components/RightDrawerContainer';
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 { RightDrawerWorkflowRunViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep';
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
@@ -28,7 +28,7 @@ const StyledRightDrawerBody = styled.div`
position: relative;
`;
-const RIGHT_DRAWER_PAGES_CONFIG: ComponentByRightDrawerPage = {
+const RIGHT_DRAWER_PAGES_CONFIG = {
[RightDrawerPages.ViewEmailThread]: ,
[RightDrawerPages.ViewCalendarEvent]: ,
[RightDrawerPages.ViewRecord]: ,
@@ -41,7 +41,8 @@ const RIGHT_DRAWER_PAGES_CONFIG: ComponentByRightDrawerPage = {
),
[RightDrawerPages.WorkflowStepEdit]: ,
[RightDrawerPages.WorkflowStepView]: ,
-};
+ [RightDrawerPages.WorkflowRunStepView]: ,
+} satisfies Record;
export const RightDrawerRouter = () => {
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts
index 64f594a5f..51fcc5264 100644
--- a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts
+++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts
@@ -9,4 +9,5 @@ export const RIGHT_DRAWER_PAGE_ICONS = {
[RightDrawerPages.WorkflowStepSelectAction]: 'IconSparkles',
[RightDrawerPages.WorkflowStepEdit]: 'IconSparkles',
[RightDrawerPages.WorkflowStepView]: 'IconSparkles',
-};
+ [RightDrawerPages.WorkflowRunStepView]: 'IconSparkles',
+} satisfies Record;
diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts
index 55e2f8899..fb918821b 100644
--- a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts
+++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts
@@ -9,4 +9,5 @@ export const RIGHT_DRAWER_PAGE_TITLES = {
[RightDrawerPages.WorkflowStepSelectAction]: 'Workflow',
[RightDrawerPages.WorkflowStepEdit]: 'Workflow',
[RightDrawerPages.WorkflowStepView]: 'Workflow',
-};
+ [RightDrawerPages.WorkflowRunStepView]: 'Workflow',
+} satisfies Record;
diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/ComponentByRightDrawerPage.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/types/ComponentByRightDrawerPage.ts
deleted file mode 100644
index bcef77e8c..000000000
--- a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/ComponentByRightDrawerPage.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
-
-export type ComponentByRightDrawerPage = {
- [componentName in RightDrawerPages]?: JSX.Element;
-};
diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts
index 1ca51cb74..764b410a8 100644
--- a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts
+++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts
@@ -7,4 +7,5 @@ export enum RightDrawerPages {
WorkflowStepSelectAction = 'workflow-step-select-action',
WorkflowStepView = 'workflow-step-view',
WorkflowStepEdit = 'workflow-step-edit',
+ WorkflowRunStepView = 'workflow-run-step-view',
}
diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/mapRightDrawerPageToCommandMenuPage.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/mapRightDrawerPageToCommandMenuPage.ts
index 6a42ddc38..8a22d1e25 100644
--- a/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/mapRightDrawerPageToCommandMenuPage.ts
+++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/utils/mapRightDrawerPageToCommandMenuPage.ts
@@ -4,24 +4,25 @@ import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPage
export const mapRightDrawerPageToCommandMenuPage = (
rightDrawerPage: RightDrawerPages,
) => {
- switch (rightDrawerPage) {
- case RightDrawerPages.ViewRecord:
- return CommandMenuPages.ViewRecord;
- case RightDrawerPages.ViewEmailThread:
- return CommandMenuPages.ViewEmailThread;
- case RightDrawerPages.ViewCalendarEvent:
- return CommandMenuPages.ViewCalendarEvent;
- case RightDrawerPages.Copilot:
- return CommandMenuPages.Copilot;
- case RightDrawerPages.WorkflowStepSelectTriggerType:
- return CommandMenuPages.WorkflowStepSelectTriggerType;
- case RightDrawerPages.WorkflowStepSelectAction:
- return CommandMenuPages.WorkflowStepSelectAction;
- case RightDrawerPages.WorkflowStepView:
- return CommandMenuPages.WorkflowStepView;
- case RightDrawerPages.WorkflowStepEdit:
- return CommandMenuPages.WorkflowStepEdit;
- default:
- return CommandMenuPages.Root;
- }
+ const rightDrawerPagesToCommandMenuPages: Record<
+ RightDrawerPages,
+ CommandMenuPages
+ > = {
+ [RightDrawerPages.ViewRecord]: CommandMenuPages.ViewRecord,
+ [RightDrawerPages.ViewEmailThread]: CommandMenuPages.ViewEmailThread,
+ [RightDrawerPages.ViewCalendarEvent]: CommandMenuPages.ViewCalendarEvent,
+ [RightDrawerPages.Copilot]: CommandMenuPages.Copilot,
+ [RightDrawerPages.WorkflowStepSelectTriggerType]:
+ CommandMenuPages.WorkflowStepSelectTriggerType,
+ [RightDrawerPages.WorkflowStepSelectAction]:
+ CommandMenuPages.WorkflowStepSelectAction,
+ [RightDrawerPages.WorkflowStepView]: CommandMenuPages.WorkflowStepView,
+ [RightDrawerPages.WorkflowRunStepView]:
+ CommandMenuPages.WorkflowRunStepView,
+ [RightDrawerPages.WorkflowStepEdit]: CommandMenuPages.WorkflowStepEdit,
+ };
+
+ return (
+ rightDrawerPagesToCommandMenuPages[rightDrawerPage] ?? CommandMenuPages.Root
+ );
};
diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
index eaeb6986e..6efae7665 100644
--- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
@@ -9,6 +9,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
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 { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
@@ -26,14 +27,8 @@ const StyledShowPageRightContainer = styled.div<{ isMobile: 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')};
- gap: ${({ theme }) => theme.spacing(2)};
- height: 40px;
-`;
+`.withComponent(ShowPageSubContainerTabListContainer);
const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>`
flex: 1;
diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainerTabListContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainerTabListContainer.tsx
new file mode 100644
index 000000000..6ba063dc9
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainerTabListContainer.tsx
@@ -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 };
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
index ec3c6d965..f1794d7ea 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
@@ -9,10 +9,10 @@ import { useEffect } from 'react';
import { IconComponent } from 'twenty-ui';
import { Tab } from './Tab';
-export type SingleTabProps = {
+export type SingleTabProps = {
title: string;
Icon?: IconComponent;
- id: string;
+ id: T;
hide?: boolean;
disabled?: boolean;
pill?: string | React.ReactElement;
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts b/packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts
index 06c0d3ad7..f71b0c0d8 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts
+++ b/packages/twenty-front/src/modules/ui/layout/tab/hooks/useTabList.ts
@@ -1,13 +1,15 @@
-import { useRecoilState } from 'recoil';
+import { RecoilState, useRecoilState } from 'recoil';
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
-export const useTabList = (tabListId?: string) => {
+export const useTabList = (tabListId?: string) => {
const { activeTabIdState } = useTabListStates({
tabListScopeId: tabListId,
});
- const [activeTabId, setActiveTabId] = useRecoilState(activeTabIdState);
+ const [activeTabId, setActiveTabId] = useRecoilState(
+ activeTabIdState as RecoilState,
+ );
return {
activeTabId,
diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowRunVisualizerContent.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowRunVisualizerContent.tsx
index 49d4e58bf..dd0623d84 100644
--- a/packages/twenty-front/src/modules/workflow/components/WorkflowRunVisualizerContent.tsx
+++ b/packages/twenty-front/src/modules/workflow/components/WorkflowRunVisualizerContent.tsx
@@ -1,6 +1,6 @@
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
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 { isDefined } from 'twenty-shared';
@@ -18,7 +18,7 @@ export const WorkflowRunVisualizerContent = ({
<>
-
+
>
);
};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas.tsx
new file mode 100644
index 000000000..348c5c8d9
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvas.tsx
@@ -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 (
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx
new file mode 100644
index 000000000..2aa382275
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx
@@ -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;
+};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow.ts
new file mode 100644
index 000000000..dcc840e55
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow.ts
@@ -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;
+};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx
index 4dfd8183b..a04dbeccb 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent.tsx
@@ -1,11 +1,9 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
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 { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
-import { useRecoilValue } from 'recoil';
-import { isDefined } from 'twenty-shared';
export const RightDrawerWorkflowEditStepContent = ({
workflow,
@@ -13,13 +11,7 @@ export const RightDrawerWorkflowEditStepContent = ({
workflow: WorkflowWithCurrentVersion;
}) => {
const flow = useFlowOrThrow();
-
- 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 workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
const { updateStep } = useUpdateStep({
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep.tsx
new file mode 100644
index 000000000..c66a4d4cd
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep.tsx
@@ -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(
+ WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
+ );
+
+ const tabs: SingleTabProps[] = [
+ { id: 'node', title: 'Node', Icon: IconStepInto },
+ { id: 'input', title: 'Input', Icon: IconLogin2 },
+ { id: 'output', title: 'Output', Icon: IconLogout },
+ ];
+
+ return (
+ <>
+
+
+
+
+ {activeTabId === 'node' ? (
+
+ ) : null}
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowViewStep.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowViewStep.tsx
index a029adf75..8b7d76b1e 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowViewStep.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/RightDrawerWorkflowViewStep.tsx
@@ -1,18 +1,10 @@
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 { useRecoilValue } from 'recoil';
-import { isDefined } from 'twenty-shared';
export const RightDrawerWorkflowViewStep = () => {
const flow = useFlowOrThrow();
-
- 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.',
- );
- }
+ const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
return (
theme.background.primary};
display: flex;
+ flex: 1 1 auto;
flex-direction: column;
+ height: 100%;
overflow-y: scroll;
padding: ${({ theme }) => theme.spacing(4)};
row-gap: ${({ theme }) => theme.spacing(6)};
- flex: 1 1 auto;
- height: 100%;
`;
export { StyledWorkflowStepBody as WorkflowStepBody };
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepDetail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepDetail.tsx
index ed9b8af02..f56d759fd 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepDetail.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepDetail.tsx
@@ -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 = {
stepId: string;
trigger: WorkflowTrigger | null;
@@ -50,6 +58,7 @@ export const WorkflowStepDetail = ({
trigger,
steps,
});
+
if (!isDefined(stepDefinition) || !isDefined(stepDefinition.definition)) {
return null;
}
@@ -60,6 +69,7 @@ export const WorkflowStepDetail = ({
case 'DATABASE_EVENT': {
return (
@@ -68,6 +78,7 @@ export const WorkflowStepDetail = ({
case 'MANUAL': {
return (
@@ -76,6 +87,7 @@ export const WorkflowStepDetail = ({
case 'CRON': {
return (
@@ -93,11 +105,18 @@ export const WorkflowStepDetail = ({
case 'CODE': {
return (
}>
-
+ {props.readonly ? (
+
+ ) : (
+
+ )}
);
}
@@ -150,8 +169,6 @@ export const WorkflowStepDetail = ({
);
}
}
-
- return null;
}
}
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx
index ffb92117f..6e17ad94b 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx
@@ -43,24 +43,38 @@ const StyledHeaderIconContainer = styled.div`
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 = ({
- onTitleChange,
Icon,
iconColor,
initialTitle,
headerType,
disabled,
-}: {
- onTitleChange: (newTitle: string) => void;
- Icon: IconComponent;
- iconColor: string;
- initialTitle: string;
- headerType: string;
- disabled?: boolean;
-}) => {
+ onTitleChange,
+}: WorkflowStepHeaderProps) => {
const theme = useTheme();
+
const [title, setTitle] = useState(initialTitle);
- const debouncedOnTitleChange = useDebouncedCallback(onTitleChange, 100);
+
+ const debouncedOnTitleChange = useDebouncedCallback((newTitle: string) => {
+ onTitleChange?.(newTitle);
+ }, 100);
+
const handleChange = (newTitle: string) => {
setTitle(newTitle);
debouncedOnTitleChange(newTitle);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId.ts
new file mode 100644
index 000000000..72a76a5bc
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId.ts
@@ -0,0 +1,2 @@
+export const WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID =
+ 'workflow-run-step-side-panel-tab-list';
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunction.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunction.tsx
index 2032b4cd9..8b757c76c 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunction.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunction.tsx
@@ -17,6 +17,7 @@ import { getFunctionOutputSchema } from '@/serverless-functions/utils/getFunctio
import { mergeDefaultFunctionInputAndFunctionInput } from '@/serverless-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { InputLabel } from '@/ui/input/components/InputLabel';
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 { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
@@ -48,14 +49,7 @@ const StyledCodeEditorContainer = styled.div`
flex-direction: column;
`;
-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: ${({ theme }) => theme.spacing(10)};
+const StyledTabListContainer = styled(ShowPageSubContainerTabListContainer)`
background-color: ${({ theme }) => theme.background.secondary};
`;
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunctionFields.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunctionFields.tsx
index e09c32b1c..6069b4492 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunctionFields.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunctionFields.tsx
@@ -5,57 +5,50 @@ import { InputLabel } from '@/ui/input/components/InputLabel';
import { FunctionInput } from '@/workflow/workflow-steps/workflow-actions/types/FunctionInput';
import styled from '@emotion/styled';
import { isObject } from '@sniptt/guards';
-import { ReactNode } from 'react';
const StyledContainer = styled.div`
display: inline-flex;
flex-direction: column;
`;
+type WorkflowEditActionFormServerlessFunctionFieldsProps = {
+ functionInput: FunctionInput;
+ path?: string[];
+ readonly?: boolean;
+ onInputChange?: (value: any, path: string[]) => void;
+ VariablePicker?: VariablePickerComponent;
+};
+
export const WorkflowEditActionFormServerlessFunctionFields = ({
functionInput,
path = [],
- VariablePicker,
+ readonly,
onInputChange,
- readonly = false,
-}: {
- functionInput: FunctionInput;
- path?: string[];
- VariablePicker?: VariablePickerComponent;
- onInputChange: (value: any, path: string[]) => void;
- readonly?: boolean;
-}) => {
- const renderFields = ({
- functionInput,
- path = [],
- VariablePicker,
- onInputChange,
- readonly = false,
- }: {
- functionInput: FunctionInput;
- path?: string[];
- VariablePicker?: VariablePickerComponent;
- onInputChange: (value: any, path: string[]) => void;
- readonly?: boolean;
- }): ReactNode[] => {
- return Object.entries(functionInput).map(([inputKey, inputValue]) => {
- const currentPath = [...path, inputKey];
- const pathKey = currentPath.join('.');
- if (inputValue !== null && isObject(inputValue)) {
- return (
-
- {inputKey}
-
- {renderFields({
- functionInput: inputValue,
- path: currentPath,
- VariablePicker,
- onInputChange,
- })}
-
-
- );
- } else {
+ VariablePicker,
+}: WorkflowEditActionFormServerlessFunctionFieldsProps) => {
+ return (
+ <>
+ {Object.entries(functionInput).map(([inputKey, inputValue]) => {
+ const currentPath = [...path, inputKey];
+ const pathKey = currentPath.join('.');
+
+ if (inputValue !== null && isObject(inputValue)) {
+ return (
+
+ {inputKey}
+
+
+
+
+ );
+ }
+
return (
onInputChange(value, currentPath)}
+ onPersist={(value) => onInputChange?.(value, currentPath)}
VariablePicker={VariablePicker}
/>
);
- }
- });
- };
-
- return (
- <>
- {renderFields({
- functionInput,
- path,
- VariablePicker,
- onInputChange,
- readonly,
})}
>
);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowReadonlyActionFormServerlessFunction.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowReadonlyActionFormServerlessFunction.tsx
new file mode 100644
index 000000000..890a89606
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowReadonlyActionFormServerlessFunction.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts
index 9a1de459e..be4909ace 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts
@@ -3,7 +3,7 @@ import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithC
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
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 { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
import {
@@ -24,10 +24,10 @@ export const useAvailableVariablesInWorkflowStep = ({
}): StepOutputSchema[] => {
const workflowId = useRecoilValue(workflowIdState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
- const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState);
+ const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const flow = useFlowOrThrow();
- if (!isDefined(workflowSelectedNode) || !isDefined(workflow)) {
+ if (!isDefined(workflow)) {
return [];
}
diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
index ae5b0c30c..875338b0b 100644
--- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
+++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
@@ -52,6 +52,9 @@ export {
IconClockPlay,
IconClockShare,
IconCode,
+ IconStepInto,
+ IconLogin2,
+ IconLogout,
IconCodeCircle,
IconCoins,
IconColorSwatch,