From fd149c3cbfc96c254bdabd890a900556d2ff5f5e Mon Sep 17 00:00:00 2001 From: Devessier Date: Fri, 6 Sep 2024 18:01:41 +0200 Subject: [PATCH] feat: display an empty trigger node when there is no trigger yet --- .../RightDrawerWorkflowEditStepContent.tsx | 9 +- .../WorkflowDiagramBaseStepNode.tsx | 19 ++-- .../components/WorkflowDiagramCanvas.tsx | 2 + .../WorkflowDiagramEmptyTrigger.tsx | 30 +++++++ .../components/WorkflowEditTriggerForm.tsx | 90 +++++++++++-------- .../workflow/utils/generateWorkflowDiagram.ts | 45 ++++++---- .../utils/getWorkflowVersionDiagram.ts | 12 +-- 7 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 packages/twenty-front/src/modules/workflow/components/WorkflowDiagramEmptyTrigger.tsx diff --git a/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx index a5669f9898782..5cbb7e9b05c5d 100644 --- a/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx +++ b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx @@ -23,7 +23,10 @@ const getStepDefinitionOrThrow = ({ if (stepId === TRIGGER_STEP_ID) { if (!isDefined(currentVersion.trigger)) { - throw new Error('Expected to find the definition of the trigger'); + return { + type: 'trigger', + definition: undefined, + } as const; } return { @@ -33,7 +36,9 @@ const getStepDefinitionOrThrow = ({ } if (!isDefined(currentVersion.steps)) { - throw new Error('Expected to find an array of steps'); + throw new Error( + 'Expected to find an array of steps while trying to locate a specific step', + ); } const selectedNodePosition = findStepPositionOrThrow({ diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx index b3bedb789b0ab..8c05d48baa09c 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx @@ -4,6 +4,8 @@ import { Handle, Position } from '@xyflow/react'; import React from 'react'; import { capitalize } from '~/utils/string/capitalize'; +type Variant = 'placeholder'; + const StyledStepNodeContainer = styled.div` display: flex; flex-direction: column; @@ -34,16 +36,19 @@ const StyledStepNodeType = styled.div` } `; -const StyledStepNodeInnerContainer = styled.div` +const StyledStepNodeInnerContainer = styled.div<{ variant?: Variant }>` background-color: ${({ theme }) => theme.background.secondary}; border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-style: ${({ variant }) => + variant === 'placeholder' ? 'dashed' : null}; border-radius: ${({ theme }) => theme.border.radius.md}; display: flex; gap: ${({ theme }) => theme.spacing(2)}; padding: ${({ theme }) => theme.spacing(2)}; position: relative; - box-shadow: ${({ theme }) => theme.boxShadow.superHeavy}; + box-shadow: ${({ variant, theme }) => + variant === 'placeholder' ? 'none' : theme.boxShadow.superHeavy}; .selectable.selected &, .selectable:focus &, @@ -53,12 +58,14 @@ const StyledStepNodeInnerContainer = styled.div` } `; -const StyledStepNodeLabel = styled.div` +const StyledStepNodeLabel = styled.div<{ variant?: Variant }>` align-items: center; display: flex; font-size: ${({ theme }) => theme.font.size.md}; font-weight: ${({ theme }) => theme.font.weight.medium}; column-gap: ${({ theme }) => theme.spacing(2)}; + color: ${({ variant, theme }) => + variant === 'placeholder' ? theme.font.color.extraLight : null}; `; const StyledSourceHandle = styled(Handle)` @@ -72,10 +79,12 @@ export const StyledTargetHandle = styled(Handle)` export const WorkflowDiagramBaseStepNode = ({ nodeType, label, + variant, Icon, }: { nodeType: WorkflowDiagramStepNodeData['nodeType']; label: string; + variant?: Variant; Icon?: React.ReactNode; }) => { return ( @@ -84,10 +93,10 @@ export const WorkflowDiagramBaseStepNode = ({ ) : null} - + {capitalize(nodeType)} - + {Icon} {label} diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvas.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvas.tsx index 6d65014a8995d..ef1123f4eed61 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvas.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvas.tsx @@ -1,5 +1,6 @@ import { WorkflowDiagramCanvasEffect } from '@/workflow/components/WorkflowDiagramCanvasEffect'; import { WorkflowDiagramCreateStepNode } from '@/workflow/components/WorkflowDiagramCreateStepNode'; +import { WorkflowDiagramEmptyTrigger } from '@/workflow/components/WorkflowDiagramEmptyTrigger'; import { WorkflowDiagramStepNode } from '@/workflow/components/WorkflowDiagramStepNode'; import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; import { @@ -72,6 +73,7 @@ export const WorkflowDiagramCanvas = ({ nodeTypes={{ default: WorkflowDiagramStepNode, 'create-step': WorkflowDiagramCreateStepNode, + 'empty-trigger': WorkflowDiagramEmptyTrigger, }} fitView nodes={nodes.map((node) => ({ ...node, draggable: false }))} diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramEmptyTrigger.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramEmptyTrigger.tsx new file mode 100644 index 0000000000000..a355733219ab8 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramEmptyTrigger.tsx @@ -0,0 +1,30 @@ +import { WorkflowDiagramBaseStepNode } from '@/workflow/components/WorkflowDiagramBaseStepNode'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { IconPlaylistAdd } from 'twenty-ui'; + +const StyledStepNodeLabelIconContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.transparent.light}; + border-radius: ${({ theme }) => theme.spacing(1)}; + display: flex; + justify-content: center; + padding: ${({ theme }) => theme.spacing(1)}; +`; + +export const WorkflowDiagramEmptyTrigger = () => { + const theme = useTheme(); + + return ( + + + + } + /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx index e345d3e65c423..c074fb84dc2d3 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx @@ -49,37 +49,33 @@ export const WorkflowEditTriggerForm = ({ trigger, onUpdateTrigger, }: { - trigger: WorkflowTrigger; + trigger: WorkflowTrigger | undefined; onUpdateTrigger: (trigger: WorkflowTrigger) => void; }) => { const theme = useTheme(); const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); - const triggerEvent = splitWorkflowTriggerEventName( - trigger.settings.eventName, - ); + const triggerEvent = isDefined(trigger) + ? splitWorkflowTriggerEventName(trigger.settings.eventName) + : undefined; const availableMetadata: Array> = activeObjectMetadataItems.map((item) => ({ label: item.labelPlural, value: item.nameSingular, })); - const recordTypeMetadata = activeObjectMetadataItems.find( - (item) => item.nameSingular === triggerEvent.objectType, - ); - if (!isDefined(recordTypeMetadata)) { - throw new Error( - 'Expected to find the metadata configuration for the currently selected record type of the trigger.', - ); - } + const recordTypeMetadata = isDefined(triggerEvent) + ? activeObjectMetadataItems.find( + (item) => item.nameSingular === triggerEvent.objectType, + ) + : undefined; - const selectedEvent = OBJECT_EVENT_TRIGGERS.find( - (availableEvent) => availableEvent.value === triggerEvent.event, - ); - if (!isDefined(selectedEvent)) { - throw new Error('Expected to find the currently selected event type.'); - } + const selectedEvent = isDefined(triggerEvent) + ? OBJECT_EVENT_TRIGGERS.find( + (availableEvent) => availableEvent.value === triggerEvent.event, + ) + : undefined; return ( <> @@ -89,11 +85,15 @@ export const WorkflowEditTriggerForm = ({ - When a {recordTypeMetadata.labelSingular} is {selectedEvent.label} + {isDefined(recordTypeMetadata) && isDefined(selectedEvent) + ? `When a ${recordTypeMetadata.labelSingular} is ${selectedEvent.label}` + : '-'} - Trigger . Record is {selectedEvent.label} + {isDefined(selectedEvent) + ? `Trigger . Record is ${selectedEvent.label}` + : '-'} @@ -102,32 +102,50 @@ export const WorkflowEditTriggerForm = ({ dropdownId="workflow-edit-trigger-record-type" label="Record Type" fullWidth - value={triggerEvent.objectType} + value={triggerEvent?.objectType} options={availableMetadata} onChange={(updatedRecordType) => { - onUpdateTrigger({ - ...trigger, - settings: { - ...trigger.settings, - eventName: `${updatedRecordType}.${triggerEvent.event}`, - }, - }); + onUpdateTrigger( + isDefined(trigger) && isDefined(triggerEvent) + ? { + ...trigger, + settings: { + ...trigger.settings, + eventName: `${updatedRecordType}.${triggerEvent.event}`, + }, + } + : { + type: 'DATABASE_EVENT', + settings: { + eventName: `${updatedRecordType}.${OBJECT_EVENT_TRIGGERS[0].value}`, + }, + }, + ); }} />