Add Manual Triggers (#8024)
In this PR: - Add support for manual triggers in the backend - Add a right drawer to let users select the type of trigger they want - Create a specific right drawer for database event triggers - Create a right drawer for manual triggers; let the user select where the manual trigger should be made available - Create a default trigger as soon as the user selects the type of trigger they want. It prevents the user to see empty selects for record type and event type. By default, the database event trigger will be set to "company.created". It should be visible enough for users to understand what happens and choose another record type or event type. https://github.com/user-attachments/assets/29a21985-1823-4890-9eb3-e4f876459c7a
This commit is contained in:
committed by
GitHub
parent
bf2ba25a6e
commit
0144553667
@ -24,18 +24,16 @@ export const RightDrawerWorkflowSelectActionContent = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledActionListContainer>
|
||||
{ACTIONS.map((action) => (
|
||||
<MenuItem
|
||||
LeftIcon={action.icon}
|
||||
text={action.label}
|
||||
onClick={() => {
|
||||
return createStep(action.type);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</StyledActionListContainer>
|
||||
</>
|
||||
<StyledActionListContainer>
|
||||
{ACTIONS.map((action) => (
|
||||
<MenuItem
|
||||
LeftIcon={action.icon}
|
||||
text={action.label}
|
||||
onClick={() => {
|
||||
return createStep(action.type);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</StyledActionListContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import { RightDrawerWorkflowSelectTriggerTypeContent } from '@/workflow/components/RightDrawerWorkflowSelectTriggerTypeContent';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const RightDrawerWorkflowSelectTriggerType = () => {
|
||||
const workflowId = useRecoilValue(workflowIdState);
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
if (!isDefined(workflow)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RightDrawerWorkflowSelectTriggerTypeContent workflow={workflow} />;
|
||||
};
|
||||
@ -0,0 +1,58 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId';
|
||||
import { TRIGGER_TYPES } from '@/workflow/constants/TriggerTypes';
|
||||
import { useUpdateWorkflowVersionTrigger } from '@/workflow/hooks/useUpdateWorkflowVersionTrigger';
|
||||
import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState';
|
||||
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||
import { getTriggerDefaultDefinition } from '@/workflow/utils/getTriggerDefaultDefinition';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
const StyledActionListContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
padding-block: ${({ theme }) => theme.spacing(1)};
|
||||
padding-inline: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const RightDrawerWorkflowSelectTriggerTypeContent = ({
|
||||
workflow,
|
||||
}: {
|
||||
workflow: WorkflowWithCurrentVersion;
|
||||
}) => {
|
||||
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
||||
|
||||
return (
|
||||
<StyledActionListContainer>
|
||||
{TRIGGER_TYPES.map((action) => (
|
||||
<MenuItem
|
||||
LeftIcon={action.icon}
|
||||
text={action.label}
|
||||
onClick={async () => {
|
||||
await updateTrigger(
|
||||
getTriggerDefaultDefinition({
|
||||
type: action.type,
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
);
|
||||
|
||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||
|
||||
openRightDrawer(RightDrawerPages.WorkflowStepEdit);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</StyledActionListContainer>
|
||||
);
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { CREATE_STEP_STEP_ID } from '@/workflow/constants/CreateStepStepId';
|
||||
import { EMPTY_TRIGGER_STEP_ID } from '@/workflow/constants/EmptyTriggerStepId';
|
||||
import { useStartNodeCreation } from '@/workflow/hooks/useStartNodeCreation';
|
||||
import { useTriggerNodeSelection } from '@/workflow/hooks/useTriggerNodeSelection';
|
||||
import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState';
|
||||
@ -26,7 +28,14 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const isCreateStepNode = selectedNode.type === 'create-step';
|
||||
const isEmptyTriggerNode = selectedNode.type === EMPTY_TRIGGER_STEP_ID;
|
||||
if (isEmptyTriggerNode) {
|
||||
openRightDrawer(RightDrawerPages.WorkflowStepSelectTriggerType);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const isCreateStepNode = selectedNode.type === CREATE_STEP_STEP_ID;
|
||||
if (isCreateStepNode) {
|
||||
if (selectedNode.data.nodeType !== 'create-step') {
|
||||
throw new Error('Expected selected node to be a create step node.');
|
||||
|
||||
@ -3,7 +3,7 @@ import { WorkflowDiagramStepNodeData } from '@/workflow/types/WorkflowDiagram';
|
||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconCode, IconMail, IconPlaylistAdd } from 'twenty-ui';
|
||||
import { IconCode, IconHandMove, IconMail, IconPlaylistAdd } from 'twenty-ui';
|
||||
|
||||
const StyledStepNodeLabelIconContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -26,17 +26,30 @@ export const WorkflowDiagramStepNodeBase = ({
|
||||
const renderStepIcon = () => {
|
||||
switch (data.nodeType) {
|
||||
case 'trigger': {
|
||||
return (
|
||||
<StyledStepNodeLabelIconContainer>
|
||||
<IconPlaylistAdd
|
||||
size={theme.icon.size.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</StyledStepNodeLabelIconContainer>
|
||||
);
|
||||
}
|
||||
case 'condition': {
|
||||
return null;
|
||||
switch (data.triggerType) {
|
||||
case 'DATABASE_EVENT': {
|
||||
return (
|
||||
<StyledStepNodeLabelIconContainer>
|
||||
<IconPlaylistAdd
|
||||
size={theme.icon.size.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</StyledStepNodeLabelIconContainer>
|
||||
);
|
||||
}
|
||||
case 'MANUAL': {
|
||||
return (
|
||||
<StyledStepNodeLabelIconContainer>
|
||||
<IconHandMove
|
||||
size={theme.icon.size.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</StyledStepNodeLabelIconContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return assertUnreachable(data.triggerType);
|
||||
}
|
||||
case 'action': {
|
||||
switch (data.actionType) {
|
||||
|
||||
@ -5,25 +5,17 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { WorkflowEditActionFormBase } from '@/workflow/components/WorkflowEditActionFormBase';
|
||||
import { VariableTagInput } from '@/workflow/search-variables/components/VariableTagInput';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import VariableTagInput from '@/workflow/search-variables/components/VariableTagInput';
|
||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||
import { WorkflowSendEmailStep } from '@/workflow/types/Workflow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useEffect } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconMail, IconPlus, isDefined } from 'twenty-ui';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
const StyledTriggerSettings = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(6)};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type WorkflowEditActionFormSendEmailProps =
|
||||
| {
|
||||
action: WorkflowSendEmailStep;
|
||||
@ -174,87 +166,85 @@ export const WorkflowEditActionFormSendEmail = (
|
||||
|
||||
return (
|
||||
!loading && (
|
||||
<WorkflowEditActionFormBase
|
||||
ActionIcon={<IconMail color={theme.color.blue} />}
|
||||
actionTitle="Send Email"
|
||||
actionType="Email"
|
||||
<WorkflowEditGenericFormBase
|
||||
HeaderIcon={<IconMail color={theme.color.blue} />}
|
||||
headerTitle="Send Email"
|
||||
headerType="Email"
|
||||
>
|
||||
<StyledTriggerSettings>
|
||||
<Controller
|
||||
name="connectedAccountId"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
dropdownId="select-connected-account-id"
|
||||
label="Account"
|
||||
fullWidth
|
||||
emptyOption={emptyOption}
|
||||
value={field.value}
|
||||
options={connectedAccountOptions}
|
||||
callToActionButton={{
|
||||
onClick: () =>
|
||||
triggerGoogleApisOAuth({ redirectLocation: redirectUrl }),
|
||||
Icon: IconPlus,
|
||||
text: 'Add account',
|
||||
}}
|
||||
onChange={(connectedAccountId) => {
|
||||
field.onChange(connectedAccountId);
|
||||
handleSave(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="email"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<VariableTagInput
|
||||
inputId="email-input"
|
||||
label="Email"
|
||||
placeholder="Enter receiver email (use {{variable}} for dynamic content)"
|
||||
value={field.value}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
handleSave();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="subject"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<VariableTagInput
|
||||
inputId="email-subject-input"
|
||||
label="Subject"
|
||||
placeholder="Enter email subject (use {{variable}} for dynamic content)"
|
||||
value={field.value}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
handleSave();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="connectedAccountId"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
dropdownId="select-connected-account-id"
|
||||
label="Account"
|
||||
fullWidth
|
||||
emptyOption={emptyOption}
|
||||
value={field.value}
|
||||
options={connectedAccountOptions}
|
||||
callToActionButton={{
|
||||
onClick: () =>
|
||||
triggerGoogleApisOAuth({ redirectLocation: redirectUrl }),
|
||||
Icon: IconPlus,
|
||||
text: 'Add account',
|
||||
}}
|
||||
onChange={(connectedAccountId) => {
|
||||
field.onChange(connectedAccountId);
|
||||
handleSave(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="email"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<VariableTagInput
|
||||
inputId="email-input"
|
||||
label="Email"
|
||||
placeholder="Enter receiver email (use {{variable}} for dynamic content)"
|
||||
value={field.value}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
handleSave();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="subject"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<VariableTagInput
|
||||
inputId="email-subject-input"
|
||||
label="Subject"
|
||||
placeholder="Enter email subject (use {{variable}} for dynamic content)"
|
||||
value={field.value}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
handleSave();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="body"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<TextArea
|
||||
label="Body"
|
||||
placeholder="Enter email body (use {{variable}} for dynamic content)"
|
||||
value={field.value}
|
||||
minRows={4}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
handleSave();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</StyledTriggerSettings>
|
||||
</WorkflowEditActionFormBase>
|
||||
<Controller
|
||||
name="body"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<TextArea
|
||||
label="Body"
|
||||
placeholder="Enter email body (use {{variable}} for dynamic content)"
|
||||
value={field.value}
|
||||
minRows={4}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
handleSave();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</WorkflowEditGenericFormBase>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowEditActionFormBase } from '@/workflow/components/WorkflowEditActionFormBase';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import { WorkflowCodeStep } from '@/workflow/types/Workflow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconCode, isDefined } from 'twenty-ui';
|
||||
|
||||
const StyledTriggerSettings = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(6)};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type WorkflowEditActionFormServerlessFunctionProps =
|
||||
| {
|
||||
action: WorkflowCodeStep;
|
||||
@ -44,36 +36,34 @@ export const WorkflowEditActionFormServerlessFunction = (
|
||||
];
|
||||
|
||||
return (
|
||||
<WorkflowEditActionFormBase
|
||||
ActionIcon={<IconCode color={theme.color.orange} />}
|
||||
actionTitle="Code - Serverless Function"
|
||||
actionType="Code"
|
||||
<WorkflowEditGenericFormBase
|
||||
HeaderIcon={<IconCode color={theme.color.orange} />}
|
||||
headerTitle="Code - Serverless Function"
|
||||
headerType="Code"
|
||||
>
|
||||
<StyledTriggerSettings>
|
||||
<Select
|
||||
dropdownId="workflow-edit-action-function"
|
||||
label="Function"
|
||||
fullWidth
|
||||
value={props.action.settings.input.serverlessFunctionId}
|
||||
options={availableFunctions}
|
||||
disabled={props.readonly}
|
||||
onChange={(updatedFunction) => {
|
||||
if (props.readonly === true) {
|
||||
return;
|
||||
}
|
||||
<Select
|
||||
dropdownId="workflow-edit-action-function"
|
||||
label="Function"
|
||||
fullWidth
|
||||
value={props.action.settings.input.serverlessFunctionId}
|
||||
options={availableFunctions}
|
||||
disabled={props.readonly}
|
||||
onChange={(updatedFunction) => {
|
||||
if (props.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onActionUpdate({
|
||||
...props.action,
|
||||
settings: {
|
||||
...props.action.settings,
|
||||
input: {
|
||||
serverlessFunctionId: updatedFunction,
|
||||
},
|
||||
props.onActionUpdate({
|
||||
...props.action,
|
||||
settings: {
|
||||
...props.action.settings,
|
||||
input: {
|
||||
serverlessFunctionId: updatedFunction,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledTriggerSettings>
|
||||
</WorkflowEditActionFormBase>
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</WorkflowEditGenericFormBase>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import React from 'react';
|
||||
|
||||
const StyledTriggerHeader = styled.div`
|
||||
const StyledHeader = styled.div`
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
display: flex;
|
||||
@ -9,7 +9,7 @@ const StyledTriggerHeader = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(6)};
|
||||
`;
|
||||
|
||||
const StyledTriggerHeaderTitle = styled.p`
|
||||
const StyledHeaderTitle = styled.p`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
@ -17,12 +17,12 @@ const StyledTriggerHeaderTitle = styled.p`
|
||||
margin: ${({ theme }) => theme.spacing(3)} 0;
|
||||
`;
|
||||
|
||||
const StyledTriggerHeaderType = styled.p`
|
||||
const StyledHeaderType = styled.p`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const StyledTriggerHeaderIconContainer = styled.div`
|
||||
const StyledHeaderIconContainer = styled.div`
|
||||
align-self: flex-start;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -32,30 +32,35 @@ const StyledTriggerHeaderIconContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const WorkflowEditActionFormBase = ({
|
||||
ActionIcon,
|
||||
actionTitle,
|
||||
actionType,
|
||||
const StyledContentContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(6)};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
export const WorkflowEditGenericFormBase = ({
|
||||
HeaderIcon,
|
||||
headerTitle,
|
||||
headerType,
|
||||
children,
|
||||
}: {
|
||||
ActionIcon: React.ReactNode;
|
||||
actionTitle: string;
|
||||
actionType: string;
|
||||
HeaderIcon: React.ReactNode;
|
||||
headerTitle: string;
|
||||
headerType: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<StyledTriggerHeader>
|
||||
<StyledTriggerHeaderIconContainer>
|
||||
{ActionIcon}
|
||||
</StyledTriggerHeaderIconContainer>
|
||||
<StyledHeader>
|
||||
<StyledHeaderIconContainer>{HeaderIcon}</StyledHeaderIconContainer>
|
||||
|
||||
<StyledTriggerHeaderTitle>{actionTitle}</StyledTriggerHeaderTitle>
|
||||
<StyledHeaderTitle>{headerTitle}</StyledHeaderTitle>
|
||||
|
||||
<StyledTriggerHeaderType>{actionType}</StyledTriggerHeaderType>
|
||||
</StyledTriggerHeader>
|
||||
<StyledHeaderType>{headerType}</StyledHeaderType>
|
||||
</StyledHeader>
|
||||
|
||||
{children}
|
||||
<StyledContentContainer>{children}</StyledContentContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { OBJECT_EVENT_TRIGGERS } from '@/workflow/constants/ObjectEventTriggers';
|
||||
import { WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow';
|
||||
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
@ -45,23 +45,23 @@ const StyledTriggerSettings = styled.div`
|
||||
row-gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type WorkflowEditTriggerFormProps =
|
||||
type WorkflowEditTriggerDatabaseEventFormProps =
|
||||
| {
|
||||
trigger: WorkflowTrigger | undefined;
|
||||
trigger: WorkflowDatabaseEventTrigger;
|
||||
readonly: true;
|
||||
onTriggerUpdate?: undefined;
|
||||
}
|
||||
| {
|
||||
trigger: WorkflowTrigger | undefined;
|
||||
trigger: WorkflowDatabaseEventTrigger;
|
||||
readonly?: false;
|
||||
onTriggerUpdate: (trigger: WorkflowTrigger) => void;
|
||||
onTriggerUpdate: (trigger: WorkflowDatabaseEventTrigger) => void;
|
||||
};
|
||||
|
||||
export const WorkflowEditTriggerForm = ({
|
||||
export const WorkflowEditTriggerDatabaseEventForm = ({
|
||||
trigger,
|
||||
readonly,
|
||||
onTriggerUpdate,
|
||||
}: WorkflowEditTriggerFormProps) => {
|
||||
}: WorkflowEditTriggerDatabaseEventFormProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
@ -102,7 +102,7 @@ export const WorkflowEditTriggerForm = ({
|
||||
|
||||
<StyledTriggerHeaderType>
|
||||
{isDefined(selectedEvent)
|
||||
? `Trigger . Record is ${selectedEvent.label}`
|
||||
? `Trigger · Record is ${selectedEvent.label}`
|
||||
: '-'}
|
||||
</StyledTriggerHeaderType>
|
||||
</StyledTriggerHeader>
|
||||
@ -0,0 +1,97 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/constants/ManualTriggerAvailabilityOptions';
|
||||
import {
|
||||
WorkflowManualTrigger,
|
||||
WorkflowManualTriggerAvailability,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { getManualTriggerDefaultSettings } from '@/workflow/utils/getManualTriggerDefaultSettings';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconHandMove, isDefined } from 'twenty-ui';
|
||||
|
||||
type WorkflowEditTriggerManualFormProps =
|
||||
| {
|
||||
trigger: WorkflowManualTrigger;
|
||||
readonly: true;
|
||||
onTriggerUpdate?: undefined;
|
||||
}
|
||||
| {
|
||||
trigger: WorkflowManualTrigger;
|
||||
readonly?: false;
|
||||
onTriggerUpdate: (trigger: WorkflowManualTrigger) => void;
|
||||
};
|
||||
|
||||
export const WorkflowEditTriggerManualForm = ({
|
||||
trigger,
|
||||
readonly,
|
||||
onTriggerUpdate,
|
||||
}: WorkflowEditTriggerManualFormProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
const availableMetadata: Array<SelectOption<string>> =
|
||||
activeObjectMetadataItems.map((item) => ({
|
||||
label: item.labelPlural,
|
||||
value: item.nameSingular,
|
||||
}));
|
||||
|
||||
const manualTriggerAvailability: WorkflowManualTriggerAvailability =
|
||||
isDefined(trigger.settings.objectType)
|
||||
? 'WHEN_RECORD_SELECTED'
|
||||
: 'EVERYWHERE';
|
||||
|
||||
return (
|
||||
<WorkflowEditGenericFormBase
|
||||
HeaderIcon={<IconHandMove color={theme.font.color.tertiary} />}
|
||||
headerTitle="Manual Trigger"
|
||||
headerType="Trigger · Manual"
|
||||
>
|
||||
<Select
|
||||
dropdownId="workflow-edit-manual-trigger-availability"
|
||||
label="Available"
|
||||
fullWidth
|
||||
disabled={readonly}
|
||||
value={manualTriggerAvailability}
|
||||
options={MANUAL_TRIGGER_AVAILABILITY_OPTIONS}
|
||||
onChange={(updatedTriggerType) => {
|
||||
if (readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
onTriggerUpdate({
|
||||
...trigger,
|
||||
settings: getManualTriggerDefaultSettings({
|
||||
availability: updatedTriggerType,
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{manualTriggerAvailability === 'WHEN_RECORD_SELECTED' ? (
|
||||
<Select
|
||||
dropdownId="workflow-edit-manual-trigger-object"
|
||||
label="Object"
|
||||
fullWidth
|
||||
value={trigger.settings.objectType}
|
||||
options={availableMetadata}
|
||||
disabled={readonly}
|
||||
onChange={(updatedObject) => {
|
||||
if (readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
onTriggerUpdate({
|
||||
...trigger,
|
||||
settings: {
|
||||
objectType: updatedObject,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</WorkflowEditGenericFormBase>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
import { WorkflowEditActionFormSendEmail } from '@/workflow/components/WorkflowEditActionFormSendEmail';
|
||||
import { WorkflowEditActionFormServerlessFunction } from '@/workflow/components/WorkflowEditActionFormServerlessFunction';
|
||||
import { WorkflowEditTriggerForm } from '@/workflow/components/WorkflowEditTriggerForm';
|
||||
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/components/WorkflowEditTriggerDatabaseEventForm';
|
||||
import { WorkflowEditTriggerManualForm } from '@/workflow/components/WorkflowEditTriggerManualForm';
|
||||
import {
|
||||
WorkflowAction,
|
||||
WorkflowTrigger,
|
||||
@ -41,12 +42,36 @@ export const WorkflowStepDetail = ({
|
||||
|
||||
switch (stepDefinition.type) {
|
||||
case 'trigger': {
|
||||
return (
|
||||
<WorkflowEditTriggerForm
|
||||
trigger={stepDefinition.definition}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
if (!isDefined(stepDefinition.definition)) {
|
||||
throw new Error(
|
||||
'Expected the trigger to be defined at this point. Ensure the trigger has been set with a default value before trying to edit it.',
|
||||
);
|
||||
}
|
||||
|
||||
switch (stepDefinition.definition.type) {
|
||||
case 'DATABASE_EVENT': {
|
||||
return (
|
||||
<WorkflowEditTriggerDatabaseEventForm
|
||||
trigger={stepDefinition.definition}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'MANUAL': {
|
||||
return (
|
||||
<WorkflowEditTriggerManualForm
|
||||
trigger={stepDefinition.definition}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return assertUnreachable(
|
||||
stepDefinition.definition,
|
||||
`Expected the step to have an handler; ${JSON.stringify(stepDefinition)}`,
|
||||
);
|
||||
}
|
||||
case 'action': {
|
||||
@ -70,6 +95,11 @@ export const WorkflowStepDetail = ({
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return assertUnreachable(
|
||||
stepDefinition.definition,
|
||||
`Expected the step to have an handler; ${JSON.stringify(stepDefinition)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user