diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts index 7c1e397c6..5a375b8b9 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup.ts @@ -20,7 +20,6 @@ import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getSh import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId'; -import { WorkflowRunTabId } from '@/workflow/workflow-steps/types/WorkflowRunTabId'; import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId'; import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId'; import { useRecoilCallback } from 'recoil'; @@ -66,7 +65,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => { activeTabIdComponentState.atomFamily({ instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, }), - WorkflowRunTabId.NODE, + null, ); set( activeTabIdComponentState.atomFamily({ diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx index b362d87fd..f0fe16e58 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep.tsx @@ -1,3 +1,5 @@ +import { getIsInputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled'; +import { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled'; import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList'; import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -15,7 +17,6 @@ import { WorkflowRunTabIdType, } from '@/workflow/workflow-steps/types/WorkflowRunTabId'; 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/utils'; import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui/display'; @@ -45,38 +46,39 @@ export const CommandMenuWorkflowRunViewStep = () => { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, ); - const stepExecutionStatus = isDefined(workflowRun) - ? getWorkflowRunStepExecutionStatus({ - workflowRunOutput: workflowRun.output, - stepId: workflowSelectedNode, - }) - : undefined; + if (!isDefined(workflowRun)) { + return null; + } - const areInputAndOutputTabsDisabled = - workflowSelectedNode === TRIGGER_STEP_ID || - stepExecutionStatus === 'running' || - stepExecutionStatus === 'not-executed'; + const stepExecutionStatus = getWorkflowRunStepExecutionStatus({ + workflowRunOutput: workflowRun.output, + stepId: workflowSelectedNode, + }); + + const isInputTabDisabled = getIsInputTabDisabled({ + stepExecutionStatus, + workflowSelectedNode, + }); + const isOutputTabDisabled = getIsOutputTabDisabled({ + stepExecutionStatus, + }); const tabs: SingleTabProps[] = [ + { + id: WorkflowRunTabId.OUTPUT, + title: 'Output', + Icon: IconLogout, + disabled: isOutputTabDisabled, + }, { id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto }, { id: WorkflowRunTabId.INPUT, title: 'Input', Icon: IconLogin2, - disabled: areInputAndOutputTabsDisabled, - }, - { - id: WorkflowRunTabId.OUTPUT, - title: 'Output', - Icon: IconLogout, - disabled: areInputAndOutputTabsDisabled, + disabled: isInputTabDisabled, }, ]; - if (!isDefined(workflowRun)) { - return null; - } - return ( { } /> + {activeTabId === WorkflowRunTabId.OUTPUT ? ( + + ) : null} + {activeTabId === WorkflowRunTabId.NODE ? ( { stepId={workflowSelectedNode} /> ) : null} - - {activeTabId === WorkflowRunTabId.OUTPUT ? ( - - ) : null} ); diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled.ts new file mode 100644 index 000000000..dd3d28b64 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled.ts @@ -0,0 +1,15 @@ +import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; +import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; + +export const getIsInputTabDisabled = ({ + stepExecutionStatus, + workflowSelectedNode, +}: { + workflowSelectedNode: string; + stepExecutionStatus: WorkflowDiagramRunStatus; +}) => { + return ( + workflowSelectedNode === TRIGGER_STEP_ID || + stepExecutionStatus === 'not-executed' + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled.ts new file mode 100644 index 000000000..bb260bf04 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled.ts @@ -0,0 +1,11 @@ +import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; + +export const getIsOutputTabDisabled = ({ + stepExecutionStatus, +}: { + stepExecutionStatus: WorkflowDiagramRunStatus; +}) => { + return ( + stepExecutionStatus === 'running' || stepExecutionStatus === 'not-executed' + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx index 065fdc9ee..4fd8631ac 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/CardComponents.tsx @@ -8,7 +8,6 @@ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableE import { FieldsCard } from '@/object-record/record-show/components/FieldsCard'; import { CardType } from '@/object-record/record-show/types/CardType'; import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer'; -import { WorkflowRunOutputVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunOutputVisualizer'; import { WorkflowRunVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizer'; import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect'; import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer'; @@ -104,7 +103,4 @@ export const CardComponents: Record = { ), - [CardType.WorkflowRunOutputCard]: ({ targetableObject }) => ( - - ), }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts index 5d10045d1..3e58a88e0 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts @@ -17,7 +17,6 @@ import { IconHome, IconMail, IconNotes, - IconPrinter, IconSettings, } from 'twenty-ui/display'; @@ -189,21 +188,7 @@ export const useRecordShowContainerTabs = ( }, [CoreObjectNameSingular.WorkflowRun]: { tabs: { - workflowRunOutput: { - title: 'Output', - position: 0, - Icon: IconPrinter, - cards: [{ type: CardType.WorkflowRunOutputCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], - }, - }, - workflowRunFlow: { + workflowRun: { title: 'Flow', position: 0, Icon: IconSettings, diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts index 6a805d0af..2045ba3d6 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts @@ -9,6 +9,5 @@ export enum CardType { WorkflowCard = 'WorkflowCard', WorkflowVersionCard = 'WorkflowVersionCard', WorkflowRunCard = 'WorkflowRunCard', - WorkflowRunOutputCard = 'WorkflowRunOutputCard', RichTextCard = 'RichTextCard', } diff --git a/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts b/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts index 88263a1be..0feadcb98 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts @@ -1,7 +1,6 @@ import { stepsOutputSchemaFamilyState } from '@/workflow/states/stepsOutputSchemaFamilyState'; import { WorkflowVersion } from '@/workflow/types/Workflow'; import { getStepOutputSchemaFamilyStateKey } from '@/workflow/utils/getStepOutputSchemaFamilyStateKey'; -import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; @@ -36,17 +35,7 @@ export const useStepsOutputSchema = () => { const trigger = workflowVersion.trigger; if (isDefined(trigger)) { - const triggerIconKey = - trigger.type === 'DATABASE_EVENT' - ? getTriggerIcon({ - type: trigger.type, - eventName: splitWorkflowTriggerEventName( - trigger.settings?.eventName, - ).event, - }) - : getTriggerIcon({ - type: trigger.type, - }); + const triggerIconKey = getTriggerIcon(trigger); const triggerOutputSchema: StepOutputSchema = { id: TRIGGER_STEP_ID, diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts index f8d81b898..257337ceb 100644 --- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts +++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts @@ -8,6 +8,7 @@ import { workflowDatabaseEventTriggerSchema, workflowDeleteRecordActionSchema, workflowDeleteRecordActionSettingsSchema, + workflowExecutorOutputSchema, workflowFindRecordsActionSchema, workflowFindRecordsActionSettingsSchema, workflowFormActionSchema, @@ -110,6 +111,9 @@ export type WorkflowVersion = { }; export type WorkflowRunOutput = z.infer; +export type WorkflowExecutorOutput = z.infer< + typeof workflowExecutorOutputSchema +>; export type WorkflowRunOutputStepsOutput = z.infer< typeof workflowRunOutputStepsOutputSchema >; diff --git a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts index 3496f7ae7..bb245d772 100644 --- a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts +++ b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts @@ -233,7 +233,7 @@ export const workflowTriggerSchema = z.discriminatedUnion('type', [ ]); // Step output schemas -const workflowExecutorOutputSchema = z.object({ +export const workflowExecutorOutputSchema = z.object({ result: z.any().optional(), error: z.string().optional(), pendingEvent: z.boolean().optional(), 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 index 70e641165..16d1123b7 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect.tsx @@ -1,16 +1,19 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { getIsInputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled'; +import { getIsOutputTabDisabled } from '@/command-menu/pages/workflow/step/view-run/utils/getIsOutputTabDisabled'; 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'; import { WorkflowDiagramNode, - WorkflowDiagramStepNodeData, + WorkflowDiagramRunStatus, + WorkflowRunDiagramStepNodeData, } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey'; import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId'; import { WorkflowRunTabId } from '@/workflow/workflow-steps/types/WorkflowRunTabId'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; +import { isNull } from '@sniptt/guards'; import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; import { useCallback } from 'react'; import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; @@ -24,9 +27,15 @@ export const WorkflowRunDiagramCanvasEffect = () => { const workflowId = useRecoilValue(workflowIdState); - const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback( + const resetWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback( ({ snapshot, set }) => - () => { + ({ + workflowSelectedNode, + stepExecutionStatus, + }: { + workflowSelectedNode: string; + stepExecutionStatus: WorkflowDiagramRunStatus; + }) => { const activeWorkflowRunRightDrawerTab = getSnapshotValue( snapshot, activeTabIdComponentState.atomFamily({ @@ -34,15 +43,40 @@ export const WorkflowRunDiagramCanvasEffect = () => { }), ) as WorkflowRunTabId | null; + const isInputTabDisabled = getIsInputTabDisabled({ + stepExecutionStatus, + workflowSelectedNode, + }); + const isOutputTabDisabled = getIsOutputTabDisabled({ + stepExecutionStatus, + }); + + if (isNull(activeWorkflowRunRightDrawerTab)) { + const defaultTabId = isOutputTabDisabled + ? WorkflowRunTabId.NODE + : WorkflowRunTabId.OUTPUT; + + set( + activeTabIdComponentState.atomFamily({ + instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, + }), + defaultTabId, + ); + + return; + } + if ( - activeWorkflowRunRightDrawerTab === 'input' || - activeWorkflowRunRightDrawerTab === 'output' + (isInputTabDisabled && + activeWorkflowRunRightDrawerTab === WorkflowRunTabId.INPUT) || + (isOutputTabDisabled && + activeWorkflowRunRightDrawerTab === WorkflowRunTabId.OUTPUT) ) { set( activeTabIdComponentState.atomFamily({ instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID, }), - 'node', + WorkflowRunTabId.NODE, ); } }, @@ -59,15 +93,8 @@ export const WorkflowRunDiagramCanvasEffect = () => { setWorkflowSelectedNode(selectedNode.id); - const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData; - - if ( - selectedNode.id === TRIGGER_STEP_ID || - selectedNodeData.runStatus === 'not-executed' || - selectedNodeData.runStatus === 'running' - ) { - goBackToFirstWorkflowRunRightDrawerTabIfNeeded(); - } + const selectedNodeData = + selectedNode.data as WorkflowRunDiagramStepNodeData; if (isDefined(workflowId)) { openWorkflowRunViewStepInCommandMenu( @@ -75,14 +102,19 @@ export const WorkflowRunDiagramCanvasEffect = () => { selectedNodeData.name, getIcon(getWorkflowNodeIconKey(selectedNodeData)), ); + + resetWorkflowRunRightDrawerTabIfNeeded({ + workflowSelectedNode: selectedNode.id, + stepExecutionStatus: selectedNodeData.runStatus, + }); } }, [ setWorkflowSelectedNode, + resetWorkflowRunRightDrawerTabIfNeeded, workflowId, - getIcon, - goBackToFirstWorkflowRunRightDrawerTabIfNeeded, openWorkflowRunViewStepInCommandMenu, + getIcon, ], ); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunOutputVisualizer.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunOutputVisualizer.tsx deleted file mode 100644 index a631203a1..000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunOutputVisualizer.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useWorkflowRunUnsafe } from '@/workflow/hooks/useWorkflowRunUnsafe'; -import styled from '@emotion/styled'; -import { isDefined } from 'twenty-shared/utils'; -import { CodeEditor } from 'twenty-ui/input'; - -const StyledSourceCodeContainer = styled.div` - margin: ${({ theme }) => theme.spacing(4)}; -`; - -export const WorkflowRunOutputVisualizer = ({ - workflowRunId, -}: { - workflowRunId: string; -}) => { - const workflowRun = useWorkflowRunUnsafe({ workflowRunId }); - - if (!isDefined(workflowRun)) { - return null; - } - - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagram.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagram.ts index d3488c8b0..1930e4e5d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagram.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagram.ts @@ -41,6 +41,13 @@ export type WorkflowDiagramStepNodeData = runStatus?: WorkflowDiagramRunStatus; }; +export type WorkflowRunDiagramStepNodeData = Exclude< + WorkflowDiagramStepNodeData, + 'runStatus' +> & { + runStatus: WorkflowDiagramRunStatus; +}; + export type WorkflowDiagramCreateStepNodeData = { nodeType: 'create-step'; parentNodeId: string; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts index e8fb18329..7f67529ca 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts @@ -19,25 +19,19 @@ export const getWorkflowDiagramTriggerNode = ({ switch (trigger.type) { case 'MANUAL': { triggerDefaultLabel = 'Manual Trigger'; - triggerIcon = getTriggerIcon({ - type: 'MANUAL', - }); + triggerIcon = getTriggerIcon(trigger); break; } case 'CRON': { triggerDefaultLabel = 'On a Schedule'; - triggerIcon = getTriggerIcon({ - type: 'CRON', - }); + triggerIcon = getTriggerIcon(trigger); break; } case 'WEBHOOK': { triggerDefaultLabel = 'Webhook'; - triggerIcon = getTriggerIcon({ - type: 'WEBHOOK', - }); + triggerIcon = getTriggerIcon(trigger); break; } @@ -50,10 +44,7 @@ export const getWorkflowDiagramTriggerNode = ({ DATABASE_TRIGGER_TYPES.find((item) => item.event === triggerEvent.event) ?.defaultLabel ?? ''; - triggerIcon = getTriggerIcon({ - type: 'DATABASE_EVENT', - eventName: triggerEvent.event, - }); + triggerIcon = getTriggerIcon(trigger); break; } diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx index 209bf3bcf..2bec95129 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx @@ -1,11 +1,15 @@ import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; +import { WorkflowExecutorOutput } from '@/workflow/types/Workflow'; import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow'; +import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType'; +import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; +import { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; import { isDefined } from 'twenty-shared/utils'; @@ -30,24 +34,38 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => { return null; } - const stepOutput = workflowRun.output.stepsOutput[stepId]; + const stepOutput = workflowRun.output.stepsOutput[stepId] as + | WorkflowExecutorOutput + | undefined; const stepDefinition = getStepDefinitionOrThrow({ stepId, trigger: workflowRun.output.flow.trigger, steps: workflowRun.output.flow.steps, }); - if (stepDefinition?.type !== 'action') { - throw new Error('The output tab must be rendered with an action step.'); + if ( + !isDefined(stepDefinition?.definition) || + !isDefined(stepDefinition.definition.name) + ) { + throw new Error('The step is expected to be properly shaped.'); } const headerTitle = stepDefinition.definition.name; - const headerIcon = getActionIcon(stepDefinition.definition.type); - const headerIconColor = getActionIconColorOrThrow({ - theme, - actionType: stepDefinition.definition.type, - }); - const headerType = getActionHeaderTypeOrThrow(stepDefinition.definition.type); + const headerIcon = + stepDefinition.type === 'trigger' + ? getTriggerIcon(stepDefinition.definition) + : getActionIcon(stepDefinition.definition.type); + const headerIconColor = + stepDefinition.type === 'trigger' + ? getTriggerIconColor({ theme }) + : getActionIconColorOrThrow({ + theme, + actionType: stepDefinition.definition.type, + }); + const headerType = + stepDefinition.type === 'trigger' + ? getTriggerHeaderType(stepDefinition.definition) + : i18n._(getActionHeaderTypeOrThrow(stepDefinition.definition.type)); const setRedHighlightingForEveryNode: GetJsonNodeHighlighting = () => 'red'; @@ -58,12 +76,12 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => { Icon={getIcon(headerIcon)} iconColor={headerIconColor} initialTitle={headerTitle} - headerType={i18n._(headerType)} + headerType={headerType} /> { arrowButtonCollapsedLabel={t`Expand`} arrowButtonExpandedLabel={t`Collapse`} getNodeHighlighting={ - isDefined(stepOutput.error) + isDefined(stepOutput?.error) ? setRedHighlightingForEveryNode : undefined } diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus.ts index af317d8a8..9604436fb 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus.ts @@ -1,5 +1,6 @@ import { WorkflowRunOutput } from '@/workflow/types/Workflow'; import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; +import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { isNull } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; @@ -14,6 +15,10 @@ export const getWorkflowRunStepExecutionStatus = ({ return 'not-executed'; } + if (stepId === TRIGGER_STEP_ID) { + return 'success'; + } + const stepOutput = workflowRunOutput.stepsOutput?.[stepId]; if (isDefined(stepOutput?.error)) { diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx index 86bb24be4..190ff814d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx @@ -6,6 +6,7 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { CRON_TRIGGER_INTERVAL_OPTIONS } from '@/workflow/workflow-trigger/constants/CronTriggerIntervalOptions'; import { getCronTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getCronTriggerDefaultSettings'; +import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel'; import { useTheme } from '@emotion/react'; @@ -48,18 +49,11 @@ export const WorkflowEditTriggerCronForm = ({ const { getIcon } = useIcons(); - const headerIcon = getTriggerIcon({ - type: 'CRON', - }); + const headerIcon = getTriggerIcon(trigger); - const defaultLabel = - getTriggerDefaultLabel({ - type: 'CRON', - }) ?? ''; - - const headerTitle = isDefined(trigger.name) ? trigger.name : defaultLabel; - - const headerType = 'Trigger'; + const defaultLabel = getTriggerDefaultLabel(trigger); + const headerTitle = trigger.name ?? defaultLabel; + const headerType = getTriggerHeaderType(trigger); const onBlur = () => { setErrorMessagesVisible(true); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx index 46e31caf4..64f2d96b1 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx @@ -12,6 +12,7 @@ import { WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow'; import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; +import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel'; import { useTheme } from '@emotion/react'; @@ -105,18 +106,9 @@ export const WorkflowEditTriggerDatabaseEventForm = ({ [systemObjects, searchInputValue], ); - const defaultLabel = - getTriggerDefaultLabel({ - type: 'DATABASE_EVENT', - eventName: triggerEvent.event, - }) ?? '-'; - - const headerIcon = getTriggerIcon({ - type: 'DATABASE_EVENT', - eventName: triggerEvent.event, - }); - - const headerType = `Trigger · ${defaultLabel}`; + const defaultLabel = trigger.name ?? getTriggerDefaultLabel(trigger); + const headerIcon = getTriggerIcon(trigger); + const headerType = getTriggerHeaderType(trigger); const handleOptionClick = (value: string) => { if (triggerOptions.readonly === true) { diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx index 3575ce756..792951b00 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx @@ -8,11 +8,13 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions'; import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings'; +import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; +import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel'; import { useTheme } from '@emotion/react'; import { isDefined } from 'twenty-shared/utils'; -import { SelectOption } from 'twenty-ui/input'; import { useIcons } from 'twenty-ui/display'; +import { SelectOption } from 'twenty-ui/input'; type WorkflowEditTriggerManualFormProps = { trigger: WorkflowManualTrigger; @@ -48,11 +50,10 @@ export const WorkflowEditTriggerManualForm = ({ ? 'WHEN_RECORD_SELECTED' : 'EVERYWHERE'; - const headerTitle = isDefined(trigger.name) ? trigger.name : 'Manual Trigger'; + const headerTitle = trigger.name ?? getTriggerDefaultLabel(trigger); - const headerIcon = getTriggerIcon({ - type: 'MANUAL', - }); + const headerIcon = getTriggerIcon(trigger); + const headerType = getTriggerHeaderType(trigger); return ( <> @@ -70,7 +71,7 @@ export const WorkflowEditTriggerManualForm = ({ Icon={getIcon(headerIcon)} iconColor={theme.font.color.tertiary} initialTitle={headerTitle} - headerType="Trigger · Manual" + headerType={headerType} disabled={triggerOptions.readonly} /> diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx index 9436d8e5f..0b39d9d53 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx @@ -1,26 +1,28 @@ -import { WorkflowWebhookTrigger } from '@/workflow/types/Workflow'; -import { useTheme } from '@emotion/react'; -import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; -import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; -import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; -import { TextInputV2 } from '@/ui/input/components/TextInputV2'; -import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; -import { useDebouncedCallback } from 'use-debounce'; -import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { REACT_APP_SERVER_BASE_URL } from '~/config'; -import { isDefined } from 'twenty-shared/utils'; -import { useIcons, IconCopy } from 'twenty-ui/display'; -import { Select } from '@/ui/input/components/Select'; -import { WEBHOOK_TRIGGER_HTTP_METHOD_OPTIONS } from '@/workflow/workflow-trigger/constants/WebhookTriggerHttpMethodOptions'; -import { getWebhookTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getWebhookTriggerDefaultSettings'; -import { WEBHOOK_TRIGGER_AUTHENTICATION_OPTIONS } from '@/workflow/workflow-trigger/constants/WebhookTriggerAuthenticationOptions'; import { FormRawJsonFieldInput } from '@/object-record/record-field/form-types/components/FormRawJsonFieldInput'; -import { useState } from 'react'; import { getFunctionOutputSchema } from '@/serverless-functions/utils/getFunctionOutputSchema'; +import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { Select } from '@/ui/input/components/Select'; +import { TextInputV2 } from '@/ui/input/components/TextInputV2'; +import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { WorkflowWebhookTrigger } from '@/workflow/types/Workflow'; +import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; +import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; +import { WEBHOOK_TRIGGER_AUTHENTICATION_OPTIONS } from '@/workflow/workflow-trigger/constants/WebhookTriggerAuthenticationOptions'; +import { WEBHOOK_TRIGGER_HTTP_METHOD_OPTIONS } from '@/workflow/workflow-trigger/constants/WebhookTriggerHttpMethodOptions'; +import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType'; +import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; +import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel'; +import { getWebhookTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getWebhookTriggerDefaultSettings'; +import { useTheme } from '@emotion/react'; +import { useLingui } from '@lingui/react/macro'; +import { useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; +import { IconCopy, useIcons } from 'twenty-ui/display'; +import { useDebouncedCallback } from 'use-debounce'; +import { REACT_APP_SERVER_BASE_URL } from '~/config'; type WorkflowEditTriggerWebhookFormProps = { trigger: WorkflowWebhookTrigger; @@ -59,11 +61,10 @@ export const WorkflowEditTriggerWebhookForm = ({ setErrorMessagesVisible(true); }; - const headerTitle = isDefined(trigger.name) ? trigger.name : 'Webhook'; + const headerTitle = trigger.name ?? getTriggerDefaultLabel(trigger); - const headerIcon = getTriggerIcon({ - type: 'WEBHOOK', - }); + const headerIcon = getTriggerIcon(trigger); + const headerType = getTriggerHeaderType(trigger); const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowId}`; const displayWebhookUrl = webhookUrl.replace(/^(https?:\/\/)?(www\.)?/, ''); @@ -98,7 +99,7 @@ export const WorkflowEditTriggerWebhookForm = ({ Icon={getIcon(headerIcon)} iconColor={theme.font.color.tertiary} initialTitle={headerTitle} - headerType="Trigger · Webhook" + headerType={headerType} disabled={triggerOptions.readonly} /> diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerHeaderType.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerHeaderType.ts new file mode 100644 index 000000000..8e32e008f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerHeaderType.ts @@ -0,0 +1,25 @@ +import { WorkflowTrigger } from '@/workflow/types/Workflow'; +import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel'; +import { assertUnreachable } from 'twenty-shared/utils'; + +export const getTriggerHeaderType = (trigger: WorkflowTrigger) => { + switch (trigger.type) { + case 'CRON': { + return 'Trigger'; + } + case 'WEBHOOK': { + return 'Trigger · Webhook'; + } + case 'MANUAL': { + return 'Trigger · Manual'; + } + case 'DATABASE_EVENT': { + const defaultLabel = getTriggerDefaultLabel(trigger); + + return `Trigger · ${defaultLabel}`; + } + default: { + assertUnreachable(trigger, 'Unknown trigger type'); + } + } +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIcon.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIcon.ts index 1e0d1496c..f9087fa22 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIcon.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIcon.ts @@ -1,26 +1,18 @@ +import { WorkflowTrigger } from '@/workflow/types/Workflow'; +import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName'; import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes'; import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes'; export const getTriggerIcon = ( - trigger: - | { - type: 'MANUAL'; - } - | { - type: 'CRON'; - } - | { - type: 'WEBHOOK'; - } - | { - type: 'DATABASE_EVENT'; - eventName: string; - }, + trigger: WorkflowTrigger, ): string | undefined => { if (trigger.type === 'DATABASE_EVENT') { - return DATABASE_TRIGGER_TYPES.find( - (type) => type.event === trigger.eventName, - )?.icon; + const eventName = splitWorkflowTriggerEventName( + trigger.settings.eventName, + ).event; + + return DATABASE_TRIGGER_TYPES.find((type) => type.event === eventName) + ?.icon; } return OTHER_TRIGGER_TYPES.find((item) => item.type === trigger.type)?.icon; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIconColor.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIconColor.ts new file mode 100644 index 000000000..8cf211ede --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIconColor.ts @@ -0,0 +1,5 @@ +import { Theme } from '@emotion/react'; + +export const getTriggerIconColor = ({ theme }: { theme: Theme }) => { + return theme.font.color.tertiary; +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerLabel.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerLabel.ts index 2f0f08ea9..6e8ea8027 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerLabel.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerLabel.ts @@ -1,28 +1,33 @@ +import { WorkflowTrigger } from '@/workflow/types/Workflow'; +import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName'; import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes'; import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes'; +import { isDefined } from 'twenty-shared/utils'; -export const getTriggerDefaultLabel = ( - trigger: - | { - type: 'MANUAL'; - } - | { - type: 'CRON'; - } - | { - type: 'WEBHOOK'; - } - | { - type: 'DATABASE_EVENT'; - eventName: string; - }, -): string | undefined => { +export const getTriggerDefaultLabel = (trigger: WorkflowTrigger): string => { if (trigger.type === 'DATABASE_EVENT') { - return DATABASE_TRIGGER_TYPES.find( - (type) => type.event === trigger.eventName, + const triggerEvent = splitWorkflowTriggerEventName( + trigger.settings.eventName, + ); + + const label = DATABASE_TRIGGER_TYPES.find( + (type) => type.event === triggerEvent.event, )?.defaultLabel; + + if (!isDefined(label)) { + throw new Error('Unknown trigger event'); + } + + return label; } - return OTHER_TRIGGER_TYPES.find((item) => item.type === trigger.type) - ?.defaultLabel; + const label = OTHER_TRIGGER_TYPES.find( + (item) => item.type === trigger.type, + )?.defaultLabel; + + if (!isDefined(label)) { + throw new Error('Unknown trigger type'); + } + + return label; }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getTriggerStepName.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getTriggerStepName.ts index 8e26c2595..3d189ea73 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getTriggerStepName.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getTriggerStepName.ts @@ -28,11 +28,7 @@ export const getTriggerStepName = (trigger: WorkflowTrigger): string => { const getDatabaseEventTriggerStepName = ( trigger: WorkflowDatabaseEventTrigger, ): string => { - const [, action] = trigger.settings.eventName.split('.'); - const defaultLabel = getTriggerDefaultLabel({ - type: 'DATABASE_EVENT', - eventName: action, - }); + const defaultLabel = getTriggerDefaultLabel(trigger); return defaultLabel ?? ''; };