Skip to content

Commit

Permalink
feat: display an empty trigger node when there is no trigger yet
Browse files Browse the repository at this point in the history
  • Loading branch information
Devessier committed Sep 9, 2024
1 parent 88964ad commit fd149c3
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 &,
Expand All @@ -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)`
Expand All @@ -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 (
Expand All @@ -84,10 +93,10 @@ export const WorkflowDiagramBaseStepNode = ({
<StyledTargetHandle type="target" position={Position.Top} />
) : null}

<StyledStepNodeInnerContainer>
<StyledStepNodeInnerContainer variant={variant}>
<StyledStepNodeType>{capitalize(nodeType)}</StyledStepNodeType>

<StyledStepNodeLabel>
<StyledStepNodeLabel variant={variant}>
{Icon}

{label}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -72,6 +73,7 @@ export const WorkflowDiagramCanvas = ({
nodeTypes={{
default: WorkflowDiagramStepNode,
'create-step': WorkflowDiagramCreateStepNode,
'empty-trigger': WorkflowDiagramEmptyTrigger,
}}
fitView
nodes={nodes.map((node) => ({ ...node, draggable: false }))}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<WorkflowDiagramBaseStepNode
label="Add a Trigger"
nodeType="trigger"
variant="placeholder"
Icon={
<StyledStepNodeLabelIconContainer>
<IconPlaylistAdd size={16} color={theme.font.color.tertiary} />
</StyledStepNodeLabelIconContainer>
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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<SelectOption<string>> =
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 (
<>
Expand All @@ -89,11 +85,15 @@ export const WorkflowEditTriggerForm = ({
</StyledTriggerHeaderIconContainer>

<StyledTriggerHeaderTitle>
When a {recordTypeMetadata.labelSingular} is {selectedEvent.label}
{isDefined(recordTypeMetadata) && isDefined(selectedEvent)
? `When a ${recordTypeMetadata.labelSingular} is ${selectedEvent.label}`
: '-'}
</StyledTriggerHeaderTitle>

<StyledTriggerHeaderType>
Trigger . Record is {selectedEvent.label}
{isDefined(selectedEvent)
? `Trigger . Record is ${selectedEvent.label}`
: '-'}
</StyledTriggerHeaderType>
</StyledTriggerHeader>

Expand All @@ -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}`,
},
},
);
}}
/>
<Select
dropdownId="workflow-edit-trigger-event-type"
label="Event type"
fullWidth
value={triggerEvent.event}
value={triggerEvent?.event}
options={OBJECT_EVENT_TRIGGERS}
onChange={(updatedEvent) => {
onUpdateTrigger({
...trigger,
settings: {
...trigger.settings,
eventName: `${triggerEvent.objectType}.${updatedEvent}`,
},
});
onUpdateTrigger(
isDefined(trigger) && isDefined(triggerEvent)
? {
...trigger,
settings: {
...trigger.settings,
eventName: `${triggerEvent.objectType}.${updatedEvent}`,
},
}
: {
type: 'DATABASE_EVENT',
settings: {
eventName: `${availableMetadata[0].value}.${updatedEvent}`,
},
},
);
}}
/>
</StyledTriggerSettings>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import {
} from '@/workflow/types/WorkflowDiagram';
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
import { MarkerType } from '@xyflow/react';
import { isDefined } from 'twenty-ui';
import { v4 } from 'uuid';
import { capitalize } from '~/utils/string/capitalize';

export const generateWorkflowDiagram = ({
trigger,
steps,
}: {
trigger: WorkflowTrigger;
trigger: WorkflowTrigger | undefined;
steps: Array<WorkflowStep>;
}): WorkflowDiagram => {
const nodes: Array<WorkflowDiagramNode> = [];
Expand Down Expand Up @@ -62,20 +63,34 @@ export const generateWorkflowDiagram = ({

// Start with the trigger node
const triggerNodeId = TRIGGER_STEP_ID;
const triggerEvent = splitWorkflowTriggerEventName(
trigger.settings.eventName,
);
nodes.push({
id: triggerNodeId,
data: {
nodeType: 'trigger',
label: `${capitalize(triggerEvent.objectType)} is ${capitalize(triggerEvent.event)}`,
},
position: {
x: 0,
y: 0,
},
});

if (isDefined(trigger)) {
const triggerEvent = splitWorkflowTriggerEventName(
trigger.settings.eventName,
);

nodes.push({
id: triggerNodeId,
data: {
nodeType: 'trigger',
label: `${capitalize(triggerEvent.objectType)} is ${capitalize(triggerEvent.event)}`,
},
position: {
x: 0,
y: 0,
},
});
} else {
nodes.push({
id: triggerNodeId,
type: 'empty-trigger',
data: {} as any,
position: {
x: 0,
y: 0,
},
});
}

let lastStepId = triggerNodeId;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,12 @@ const EMPTY_DIAGRAM: WorkflowDiagram = {
export const getWorkflowVersionDiagram = (
workflowVersion: WorkflowVersion | undefined,
): WorkflowDiagram => {
if (
!(
isDefined(workflowVersion) &&
isDefined(workflowVersion.trigger) &&
isDefined(workflowVersion.steps)
)
) {
if (!isDefined(workflowVersion)) {
return EMPTY_DIAGRAM;
}

return generateWorkflowDiagram({
trigger: workflowVersion.trigger,
steps: workflowVersion.steps,
trigger: workflowVersion.trigger ?? undefined,
steps: workflowVersion.steps ?? [],
});
};

0 comments on commit fd149c3

Please sign in to comment.