Make workflow step name editable (#8677)
- Use TextInput in header title - add onTitleChange prop - rename field name instead of label To fix : - padding right on title comes from current TextInput component. It needs to be refactored https://github.com/user-attachments/assets/535cd6d3-866b-4a61-9c5d-cdbe7710396a
This commit is contained in:
@ -19,10 +19,19 @@ type TextInputProps = {
|
|||||||
onEscape: (newText: string) => void;
|
onEscape: (newText: string) => void;
|
||||||
onTab?: (newText: string) => void;
|
onTab?: (newText: string) => void;
|
||||||
onShiftTab?: (newText: string) => void;
|
onShiftTab?: (newText: string) => void;
|
||||||
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
|
onClickOutside?: (event: MouseEvent | TouchEvent, inputValue: string) => void;
|
||||||
hotkeyScope: string;
|
hotkeyScope: string;
|
||||||
onChange?: (newText: string) => void;
|
onChange?: (newText: string) => void;
|
||||||
copyButton?: boolean;
|
copyButton?: boolean;
|
||||||
|
shouldTrim?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getValue = (value: string, shouldTrim: boolean) => {
|
||||||
|
if (shouldTrim) {
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TextInput = ({
|
export const TextInput = ({
|
||||||
@ -37,6 +46,7 @@ export const TextInput = ({
|
|||||||
onClickOutside,
|
onClickOutside,
|
||||||
onChange,
|
onChange,
|
||||||
copyButton = true,
|
copyButton = true,
|
||||||
|
shouldTrim = true,
|
||||||
}: TextInputProps) => {
|
}: TextInputProps) => {
|
||||||
const [internalText, setInternalText] = useState(value);
|
const [internalText, setInternalText] = useState(value);
|
||||||
|
|
||||||
@ -44,12 +54,12 @@ export const TextInput = ({
|
|||||||
const copyRef = useRef<HTMLDivElement>(null);
|
const copyRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setInternalText(event.target.value.trim());
|
setInternalText(getValue(event.target.value, shouldTrim));
|
||||||
onChange?.(event.target.value.trim());
|
onChange?.(getValue(event.target.value, shouldTrim));
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInternalText(value.trim());
|
setInternalText(getValue(value, shouldTrim));
|
||||||
}, [value]);
|
}, [value, shouldTrim]);
|
||||||
|
|
||||||
useRegisterInputEvents({
|
useRegisterInputEvents({
|
||||||
inputRef: wrapperRef,
|
inputRef: wrapperRef,
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key={action.type}
|
key={action.type}
|
||||||
LeftIcon={action.icon}
|
LeftIcon={action.icon}
|
||||||
text={action.label}
|
text={action.name}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await updateTrigger(
|
await updateTrigger(
|
||||||
getTriggerDefaultDefinition({
|
getTriggerDefaultDefinition({
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { WorkflowDiagramStepNodeData } from '@/workflow/types/WorkflowDiagram';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Handle, Position } from '@xyflow/react';
|
import { Handle, Position } from '@xyflow/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined, OverflowingTextWithTooltip } from 'twenty-ui';
|
||||||
import { capitalize } from '~/utils/string/capitalize';
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
type Variant = 'placeholder';
|
type Variant = 'placeholder';
|
||||||
@ -68,6 +68,7 @@ const StyledStepNodeLabel = styled.div<{ variant?: Variant }>`
|
|||||||
variant === 'placeholder'
|
variant === 'placeholder'
|
||||||
? theme.font.color.extraLight
|
? theme.font.color.extraLight
|
||||||
: theme.font.color.primary};
|
: theme.font.color.primary};
|
||||||
|
max-width: 200px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledSourceHandle = styled(Handle)`
|
const StyledSourceHandle = styled(Handle)`
|
||||||
@ -90,13 +91,13 @@ const StyledRightFloatingElementContainer = styled.div`
|
|||||||
|
|
||||||
export const WorkflowDiagramBaseStepNode = ({
|
export const WorkflowDiagramBaseStepNode = ({
|
||||||
nodeType,
|
nodeType,
|
||||||
label,
|
name,
|
||||||
variant,
|
variant,
|
||||||
Icon,
|
Icon,
|
||||||
RightFloatingElement,
|
RightFloatingElement,
|
||||||
}: {
|
}: {
|
||||||
nodeType: WorkflowDiagramStepNodeData['nodeType'];
|
nodeType: WorkflowDiagramStepNodeData['nodeType'];
|
||||||
label: string;
|
name: string;
|
||||||
variant?: Variant;
|
variant?: Variant;
|
||||||
Icon?: React.ReactNode;
|
Icon?: React.ReactNode;
|
||||||
RightFloatingElement?: React.ReactNode;
|
RightFloatingElement?: React.ReactNode;
|
||||||
@ -113,7 +114,7 @@ export const WorkflowDiagramBaseStepNode = ({
|
|||||||
<StyledStepNodeLabel variant={variant}>
|
<StyledStepNodeLabel variant={variant}>
|
||||||
{Icon}
|
{Icon}
|
||||||
|
|
||||||
{label}
|
<OverflowingTextWithTooltip text={name} />
|
||||||
</StyledStepNodeLabel>
|
</StyledStepNodeLabel>
|
||||||
|
|
||||||
{isDefined(RightFloatingElement) ? (
|
{isDefined(RightFloatingElement) ? (
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export const WorkflowDiagramEmptyTrigger = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowDiagramBaseStepNode
|
<WorkflowDiagramBaseStepNode
|
||||||
label="Add a Trigger"
|
name="Add a Trigger"
|
||||||
nodeType="trigger"
|
nodeType="trigger"
|
||||||
variant="placeholder"
|
variant="placeholder"
|
||||||
Icon={
|
Icon={
|
||||||
|
|||||||
@ -100,8 +100,8 @@ export const WorkflowDiagramStepNodeBase = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowDiagramBaseStepNode
|
<WorkflowDiagramBaseStepNode
|
||||||
|
name={data.name}
|
||||||
nodeType={data.nodeType}
|
nodeType={data.nodeType}
|
||||||
label={data.label}
|
|
||||||
Icon={renderStepIcon()}
|
Icon={renderStepIcon()}
|
||||||
RightFloatingElement={RightFloatingElement}
|
RightFloatingElement={RightFloatingElement}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -119,15 +119,27 @@ export const WorkflowEditActionFormRecordCreate = ({
|
|||||||
};
|
};
|
||||||
}, [saveAction]);
|
}, [saveAction]);
|
||||||
|
|
||||||
|
const headerTitle = isDefined(action.name) ? action.name : `Create Record`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowEditGenericFormBase
|
<WorkflowEditGenericFormBase
|
||||||
|
onTitleChange={(newName: string) => {
|
||||||
|
if (actionOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionOptions.onActionUpdate({
|
||||||
|
...action,
|
||||||
|
name: newName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
HeaderIcon={
|
HeaderIcon={
|
||||||
<IconAddressBook
|
<IconAddressBook
|
||||||
color={theme.font.color.tertiary}
|
color={theme.font.color.tertiary}
|
||||||
stroke={theme.icon.stroke.sm}
|
stroke={theme.icon.stroke.sm}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
headerTitle="Record Create"
|
headerTitle={headerTitle}
|
||||||
headerType="Action"
|
headerType="Action"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -166,11 +166,23 @@ export const WorkflowEditActionFormSendEmail = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const headerTitle = isDefined(action.name) ? action.name : 'Send Email';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!loading && (
|
!loading && (
|
||||||
<WorkflowEditGenericFormBase
|
<WorkflowEditGenericFormBase
|
||||||
|
onTitleChange={(newName: string) => {
|
||||||
|
if (actionOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionOptions.onActionUpdate({
|
||||||
|
...action,
|
||||||
|
name: newName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
HeaderIcon={<IconMail color={theme.color.blue} />}
|
HeaderIcon={<IconMail color={theme.color.blue} />}
|
||||||
headerTitle="Send Email"
|
headerTitle={headerTitle}
|
||||||
headerType="Email"
|
headerType="Email"
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
|
|||||||
@ -217,10 +217,24 @@ export const WorkflowEditActionFormServerlessFunctionInner = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const headerTitle = isDefined(action.name)
|
||||||
|
? action.name
|
||||||
|
: 'Code - Serverless Function';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowEditGenericFormBase
|
<WorkflowEditGenericFormBase
|
||||||
|
onTitleChange={(newName: string) => {
|
||||||
|
if (actionOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionOptions?.onActionUpdate({
|
||||||
|
...action,
|
||||||
|
name: newName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
HeaderIcon={<IconCode color={theme.color.orange} />}
|
HeaderIcon={<IconCode color={theme.color.orange} />}
|
||||||
headerTitle="Code - Serverless Function"
|
headerTitle={headerTitle}
|
||||||
headerType="Code"
|
headerType="Code"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
import { TextInput } from '@/ui/field/input/components/TextInput';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
const StyledHeader = styled.div`
|
const StyledHeader = styled.div`
|
||||||
background-color: ${({ theme }) => theme.background.secondary};
|
background-color: ${({ theme }) => theme.background.secondary};
|
||||||
@ -40,22 +42,36 @@ const StyledContentContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const WorkflowEditGenericFormBase = ({
|
export const WorkflowEditGenericFormBase = ({
|
||||||
|
onTitleChange,
|
||||||
HeaderIcon,
|
HeaderIcon,
|
||||||
headerTitle,
|
headerTitle,
|
||||||
headerType,
|
headerType,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
|
onTitleChange: (newTitle: string) => void;
|
||||||
HeaderIcon: React.ReactNode;
|
HeaderIcon: React.ReactNode;
|
||||||
headerTitle: string;
|
headerTitle: string;
|
||||||
headerType: string;
|
headerType: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
|
const debouncedOnTitleChange = useDebouncedCallback(onTitleChange, 100);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledHeader>
|
<StyledHeader>
|
||||||
<StyledHeaderIconContainer>{HeaderIcon}</StyledHeaderIconContainer>
|
<StyledHeaderIconContainer>{HeaderIcon}</StyledHeaderIconContainer>
|
||||||
|
|
||||||
<StyledHeaderTitle>{headerTitle}</StyledHeaderTitle>
|
<StyledHeaderTitle>
|
||||||
|
<TextInput
|
||||||
|
value={headerTitle}
|
||||||
|
copyButton={false}
|
||||||
|
hotkeyScope="workflow-step-title"
|
||||||
|
onEnter={onTitleChange}
|
||||||
|
onEscape={onTitleChange}
|
||||||
|
onChange={debouncedOnTitleChange}
|
||||||
|
shouldTrim={false}
|
||||||
|
/>
|
||||||
|
</StyledHeaderTitle>
|
||||||
|
|
||||||
<StyledHeaderType>{headerType}</StyledHeaderType>
|
<StyledHeaderType>{headerType}</StyledHeaderType>
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
|
|||||||
@ -1,50 +1,12 @@
|
|||||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||||
|
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||||
import { OBJECT_EVENT_TRIGGERS } from '@/workflow/constants/ObjectEventTriggers';
|
import { OBJECT_EVENT_TRIGGERS } from '@/workflow/constants/ObjectEventTriggers';
|
||||||
import { WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow';
|
import { WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow';
|
||||||
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { IconPlaylistAdd, isDefined } from 'twenty-ui';
|
import { IconPlaylistAdd, isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledTriggerHeader = styled.div`
|
|
||||||
background-color: ${({ theme }) => theme.background.secondary};
|
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: ${({ theme }) => theme.spacing(6)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTriggerHeaderTitle = styled.p`
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
|
||||||
font-size: ${({ theme }) => theme.font.size.xl};
|
|
||||||
|
|
||||||
margin: ${({ theme }) => theme.spacing(3)} 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTriggerHeaderType = styled.p`
|
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
margin: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTriggerHeaderIconContainer = styled.div`
|
|
||||||
align-self: flex-start;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: ${({ theme }) => theme.background.transparent.light};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
|
||||||
padding: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTriggerSettings = styled.div`
|
|
||||||
padding: ${({ theme }) => theme.spacing(6)};
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
row-gap: ${({ theme }) => theme.spacing(4)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type WorkflowEditTriggerDatabaseEventFormProps = {
|
type WorkflowEditTriggerDatabaseEventFormProps = {
|
||||||
trigger: WorkflowDatabaseEventTrigger;
|
trigger: WorkflowDatabaseEventTrigger;
|
||||||
triggerOptions:
|
triggerOptions:
|
||||||
@ -87,92 +49,98 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
|
|||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const headerTitle = isDefined(trigger.name)
|
||||||
|
? trigger.name
|
||||||
|
: isDefined(recordTypeMetadata) && isDefined(selectedEvent)
|
||||||
|
? `When a ${recordTypeMetadata.labelSingular} is ${selectedEvent.label}`
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
const headerType = isDefined(selectedEvent)
|
||||||
|
? `Trigger · Record is ${selectedEvent.label}`
|
||||||
|
: '-';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<WorkflowEditGenericFormBase
|
||||||
<StyledTriggerHeader>
|
onTitleChange={(newName: string) => {
|
||||||
<StyledTriggerHeaderIconContainer>
|
if (triggerOptions.readonly === true) {
|
||||||
<IconPlaylistAdd color={theme.font.color.tertiary} />
|
return;
|
||||||
</StyledTriggerHeaderIconContainer>
|
}
|
||||||
|
|
||||||
<StyledTriggerHeaderTitle>
|
triggerOptions.onTriggerUpdate({
|
||||||
{isDefined(recordTypeMetadata) && isDefined(selectedEvent)
|
...trigger,
|
||||||
? `When a ${recordTypeMetadata.labelSingular} is ${selectedEvent.label}`
|
name: newName,
|
||||||
: '-'}
|
});
|
||||||
</StyledTriggerHeaderTitle>
|
}}
|
||||||
|
HeaderIcon={<IconPlaylistAdd color={theme.font.color.tertiary} />}
|
||||||
|
headerTitle={headerTitle}
|
||||||
|
headerType={headerType}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
dropdownId="workflow-edit-trigger-record-type"
|
||||||
|
label="Record Type"
|
||||||
|
fullWidth
|
||||||
|
disabled={triggerOptions.readonly}
|
||||||
|
value={triggerEvent?.objectType}
|
||||||
|
emptyOption={{ label: 'Select an option', value: '' }}
|
||||||
|
options={availableMetadata}
|
||||||
|
onChange={(updatedRecordType) => {
|
||||||
|
if (triggerOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
<StyledTriggerHeaderType>
|
triggerOptions.onTriggerUpdate(
|
||||||
{isDefined(selectedEvent)
|
isDefined(trigger) && isDefined(triggerEvent)
|
||||||
? `Trigger · Record is ${selectedEvent.label}`
|
? {
|
||||||
: '-'}
|
...trigger,
|
||||||
</StyledTriggerHeaderType>
|
settings: {
|
||||||
</StyledTriggerHeader>
|
...trigger.settings,
|
||||||
|
eventName: `${updatedRecordType}.${triggerEvent.event}`,
|
||||||
<StyledTriggerSettings>
|
|
||||||
<Select
|
|
||||||
dropdownId="workflow-edit-trigger-record-type"
|
|
||||||
label="Record Type"
|
|
||||||
fullWidth
|
|
||||||
disabled={triggerOptions.readonly}
|
|
||||||
value={triggerEvent?.objectType}
|
|
||||||
emptyOption={{ label: 'Select an option', value: '' }}
|
|
||||||
options={availableMetadata}
|
|
||||||
onChange={(updatedRecordType) => {
|
|
||||||
if (triggerOptions.readonly === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerOptions.onTriggerUpdate(
|
|
||||||
isDefined(trigger) && isDefined(triggerEvent)
|
|
||||||
? {
|
|
||||||
...trigger,
|
|
||||||
settings: {
|
|
||||||
...trigger.settings,
|
|
||||||
eventName: `${updatedRecordType}.${triggerEvent.event}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: 'DATABASE_EVENT',
|
|
||||||
settings: {
|
|
||||||
eventName: `${updatedRecordType}.${OBJECT_EVENT_TRIGGERS[0].value}`,
|
|
||||||
outputSchema: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
}
|
||||||
}}
|
: {
|
||||||
/>
|
name: headerTitle,
|
||||||
<Select
|
type: 'DATABASE_EVENT',
|
||||||
dropdownId="workflow-edit-trigger-event-type"
|
settings: {
|
||||||
label="Event type"
|
eventName: `${updatedRecordType}.${OBJECT_EVENT_TRIGGERS[0].value}`,
|
||||||
fullWidth
|
outputSchema: {},
|
||||||
value={triggerEvent?.event}
|
|
||||||
emptyOption={{ label: 'Select an option', value: '' }}
|
|
||||||
options={OBJECT_EVENT_TRIGGERS}
|
|
||||||
disabled={triggerOptions.readonly}
|
|
||||||
onChange={(updatedEvent) => {
|
|
||||||
if (triggerOptions.readonly === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerOptions.onTriggerUpdate(
|
|
||||||
isDefined(trigger) && isDefined(triggerEvent)
|
|
||||||
? {
|
|
||||||
...trigger,
|
|
||||||
settings: {
|
|
||||||
...trigger.settings,
|
|
||||||
eventName: `${triggerEvent.objectType}.${updatedEvent}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: 'DATABASE_EVENT',
|
|
||||||
settings: {
|
|
||||||
eventName: `${availableMetadata[0].value}.${updatedEvent}`,
|
|
||||||
outputSchema: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
},
|
||||||
}}
|
);
|
||||||
/>
|
}}
|
||||||
</StyledTriggerSettings>
|
/>
|
||||||
</>
|
<Select
|
||||||
|
dropdownId="workflow-edit-trigger-event-type"
|
||||||
|
label="Event type"
|
||||||
|
fullWidth
|
||||||
|
value={triggerEvent?.event}
|
||||||
|
emptyOption={{ label: 'Select an option', value: '' }}
|
||||||
|
options={OBJECT_EVENT_TRIGGERS}
|
||||||
|
disabled={triggerOptions.readonly}
|
||||||
|
onChange={(updatedEvent) => {
|
||||||
|
if (triggerOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerOptions.onTriggerUpdate(
|
||||||
|
isDefined(trigger) && isDefined(triggerEvent)
|
||||||
|
? {
|
||||||
|
...trigger,
|
||||||
|
settings: {
|
||||||
|
...trigger.settings,
|
||||||
|
eventName: `${triggerEvent.objectType}.${updatedEvent}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
name: headerTitle,
|
||||||
|
type: 'DATABASE_EVENT',
|
||||||
|
settings: {
|
||||||
|
eventName: `${availableMetadata[0].value}.${updatedEvent}`,
|
||||||
|
outputSchema: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</WorkflowEditGenericFormBase>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -44,10 +44,22 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
? 'WHEN_RECORD_SELECTED'
|
? 'WHEN_RECORD_SELECTED'
|
||||||
: 'EVERYWHERE';
|
: 'EVERYWHERE';
|
||||||
|
|
||||||
|
const headerTitle = isDefined(trigger.name) ? trigger.name : 'Manual Trigger';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowEditGenericFormBase
|
<WorkflowEditGenericFormBase
|
||||||
|
onTitleChange={(newName: string) => {
|
||||||
|
if (triggerOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerOptions.onTriggerUpdate({
|
||||||
|
...trigger,
|
||||||
|
name: newName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
HeaderIcon={<IconHandMove color={theme.font.color.tertiary} />}
|
HeaderIcon={<IconHandMove color={theme.font.color.tertiary} />}
|
||||||
headerTitle="Manual Trigger"
|
headerTitle={headerTitle}
|
||||||
headerType="Trigger · Manual"
|
headerType="Trigger · Manual"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -2,17 +2,17 @@ import { WorkflowTriggerType } from '@/workflow/types/Workflow';
|
|||||||
import { IconComponent, IconSettingsAutomation } from 'twenty-ui';
|
import { IconComponent, IconSettingsAutomation } from 'twenty-ui';
|
||||||
|
|
||||||
export const TRIGGER_TYPES: Array<{
|
export const TRIGGER_TYPES: Array<{
|
||||||
label: string;
|
name: string;
|
||||||
type: WorkflowTriggerType;
|
type: WorkflowTriggerType;
|
||||||
icon: IconComponent;
|
icon: IconComponent;
|
||||||
}> = [
|
}> = [
|
||||||
{
|
{
|
||||||
label: 'Database Event',
|
name: 'Database Event',
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
icon: IconSettingsAutomation,
|
icon: IconSettingsAutomation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Manual',
|
name: 'Manual Trigger',
|
||||||
type: 'MANUAL',
|
type: 'MANUAL',
|
||||||
icon: IconSettingsAutomation,
|
icon: IconSettingsAutomation,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -49,7 +49,9 @@ export const useAvailableVariablesInWorkflowStep = (): StepOutputSchema[] => {
|
|||||||
) {
|
) {
|
||||||
result.push({
|
result.push({
|
||||||
id: 'trigger',
|
id: 'trigger',
|
||||||
name: getTriggerStepName(workflow.currentVersion.trigger),
|
name: isDefined(workflow.currentVersion.trigger.name)
|
||||||
|
? workflow.currentVersion.trigger.name
|
||||||
|
: getTriggerStepName(workflow.currentVersion.trigger),
|
||||||
outputSchema: workflow.currentVersion.trigger.settings.outputSchema,
|
outputSchema: workflow.currentVersion.trigger.settings.outputSchema,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,8 +109,8 @@ export type WorkflowActionType =
|
|||||||
export type WorkflowStepType = WorkflowActionType;
|
export type WorkflowStepType = WorkflowActionType;
|
||||||
|
|
||||||
type BaseTrigger = {
|
type BaseTrigger = {
|
||||||
|
name?: string;
|
||||||
type: string;
|
type: string;
|
||||||
input?: object;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowDatabaseEventTrigger = BaseTrigger & {
|
export type WorkflowDatabaseEventTrigger = BaseTrigger & {
|
||||||
|
|||||||
@ -16,12 +16,12 @@ export type WorkflowDiagramStepNodeData =
|
|||||||
| {
|
| {
|
||||||
nodeType: 'trigger';
|
nodeType: 'trigger';
|
||||||
triggerType: WorkflowTriggerType;
|
triggerType: WorkflowTriggerType;
|
||||||
label: string;
|
name: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
nodeType: 'action';
|
nodeType: 'action';
|
||||||
actionType: WorkflowActionType;
|
actionType: WorkflowActionType;
|
||||||
label: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowDiagramCreateStepNodeData = {
|
export type WorkflowDiagramCreateStepNodeData = {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { addCreateStepNodes } from '../addCreateStepNodes';
|
|||||||
describe('addCreateStepNodes', () => {
|
describe('addCreateStepNodes', () => {
|
||||||
it("adds a create step node to the end of a single-branch flow and doesn't change the shape of other nodes", () => {
|
it("adds a create step node to the end of a single-branch flow and doesn't change the shape of other nodes", () => {
|
||||||
const trigger: WorkflowTrigger = {
|
const trigger: WorkflowTrigger = {
|
||||||
|
name: 'Company created',
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
settings: {
|
settings: {
|
||||||
eventName: 'company.created',
|
eventName: 'company.created',
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { generateWorkflowDiagram } from '../generateWorkflowDiagram';
|
|||||||
describe('generateWorkflowDiagram', () => {
|
describe('generateWorkflowDiagram', () => {
|
||||||
it('should generate a single trigger node when no step is provided', () => {
|
it('should generate a single trigger node when no step is provided', () => {
|
||||||
const trigger: WorkflowTrigger = {
|
const trigger: WorkflowTrigger = {
|
||||||
|
name: 'Company created',
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
settings: {
|
settings: {
|
||||||
eventName: 'company.created',
|
eventName: 'company.created',
|
||||||
@ -19,7 +20,6 @@ describe('generateWorkflowDiagram', () => {
|
|||||||
|
|
||||||
expect(result.nodes[0]).toMatchObject({
|
expect(result.nodes[0]).toMatchObject({
|
||||||
data: {
|
data: {
|
||||||
label: 'Company is Created',
|
|
||||||
nodeType: 'trigger',
|
nodeType: 'trigger',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -27,6 +27,7 @@ describe('generateWorkflowDiagram', () => {
|
|||||||
|
|
||||||
it('should generate a diagram with nodes and edges corresponding to the steps', () => {
|
it('should generate a diagram with nodes and edges corresponding to the steps', () => {
|
||||||
const trigger: WorkflowTrigger = {
|
const trigger: WorkflowTrigger = {
|
||||||
|
name: 'Company created',
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
settings: {
|
settings: {
|
||||||
eventName: 'company.created',
|
eventName: 'company.created',
|
||||||
@ -85,13 +86,14 @@ describe('generateWorkflowDiagram', () => {
|
|||||||
expect(stepNodes[index].data).toEqual({
|
expect(stepNodes[index].data).toEqual({
|
||||||
nodeType: 'action',
|
nodeType: 'action',
|
||||||
actionType: 'CODE',
|
actionType: 'CODE',
|
||||||
label: step.name,
|
name: step.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly link nodes with edges', () => {
|
it('should correctly link nodes with edges', () => {
|
||||||
const trigger: WorkflowTrigger = {
|
const trigger: WorkflowTrigger = {
|
||||||
|
name: 'Company created',
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
settings: {
|
settings: {
|
||||||
eventName: 'company.created',
|
eventName: 'company.created',
|
||||||
|
|||||||
@ -42,6 +42,7 @@ describe('getWorkflowVersionDiagram', () => {
|
|||||||
name: '',
|
name: '',
|
||||||
steps: null,
|
steps: null,
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
@ -53,7 +54,7 @@ describe('getWorkflowVersionDiagram', () => {
|
|||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
label: 'Company is Created',
|
name: 'Company created',
|
||||||
nodeType: 'trigger',
|
nodeType: 'trigger',
|
||||||
triggerType: 'DATABASE_EVENT',
|
triggerType: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
@ -93,6 +94,7 @@ describe('getWorkflowVersionDiagram', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,6 +11,7 @@ describe('insertStep', () => {
|
|||||||
name: '',
|
name: '',
|
||||||
steps: [],
|
steps: [],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
@ -54,6 +55,7 @@ describe('insertStep', () => {
|
|||||||
name: '',
|
name: '',
|
||||||
steps: [],
|
steps: [],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
@ -135,6 +137,7 @@ describe('insertStep', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
@ -220,6 +223,7 @@ describe('insertStep', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ it('Preserves the properties defined in the previous version but not in the next
|
|||||||
const previousDiagram: WorkflowDiagram = {
|
const previousDiagram: WorkflowDiagram = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
data: { nodeType: 'action', label: '', actionType: 'CODE' },
|
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||||
id: '1',
|
id: '1',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
selected: true,
|
selected: true,
|
||||||
@ -16,7 +16,7 @@ it('Preserves the properties defined in the previous version but not in the next
|
|||||||
const nextDiagram: WorkflowDiagram = {
|
const nextDiagram: WorkflowDiagram = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
data: { nodeType: 'action', label: '', actionType: 'CODE' },
|
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||||
id: '1',
|
id: '1',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
},
|
},
|
||||||
@ -27,7 +27,7 @@ it('Preserves the properties defined in the previous version but not in the next
|
|||||||
expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({
|
expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
data: { nodeType: 'action', label: '', actionType: 'CODE' },
|
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||||
id: '1',
|
id: '1',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
selected: true,
|
selected: true,
|
||||||
@ -41,7 +41,7 @@ it('Replaces duplicated properties with the next value', () => {
|
|||||||
const previousDiagram: WorkflowDiagram = {
|
const previousDiagram: WorkflowDiagram = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
data: { nodeType: 'action', label: '', actionType: 'CODE' },
|
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||||
id: '1',
|
id: '1',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
},
|
},
|
||||||
@ -51,7 +51,7 @@ it('Replaces duplicated properties with the next value', () => {
|
|||||||
const nextDiagram: WorkflowDiagram = {
|
const nextDiagram: WorkflowDiagram = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
data: { nodeType: 'action', label: '2', actionType: 'CODE' },
|
data: { nodeType: 'action', name: '2', actionType: 'CODE' },
|
||||||
id: '1',
|
id: '1',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
},
|
},
|
||||||
@ -62,7 +62,7 @@ it('Replaces duplicated properties with the next value', () => {
|
|||||||
expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({
|
expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
data: { nodeType: 'action', label: '2', actionType: 'CODE' },
|
data: { nodeType: 'action', name: '2', actionType: 'CODE' },
|
||||||
id: '1',
|
id: '1',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,6 +28,7 @@ it('returns a deep copy of the provided steps array instead of mutating it', ()
|
|||||||
name: '',
|
name: '',
|
||||||
steps: [stepToBeRemoved],
|
steps: [stepToBeRemoved],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
@ -108,6 +109,7 @@ it('removes a step in a non-empty steps array', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -29,6 +29,7 @@ describe('replaceStep', () => {
|
|||||||
name: '',
|
name: '',
|
||||||
steps: [stepToBeReplaced],
|
steps: [stepToBeReplaced],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: { eventName: 'company.created', outputSchema: {} },
|
settings: { eventName: 'company.created', outputSchema: {} },
|
||||||
type: 'DATABASE_EVENT',
|
type: 'DATABASE_EVENT',
|
||||||
},
|
},
|
||||||
@ -123,6 +124,7 @@ describe('replaceStep', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
trigger: {
|
trigger: {
|
||||||
|
name: 'Company created',
|
||||||
settings: {
|
settings: {
|
||||||
eventName: 'company.created',
|
eventName: 'company.created',
|
||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export const generateWorkflowDiagram = ({
|
|||||||
data: {
|
data: {
|
||||||
nodeType: 'action',
|
nodeType: 'action',
|
||||||
actionType: nodeActionType,
|
actionType: nodeActionType,
|
||||||
label: nodeLabel,
|
name: isDefined(step.name) ? step.name : nodeLabel,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: xPos,
|
x: xPos,
|
||||||
@ -110,7 +110,7 @@ export const generateWorkflowDiagram = ({
|
|||||||
data: {
|
data: {
|
||||||
nodeType: 'trigger',
|
nodeType: 'trigger',
|
||||||
triggerType: trigger.type,
|
triggerType: trigger.type,
|
||||||
label: triggerLabel,
|
name: isDefined(trigger.name) ? trigger.name : triggerLabel,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export const getStepDefaultDefinition = ({
|
|||||||
case 'RECORD_CRUD.CREATE': {
|
case 'RECORD_CRUD.CREATE': {
|
||||||
return {
|
return {
|
||||||
id: newStepId,
|
id: newStepId,
|
||||||
name: 'Record Create',
|
name: 'Create Record',
|
||||||
type: 'RECORD_CRUD',
|
type: 'RECORD_CRUD',
|
||||||
valid: false,
|
valid: false,
|
||||||
settings: {
|
settings: {
|
||||||
|
|||||||
Reference in New Issue
Block a user