Only display Flow for Workflow Runs and display Output tab for triggers (#11520)
> [!WARNING] > I refactored a bunch of components into utility functions to make it possible to display the `WorkflowStepHeader` component for **triggers** in the `CommandMenuWorkflowRunViewStep` component. Previously, we were asserting that we were displaying the header in `Output` and `Input` tabs only for **actions**. Handling triggers too required a bunch of changes. We can think of making a bigger refactor of this part. In this PR: - Only display the Flow for Workflow Runs; removed the Code Editor tab - Allows users to see the Output of trigger nodes - Prevent impossible states by manually setting the selected tab when selecting a node ## Demo ### Success, Running and Not Executed steps https://github.com/user-attachments/assets/c6bebd0f-5da2-4ccc-aef2-d9890eafa59a ### Failed step https://github.com/user-attachments/assets/e1f4e13a-2f5e-4792-a089-928e4d6b1ac0 Closes https://github.com/twentyhq/core-team-issues/issues/709
This commit is contained in:
committed by
GitHub
parent
c8011da4d7
commit
e8488e1da0
@ -20,7 +20,6 @@ import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getSh
|
|||||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
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 { 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 { 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 { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
@ -66,7 +65,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
|||||||
activeTabIdComponentState.atomFamily({
|
activeTabIdComponentState.atomFamily({
|
||||||
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
}),
|
}),
|
||||||
WorkflowRunTabId.NODE,
|
null,
|
||||||
);
|
);
|
||||||
set(
|
set(
|
||||||
activeTabIdComponentState.atomFamily({
|
activeTabIdComponentState.atomFamily({
|
||||||
|
|||||||
@ -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 { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
@ -15,7 +17,6 @@ import {
|
|||||||
WorkflowRunTabIdType,
|
WorkflowRunTabIdType,
|
||||||
} from '@/workflow/workflow-steps/types/WorkflowRunTabId';
|
} from '@/workflow/workflow-steps/types/WorkflowRunTabId';
|
||||||
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
|
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
|
||||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui/display';
|
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui/display';
|
||||||
@ -45,38 +46,39 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
|||||||
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
const stepExecutionStatus = isDefined(workflowRun)
|
if (!isDefined(workflowRun)) {
|
||||||
? getWorkflowRunStepExecutionStatus({
|
return null;
|
||||||
workflowRunOutput: workflowRun.output,
|
}
|
||||||
stepId: workflowSelectedNode,
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const areInputAndOutputTabsDisabled =
|
const stepExecutionStatus = getWorkflowRunStepExecutionStatus({
|
||||||
workflowSelectedNode === TRIGGER_STEP_ID ||
|
workflowRunOutput: workflowRun.output,
|
||||||
stepExecutionStatus === 'running' ||
|
stepId: workflowSelectedNode,
|
||||||
stepExecutionStatus === 'not-executed';
|
});
|
||||||
|
|
||||||
|
const isInputTabDisabled = getIsInputTabDisabled({
|
||||||
|
stepExecutionStatus,
|
||||||
|
workflowSelectedNode,
|
||||||
|
});
|
||||||
|
const isOutputTabDisabled = getIsOutputTabDisabled({
|
||||||
|
stepExecutionStatus,
|
||||||
|
});
|
||||||
|
|
||||||
const tabs: SingleTabProps<TabId>[] = [
|
const tabs: SingleTabProps<TabId>[] = [
|
||||||
|
{
|
||||||
|
id: WorkflowRunTabId.OUTPUT,
|
||||||
|
title: 'Output',
|
||||||
|
Icon: IconLogout,
|
||||||
|
disabled: isOutputTabDisabled,
|
||||||
|
},
|
||||||
{ id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto },
|
{ id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto },
|
||||||
{
|
{
|
||||||
id: WorkflowRunTabId.INPUT,
|
id: WorkflowRunTabId.INPUT,
|
||||||
title: 'Input',
|
title: 'Input',
|
||||||
Icon: IconLogin2,
|
Icon: IconLogin2,
|
||||||
disabled: areInputAndOutputTabsDisabled,
|
disabled: isInputTabDisabled,
|
||||||
},
|
|
||||||
{
|
|
||||||
id: WorkflowRunTabId.OUTPUT,
|
|
||||||
title: 'Output',
|
|
||||||
Icon: IconLogout,
|
|
||||||
disabled: areInputAndOutputTabsDisabled,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!isDefined(workflowRun)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowStepContextProvider
|
<WorkflowStepContextProvider
|
||||||
value={{
|
value={{
|
||||||
@ -93,6 +95,13 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{activeTabId === WorkflowRunTabId.OUTPUT ? (
|
||||||
|
<WorkflowRunStepOutputDetail
|
||||||
|
key={workflowSelectedNode}
|
||||||
|
stepId={workflowSelectedNode}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{activeTabId === WorkflowRunTabId.NODE ? (
|
{activeTabId === WorkflowRunTabId.NODE ? (
|
||||||
<WorkflowRunStepNodeDetail
|
<WorkflowRunStepNodeDetail
|
||||||
stepId={workflowSelectedNode}
|
stepId={workflowSelectedNode}
|
||||||
@ -108,13 +117,6 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
|||||||
stepId={workflowSelectedNode}
|
stepId={workflowSelectedNode}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{activeTabId === WorkflowRunTabId.OUTPUT ? (
|
|
||||||
<WorkflowRunStepOutputDetail
|
|
||||||
key={workflowSelectedNode}
|
|
||||||
stepId={workflowSelectedNode}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</WorkflowStepContextProvider>
|
</WorkflowStepContextProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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'
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||||
|
|
||||||
|
export const getIsOutputTabDisabled = ({
|
||||||
|
stepExecutionStatus,
|
||||||
|
}: {
|
||||||
|
stepExecutionStatus: WorkflowDiagramRunStatus;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
stepExecutionStatus === 'running' || stepExecutionStatus === 'not-executed'
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -8,7 +8,6 @@ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableE
|
|||||||
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
||||||
import { CardType } from '@/object-record/record-show/types/CardType';
|
import { CardType } from '@/object-record/record-show/types/CardType';
|
||||||
import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
|
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 { WorkflowRunVisualizer } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizer';
|
||||||
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
|
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
|
||||||
import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer';
|
import { WorkflowVersionVisualizer } from '@/workflow/workflow-diagram/components/WorkflowVersionVisualizer';
|
||||||
@ -104,7 +103,4 @@ export const CardComponents: Record<CardType, CardComponentType> = {
|
|||||||
<WorkflowRunVisualizer workflowRunId={targetableObject.id} />
|
<WorkflowRunVisualizer workflowRunId={targetableObject.id} />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
[CardType.WorkflowRunOutputCard]: ({ targetableObject }) => (
|
|
||||||
<WorkflowRunOutputVisualizer workflowRunId={targetableObject.id} />
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
IconHome,
|
IconHome,
|
||||||
IconMail,
|
IconMail,
|
||||||
IconNotes,
|
IconNotes,
|
||||||
IconPrinter,
|
|
||||||
IconSettings,
|
IconSettings,
|
||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
|
|
||||||
@ -189,21 +188,7 @@ export const useRecordShowContainerTabs = (
|
|||||||
},
|
},
|
||||||
[CoreObjectNameSingular.WorkflowRun]: {
|
[CoreObjectNameSingular.WorkflowRun]: {
|
||||||
tabs: {
|
tabs: {
|
||||||
workflowRunOutput: {
|
workflowRun: {
|
||||||
title: 'Output',
|
|
||||||
position: 0,
|
|
||||||
Icon: IconPrinter,
|
|
||||||
cards: [{ type: CardType.WorkflowRunOutputCard }],
|
|
||||||
hide: {
|
|
||||||
ifMobile: false,
|
|
||||||
ifDesktop: false,
|
|
||||||
ifInRightDrawer: false,
|
|
||||||
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
|
|
||||||
ifRequiredObjectsInactive: [],
|
|
||||||
ifRelationsMissing: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
workflowRunFlow: {
|
|
||||||
title: 'Flow',
|
title: 'Flow',
|
||||||
position: 0,
|
position: 0,
|
||||||
Icon: IconSettings,
|
Icon: IconSettings,
|
||||||
|
|||||||
@ -9,6 +9,5 @@ export enum CardType {
|
|||||||
WorkflowCard = 'WorkflowCard',
|
WorkflowCard = 'WorkflowCard',
|
||||||
WorkflowVersionCard = 'WorkflowVersionCard',
|
WorkflowVersionCard = 'WorkflowVersionCard',
|
||||||
WorkflowRunCard = 'WorkflowRunCard',
|
WorkflowRunCard = 'WorkflowRunCard',
|
||||||
WorkflowRunOutputCard = 'WorkflowRunOutputCard',
|
|
||||||
RichTextCard = 'RichTextCard',
|
RichTextCard = 'RichTextCard',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { stepsOutputSchemaFamilyState } from '@/workflow/states/stepsOutputSchemaFamilyState';
|
import { stepsOutputSchemaFamilyState } from '@/workflow/states/stepsOutputSchemaFamilyState';
|
||||||
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
||||||
import { getStepOutputSchemaFamilyStateKey } from '@/workflow/utils/getStepOutputSchemaFamilyStateKey';
|
import { getStepOutputSchemaFamilyStateKey } from '@/workflow/utils/getStepOutputSchemaFamilyStateKey';
|
||||||
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
|
||||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||||
@ -36,17 +35,7 @@ export const useStepsOutputSchema = () => {
|
|||||||
const trigger = workflowVersion.trigger;
|
const trigger = workflowVersion.trigger;
|
||||||
|
|
||||||
if (isDefined(trigger)) {
|
if (isDefined(trigger)) {
|
||||||
const triggerIconKey =
|
const triggerIconKey = getTriggerIcon(trigger);
|
||||||
trigger.type === 'DATABASE_EVENT'
|
|
||||||
? getTriggerIcon({
|
|
||||||
type: trigger.type,
|
|
||||||
eventName: splitWorkflowTriggerEventName(
|
|
||||||
trigger.settings?.eventName,
|
|
||||||
).event,
|
|
||||||
})
|
|
||||||
: getTriggerIcon({
|
|
||||||
type: trigger.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerOutputSchema: StepOutputSchema = {
|
const triggerOutputSchema: StepOutputSchema = {
|
||||||
id: TRIGGER_STEP_ID,
|
id: TRIGGER_STEP_ID,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
workflowDatabaseEventTriggerSchema,
|
workflowDatabaseEventTriggerSchema,
|
||||||
workflowDeleteRecordActionSchema,
|
workflowDeleteRecordActionSchema,
|
||||||
workflowDeleteRecordActionSettingsSchema,
|
workflowDeleteRecordActionSettingsSchema,
|
||||||
|
workflowExecutorOutputSchema,
|
||||||
workflowFindRecordsActionSchema,
|
workflowFindRecordsActionSchema,
|
||||||
workflowFindRecordsActionSettingsSchema,
|
workflowFindRecordsActionSettingsSchema,
|
||||||
workflowFormActionSchema,
|
workflowFormActionSchema,
|
||||||
@ -110,6 +111,9 @@ export type WorkflowVersion = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowRunOutput = z.infer<typeof workflowRunOutputSchema>;
|
export type WorkflowRunOutput = z.infer<typeof workflowRunOutputSchema>;
|
||||||
|
export type WorkflowExecutorOutput = z.infer<
|
||||||
|
typeof workflowExecutorOutputSchema
|
||||||
|
>;
|
||||||
export type WorkflowRunOutputStepsOutput = z.infer<
|
export type WorkflowRunOutputStepsOutput = z.infer<
|
||||||
typeof workflowRunOutputStepsOutputSchema
|
typeof workflowRunOutputStepsOutputSchema
|
||||||
>;
|
>;
|
||||||
|
|||||||
@ -233,7 +233,7 @@ export const workflowTriggerSchema = z.discriminatedUnion('type', [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Step output schemas
|
// Step output schemas
|
||||||
const workflowExecutorOutputSchema = z.object({
|
export const workflowExecutorOutputSchema = z.object({
|
||||||
result: z.any().optional(),
|
result: z.any().optional(),
|
||||||
error: z.string().optional(),
|
error: z.string().optional(),
|
||||||
pendingEvent: z.boolean().optional(),
|
pendingEvent: z.boolean().optional(),
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
|
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 { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
||||||
import {
|
import {
|
||||||
WorkflowDiagramNode,
|
WorkflowDiagramNode,
|
||||||
WorkflowDiagramStepNodeData,
|
WorkflowDiagramRunStatus,
|
||||||
|
WorkflowRunDiagramStepNodeData,
|
||||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||||
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
|
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 { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
|
||||||
import { WorkflowRunTabId } from '@/workflow/workflow-steps/types/WorkflowRunTabId';
|
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 { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
@ -24,9 +27,15 @@ export const WorkflowRunDiagramCanvasEffect = () => {
|
|||||||
|
|
||||||
const workflowId = useRecoilValue(workflowIdState);
|
const workflowId = useRecoilValue(workflowIdState);
|
||||||
|
|
||||||
const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback(
|
const resetWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
() => {
|
({
|
||||||
|
workflowSelectedNode,
|
||||||
|
stepExecutionStatus,
|
||||||
|
}: {
|
||||||
|
workflowSelectedNode: string;
|
||||||
|
stepExecutionStatus: WorkflowDiagramRunStatus;
|
||||||
|
}) => {
|
||||||
const activeWorkflowRunRightDrawerTab = getSnapshotValue(
|
const activeWorkflowRunRightDrawerTab = getSnapshotValue(
|
||||||
snapshot,
|
snapshot,
|
||||||
activeTabIdComponentState.atomFamily({
|
activeTabIdComponentState.atomFamily({
|
||||||
@ -34,15 +43,40 @@ export const WorkflowRunDiagramCanvasEffect = () => {
|
|||||||
}),
|
}),
|
||||||
) as WorkflowRunTabId | null;
|
) 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 (
|
if (
|
||||||
activeWorkflowRunRightDrawerTab === 'input' ||
|
(isInputTabDisabled &&
|
||||||
activeWorkflowRunRightDrawerTab === 'output'
|
activeWorkflowRunRightDrawerTab === WorkflowRunTabId.INPUT) ||
|
||||||
|
(isOutputTabDisabled &&
|
||||||
|
activeWorkflowRunRightDrawerTab === WorkflowRunTabId.OUTPUT)
|
||||||
) {
|
) {
|
||||||
set(
|
set(
|
||||||
activeTabIdComponentState.atomFamily({
|
activeTabIdComponentState.atomFamily({
|
||||||
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
}),
|
}),
|
||||||
'node',
|
WorkflowRunTabId.NODE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -59,15 +93,8 @@ export const WorkflowRunDiagramCanvasEffect = () => {
|
|||||||
|
|
||||||
setWorkflowSelectedNode(selectedNode.id);
|
setWorkflowSelectedNode(selectedNode.id);
|
||||||
|
|
||||||
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
|
const selectedNodeData =
|
||||||
|
selectedNode.data as WorkflowRunDiagramStepNodeData;
|
||||||
if (
|
|
||||||
selectedNode.id === TRIGGER_STEP_ID ||
|
|
||||||
selectedNodeData.runStatus === 'not-executed' ||
|
|
||||||
selectedNodeData.runStatus === 'running'
|
|
||||||
) {
|
|
||||||
goBackToFirstWorkflowRunRightDrawerTabIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefined(workflowId)) {
|
if (isDefined(workflowId)) {
|
||||||
openWorkflowRunViewStepInCommandMenu(
|
openWorkflowRunViewStepInCommandMenu(
|
||||||
@ -75,14 +102,19 @@ export const WorkflowRunDiagramCanvasEffect = () => {
|
|||||||
selectedNodeData.name,
|
selectedNodeData.name,
|
||||||
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
|
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
resetWorkflowRunRightDrawerTabIfNeeded({
|
||||||
|
workflowSelectedNode: selectedNode.id,
|
||||||
|
stepExecutionStatus: selectedNodeData.runStatus,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
setWorkflowSelectedNode,
|
setWorkflowSelectedNode,
|
||||||
|
resetWorkflowRunRightDrawerTabIfNeeded,
|
||||||
workflowId,
|
workflowId,
|
||||||
getIcon,
|
|
||||||
goBackToFirstWorkflowRunRightDrawerTabIfNeeded,
|
|
||||||
openWorkflowRunViewStepInCommandMenu,
|
openWorkflowRunViewStepInCommandMenu,
|
||||||
|
getIcon,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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 (
|
|
||||||
<StyledSourceCodeContainer>
|
|
||||||
<CodeEditor
|
|
||||||
value={JSON.stringify(workflowRun.output, null, 2)}
|
|
||||||
language="json"
|
|
||||||
options={{ readOnly: true, domReadOnly: true }}
|
|
||||||
/>
|
|
||||||
</StyledSourceCodeContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -41,6 +41,13 @@ export type WorkflowDiagramStepNodeData =
|
|||||||
runStatus?: WorkflowDiagramRunStatus;
|
runStatus?: WorkflowDiagramRunStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WorkflowRunDiagramStepNodeData = Exclude<
|
||||||
|
WorkflowDiagramStepNodeData,
|
||||||
|
'runStatus'
|
||||||
|
> & {
|
||||||
|
runStatus: WorkflowDiagramRunStatus;
|
||||||
|
};
|
||||||
|
|
||||||
export type WorkflowDiagramCreateStepNodeData = {
|
export type WorkflowDiagramCreateStepNodeData = {
|
||||||
nodeType: 'create-step';
|
nodeType: 'create-step';
|
||||||
parentNodeId: string;
|
parentNodeId: string;
|
||||||
|
|||||||
@ -19,25 +19,19 @@ export const getWorkflowDiagramTriggerNode = ({
|
|||||||
switch (trigger.type) {
|
switch (trigger.type) {
|
||||||
case 'MANUAL': {
|
case 'MANUAL': {
|
||||||
triggerDefaultLabel = 'Manual Trigger';
|
triggerDefaultLabel = 'Manual Trigger';
|
||||||
triggerIcon = getTriggerIcon({
|
triggerIcon = getTriggerIcon(trigger);
|
||||||
type: 'MANUAL',
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'CRON': {
|
case 'CRON': {
|
||||||
triggerDefaultLabel = 'On a Schedule';
|
triggerDefaultLabel = 'On a Schedule';
|
||||||
triggerIcon = getTriggerIcon({
|
triggerIcon = getTriggerIcon(trigger);
|
||||||
type: 'CRON',
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'WEBHOOK': {
|
case 'WEBHOOK': {
|
||||||
triggerDefaultLabel = 'Webhook';
|
triggerDefaultLabel = 'Webhook';
|
||||||
triggerIcon = getTriggerIcon({
|
triggerIcon = getTriggerIcon(trigger);
|
||||||
type: 'WEBHOOK',
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -50,10 +44,7 @@ export const getWorkflowDiagramTriggerNode = ({
|
|||||||
DATABASE_TRIGGER_TYPES.find((item) => item.event === triggerEvent.event)
|
DATABASE_TRIGGER_TYPES.find((item) => item.event === triggerEvent.event)
|
||||||
?.defaultLabel ?? '';
|
?.defaultLabel ?? '';
|
||||||
|
|
||||||
triggerIcon = getTriggerIcon({
|
triggerIcon = getTriggerIcon(trigger);
|
||||||
type: 'DATABASE_EVENT',
|
|
||||||
eventName: triggerEvent.event,
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||||
|
import { WorkflowExecutorOutput } from '@/workflow/types/Workflow';
|
||||||
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
||||||
import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer';
|
import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer';
|
||||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
|
import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
|
||||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
|
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 { useTheme } from '@emotion/react';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -30,24 +34,38 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stepOutput = workflowRun.output.stepsOutput[stepId];
|
const stepOutput = workflowRun.output.stepsOutput[stepId] as
|
||||||
|
| WorkflowExecutorOutput
|
||||||
|
| undefined;
|
||||||
|
|
||||||
const stepDefinition = getStepDefinitionOrThrow({
|
const stepDefinition = getStepDefinitionOrThrow({
|
||||||
stepId,
|
stepId,
|
||||||
trigger: workflowRun.output.flow.trigger,
|
trigger: workflowRun.output.flow.trigger,
|
||||||
steps: workflowRun.output.flow.steps,
|
steps: workflowRun.output.flow.steps,
|
||||||
});
|
});
|
||||||
if (stepDefinition?.type !== 'action') {
|
if (
|
||||||
throw new Error('The output tab must be rendered with an action step.');
|
!isDefined(stepDefinition?.definition) ||
|
||||||
|
!isDefined(stepDefinition.definition.name)
|
||||||
|
) {
|
||||||
|
throw new Error('The step is expected to be properly shaped.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerTitle = stepDefinition.definition.name;
|
const headerTitle = stepDefinition.definition.name;
|
||||||
const headerIcon = getActionIcon(stepDefinition.definition.type);
|
const headerIcon =
|
||||||
const headerIconColor = getActionIconColorOrThrow({
|
stepDefinition.type === 'trigger'
|
||||||
theme,
|
? getTriggerIcon(stepDefinition.definition)
|
||||||
actionType: stepDefinition.definition.type,
|
: getActionIcon(stepDefinition.definition.type);
|
||||||
});
|
const headerIconColor =
|
||||||
const headerType = getActionHeaderTypeOrThrow(stepDefinition.definition.type);
|
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';
|
const setRedHighlightingForEveryNode: GetJsonNodeHighlighting = () => 'red';
|
||||||
|
|
||||||
@ -58,12 +76,12 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
Icon={getIcon(headerIcon)}
|
Icon={getIcon(headerIcon)}
|
||||||
iconColor={headerIconColor}
|
iconColor={headerIconColor}
|
||||||
initialTitle={headerTitle}
|
initialTitle={headerTitle}
|
||||||
headerType={i18n._(headerType)}
|
headerType={headerType}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WorkflowRunStepJsonContainer>
|
<WorkflowRunStepJsonContainer>
|
||||||
<JsonTree
|
<JsonTree
|
||||||
value={stepOutput}
|
value={stepOutput ?? t`No output available`}
|
||||||
shouldExpandNodeInitially={isTwoFirstDepths}
|
shouldExpandNodeInitially={isTwoFirstDepths}
|
||||||
emptyArrayLabel={t`Empty Array`}
|
emptyArrayLabel={t`Empty Array`}
|
||||||
emptyObjectLabel={t`Empty Object`}
|
emptyObjectLabel={t`Empty Object`}
|
||||||
@ -71,7 +89,7 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
arrowButtonCollapsedLabel={t`Expand`}
|
arrowButtonCollapsedLabel={t`Expand`}
|
||||||
arrowButtonExpandedLabel={t`Collapse`}
|
arrowButtonExpandedLabel={t`Collapse`}
|
||||||
getNodeHighlighting={
|
getNodeHighlighting={
|
||||||
isDefined(stepOutput.error)
|
isDefined(stepOutput?.error)
|
||||||
? setRedHighlightingForEveryNode
|
? setRedHighlightingForEveryNode
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { WorkflowRunOutput } from '@/workflow/types/Workflow';
|
import { WorkflowRunOutput } from '@/workflow/types/Workflow';
|
||||||
import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
import { WorkflowDiagramRunStatus } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||||
|
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||||
import { isNull } from '@sniptt/guards';
|
import { isNull } from '@sniptt/guards';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -14,6 +15,10 @@ export const getWorkflowRunStepExecutionStatus = ({
|
|||||||
return 'not-executed';
|
return 'not-executed';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stepId === TRIGGER_STEP_ID) {
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
const stepOutput = workflowRunOutput.stepsOutput?.[stepId];
|
const stepOutput = workflowRunOutput.stepsOutput?.[stepId];
|
||||||
|
|
||||||
if (isDefined(stepOutput?.error)) {
|
if (isDefined(stepOutput?.error)) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS
|
|||||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
import { CRON_TRIGGER_INTERVAL_OPTIONS } from '@/workflow/workflow-trigger/constants/CronTriggerIntervalOptions';
|
import { CRON_TRIGGER_INTERVAL_OPTIONS } from '@/workflow/workflow-trigger/constants/CronTriggerIntervalOptions';
|
||||||
import { getCronTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getCronTriggerDefaultSettings';
|
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 { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
@ -48,18 +49,11 @@ export const WorkflowEditTriggerCronForm = ({
|
|||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
const headerIcon = getTriggerIcon({
|
const headerIcon = getTriggerIcon(trigger);
|
||||||
type: 'CRON',
|
|
||||||
});
|
|
||||||
|
|
||||||
const defaultLabel =
|
const defaultLabel = getTriggerDefaultLabel(trigger);
|
||||||
getTriggerDefaultLabel({
|
const headerTitle = trigger.name ?? defaultLabel;
|
||||||
type: 'CRON',
|
const headerType = getTriggerHeaderType(trigger);
|
||||||
}) ?? '';
|
|
||||||
|
|
||||||
const headerTitle = isDefined(trigger.name) ? trigger.name : defaultLabel;
|
|
||||||
|
|
||||||
const headerType = 'Trigger';
|
|
||||||
|
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
setErrorMessagesVisible(true);
|
setErrorMessagesVisible(true);
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow';
|
|||||||
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
||||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
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 { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
@ -105,18 +106,9 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
|
|||||||
[systemObjects, searchInputValue],
|
[systemObjects, searchInputValue],
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultLabel =
|
const defaultLabel = trigger.name ?? getTriggerDefaultLabel(trigger);
|
||||||
getTriggerDefaultLabel({
|
const headerIcon = getTriggerIcon(trigger);
|
||||||
type: 'DATABASE_EVENT',
|
const headerType = getTriggerHeaderType(trigger);
|
||||||
eventName: triggerEvent.event,
|
|
||||||
}) ?? '-';
|
|
||||||
|
|
||||||
const headerIcon = getTriggerIcon({
|
|
||||||
type: 'DATABASE_EVENT',
|
|
||||||
eventName: triggerEvent.event,
|
|
||||||
});
|
|
||||||
|
|
||||||
const headerType = `Trigger · ${defaultLabel}`;
|
|
||||||
|
|
||||||
const handleOptionClick = (value: string) => {
|
const handleOptionClick = (value: string) => {
|
||||||
if (triggerOptions.readonly === true) {
|
if (triggerOptions.readonly === true) {
|
||||||
|
|||||||
@ -8,11 +8,13 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS
|
|||||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions';
|
import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions';
|
||||||
import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings';
|
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 { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||||
|
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { SelectOption } from 'twenty-ui/input';
|
|
||||||
import { useIcons } from 'twenty-ui/display';
|
import { useIcons } from 'twenty-ui/display';
|
||||||
|
import { SelectOption } from 'twenty-ui/input';
|
||||||
|
|
||||||
type WorkflowEditTriggerManualFormProps = {
|
type WorkflowEditTriggerManualFormProps = {
|
||||||
trigger: WorkflowManualTrigger;
|
trigger: WorkflowManualTrigger;
|
||||||
@ -48,11 +50,10 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
? 'WHEN_RECORD_SELECTED'
|
? 'WHEN_RECORD_SELECTED'
|
||||||
: 'EVERYWHERE';
|
: 'EVERYWHERE';
|
||||||
|
|
||||||
const headerTitle = isDefined(trigger.name) ? trigger.name : 'Manual Trigger';
|
const headerTitle = trigger.name ?? getTriggerDefaultLabel(trigger);
|
||||||
|
|
||||||
const headerIcon = getTriggerIcon({
|
const headerIcon = getTriggerIcon(trigger);
|
||||||
type: 'MANUAL',
|
const headerType = getTriggerHeaderType(trigger);
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -70,7 +71,7 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
Icon={getIcon(headerIcon)}
|
Icon={getIcon(headerIcon)}
|
||||||
iconColor={theme.font.color.tertiary}
|
iconColor={theme.font.color.tertiary}
|
||||||
initialTitle={headerTitle}
|
initialTitle={headerTitle}
|
||||||
headerType="Trigger · Manual"
|
headerType={headerType}
|
||||||
disabled={triggerOptions.readonly}
|
disabled={triggerOptions.readonly}
|
||||||
/>
|
/>
|
||||||
<WorkflowStepBody>
|
<WorkflowStepBody>
|
||||||
|
|||||||
@ -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 { 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 { FormRawJsonFieldInput } from '@/object-record/record-field/form-types/components/FormRawJsonFieldInput';
|
||||||
import { useState } from 'react';
|
|
||||||
import { getFunctionOutputSchema } from '@/serverless-functions/utils/getFunctionOutputSchema';
|
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 = {
|
type WorkflowEditTriggerWebhookFormProps = {
|
||||||
trigger: WorkflowWebhookTrigger;
|
trigger: WorkflowWebhookTrigger;
|
||||||
@ -59,11 +61,10 @@ export const WorkflowEditTriggerWebhookForm = ({
|
|||||||
setErrorMessagesVisible(true);
|
setErrorMessagesVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerTitle = isDefined(trigger.name) ? trigger.name : 'Webhook';
|
const headerTitle = trigger.name ?? getTriggerDefaultLabel(trigger);
|
||||||
|
|
||||||
const headerIcon = getTriggerIcon({
|
const headerIcon = getTriggerIcon(trigger);
|
||||||
type: 'WEBHOOK',
|
const headerType = getTriggerHeaderType(trigger);
|
||||||
});
|
|
||||||
|
|
||||||
const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowId}`;
|
const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowId}`;
|
||||||
const displayWebhookUrl = webhookUrl.replace(/^(https?:\/\/)?(www\.)?/, '');
|
const displayWebhookUrl = webhookUrl.replace(/^(https?:\/\/)?(www\.)?/, '');
|
||||||
@ -98,7 +99,7 @@ export const WorkflowEditTriggerWebhookForm = ({
|
|||||||
Icon={getIcon(headerIcon)}
|
Icon={getIcon(headerIcon)}
|
||||||
iconColor={theme.font.color.tertiary}
|
iconColor={theme.font.color.tertiary}
|
||||||
initialTitle={headerTitle}
|
initialTitle={headerTitle}
|
||||||
headerType="Trigger · Webhook"
|
headerType={headerType}
|
||||||
disabled={triggerOptions.readonly}
|
disabled={triggerOptions.readonly}
|
||||||
/>
|
/>
|
||||||
<WorkflowStepBody>
|
<WorkflowStepBody>
|
||||||
|
|||||||
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -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 { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes';
|
||||||
import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes';
|
import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes';
|
||||||
|
|
||||||
export const getTriggerIcon = (
|
export const getTriggerIcon = (
|
||||||
trigger:
|
trigger: WorkflowTrigger,
|
||||||
| {
|
|
||||||
type: 'MANUAL';
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'CRON';
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'WEBHOOK';
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'DATABASE_EVENT';
|
|
||||||
eventName: string;
|
|
||||||
},
|
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
if (trigger.type === 'DATABASE_EVENT') {
|
if (trigger.type === 'DATABASE_EVENT') {
|
||||||
return DATABASE_TRIGGER_TYPES.find(
|
const eventName = splitWorkflowTriggerEventName(
|
||||||
(type) => type.event === trigger.eventName,
|
trigger.settings.eventName,
|
||||||
)?.icon;
|
).event;
|
||||||
|
|
||||||
|
return DATABASE_TRIGGER_TYPES.find((type) => type.event === eventName)
|
||||||
|
?.icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
return OTHER_TRIGGER_TYPES.find((item) => item.type === trigger.type)?.icon;
|
return OTHER_TRIGGER_TYPES.find((item) => item.type === trigger.type)?.icon;
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { Theme } from '@emotion/react';
|
||||||
|
|
||||||
|
export const getTriggerIconColor = ({ theme }: { theme: Theme }) => {
|
||||||
|
return theme.font.color.tertiary;
|
||||||
|
};
|
||||||
@ -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 { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes';
|
||||||
import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes';
|
import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const getTriggerDefaultLabel = (
|
export const getTriggerDefaultLabel = (trigger: WorkflowTrigger): string => {
|
||||||
trigger:
|
|
||||||
| {
|
|
||||||
type: 'MANUAL';
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'CRON';
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'WEBHOOK';
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'DATABASE_EVENT';
|
|
||||||
eventName: string;
|
|
||||||
},
|
|
||||||
): string | undefined => {
|
|
||||||
if (trigger.type === 'DATABASE_EVENT') {
|
if (trigger.type === 'DATABASE_EVENT') {
|
||||||
return DATABASE_TRIGGER_TYPES.find(
|
const triggerEvent = splitWorkflowTriggerEventName(
|
||||||
(type) => type.event === trigger.eventName,
|
trigger.settings.eventName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const label = DATABASE_TRIGGER_TYPES.find(
|
||||||
|
(type) => type.event === triggerEvent.event,
|
||||||
)?.defaultLabel;
|
)?.defaultLabel;
|
||||||
|
|
||||||
|
if (!isDefined(label)) {
|
||||||
|
throw new Error('Unknown trigger event');
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
return OTHER_TRIGGER_TYPES.find((item) => item.type === trigger.type)
|
const label = OTHER_TRIGGER_TYPES.find(
|
||||||
?.defaultLabel;
|
(item) => item.type === trigger.type,
|
||||||
|
)?.defaultLabel;
|
||||||
|
|
||||||
|
if (!isDefined(label)) {
|
||||||
|
throw new Error('Unknown trigger type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -28,11 +28,7 @@ export const getTriggerStepName = (trigger: WorkflowTrigger): string => {
|
|||||||
const getDatabaseEventTriggerStepName = (
|
const getDatabaseEventTriggerStepName = (
|
||||||
trigger: WorkflowDatabaseEventTrigger,
|
trigger: WorkflowDatabaseEventTrigger,
|
||||||
): string => {
|
): string => {
|
||||||
const [, action] = trigger.settings.eventName.split('.');
|
const defaultLabel = getTriggerDefaultLabel(trigger);
|
||||||
const defaultLabel = getTriggerDefaultLabel({
|
|
||||||
type: 'DATABASE_EVENT',
|
|
||||||
eventName: action,
|
|
||||||
});
|
|
||||||
|
|
||||||
return defaultLabel ?? '';
|
return defaultLabel ?? '';
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user