mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-22 03:17:40 +03:00
Add Record Create action in the frontend (#8514)
In this PR: - Updated the front-end types for workflows to include CRUD actions and global naming changes - Allow users to create a Record Create action - Scaffold the edit for Record Create action; for now, I render a `<VariableTagInput />` component for every editable field; it's likely we'll change it soon Closes https://github.com/twentyhq/private-issues/issues/142 Demo: https://github.com/user-attachments/assets/6f0b207a-b7d2-46d9-b5ab-9e32bde55d76
This commit is contained in:
parent
316537a68a
commit
c17e18b1e9
@ -0,0 +1,26 @@
|
|||||||
|
import VariableTagInput from '@/workflow/search-variables/components/VariableTagInput';
|
||||||
|
|
||||||
|
type FormFieldInputProps = {
|
||||||
|
recordFieldInputdId: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormFieldInput = ({
|
||||||
|
recordFieldInputdId,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
}: FormFieldInputProps) => {
|
||||||
|
return (
|
||||||
|
<VariableTagInput
|
||||||
|
inputId={recordFieldInputdId}
|
||||||
|
label={label}
|
||||||
|
placeholder="Enter value (use {{variable}} for dynamic content)"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -3,7 +3,13 @@ import { WorkflowDiagramStepNodeData } from '@/workflow/types/WorkflowDiagram';
|
|||||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconCode, IconHandMove, IconMail, IconPlaylistAdd } from 'twenty-ui';
|
import {
|
||||||
|
IconAddressBook,
|
||||||
|
IconCode,
|
||||||
|
IconHandMove,
|
||||||
|
IconMail,
|
||||||
|
IconPlaylistAdd,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
const StyledStepNodeLabelIconContainer = styled.div`
|
const StyledStepNodeLabelIconContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -70,6 +76,21 @@ export const WorkflowDiagramStepNodeBase = ({
|
|||||||
</StyledStepNodeLabelIconContainer>
|
</StyledStepNodeLabelIconContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'RECORD_CRUD.CREATE': {
|
||||||
|
return (
|
||||||
|
<StyledStepNodeLabelIconContainer>
|
||||||
|
<IconAddressBook
|
||||||
|
size={theme.icon.size.lg}
|
||||||
|
color={theme.font.color.tertiary}
|
||||||
|
stroke={theme.icon.stroke.sm}
|
||||||
|
/>
|
||||||
|
</StyledStepNodeLabelIconContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'RECORD_CRUD.DELETE':
|
||||||
|
case 'RECORD_CRUD.UPDATE': {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,167 @@
|
|||||||
|
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||||
|
import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput';
|
||||||
|
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||||
|
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||||
|
import { WorkflowRecordCreateAction } from '@/workflow/types/Workflow';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
HorizontalSeparator,
|
||||||
|
IconAddressBook,
|
||||||
|
isDefined,
|
||||||
|
useIcons,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
|
type WorkflowEditActionFormRecordCreateProps = {
|
||||||
|
action: WorkflowRecordCreateAction;
|
||||||
|
actionOptions:
|
||||||
|
| {
|
||||||
|
readonly: true;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
readonly?: false;
|
||||||
|
onActionUpdate: (action: WorkflowRecordCreateAction) => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type SendEmailFormData = {
|
||||||
|
objectName: string;
|
||||||
|
[field: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkflowEditActionFormRecordCreate = ({
|
||||||
|
action,
|
||||||
|
actionOptions,
|
||||||
|
}: WorkflowEditActionFormRecordCreateProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
|
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||||
|
|
||||||
|
const availableMetadata: Array<SelectOption<string>> =
|
||||||
|
activeObjectMetadataItems.map((item) => ({
|
||||||
|
Icon: getIcon(item.icon),
|
||||||
|
label: item.labelPlural,
|
||||||
|
value: item.nameSingular,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState<SendEmailFormData>({
|
||||||
|
objectName: action.settings.input.objectName,
|
||||||
|
...action.settings.input.objectRecord,
|
||||||
|
});
|
||||||
|
const isFormDisabled = actionOptions.readonly;
|
||||||
|
|
||||||
|
const handleFieldChange = (
|
||||||
|
fieldName: keyof SendEmailFormData,
|
||||||
|
updatedValue: string,
|
||||||
|
) => {
|
||||||
|
const newFormData: SendEmailFormData = {
|
||||||
|
...formData,
|
||||||
|
[fieldName]: updatedValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
setFormData(newFormData);
|
||||||
|
|
||||||
|
saveAction(newFormData);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormData({
|
||||||
|
objectName: action.settings.input.objectName,
|
||||||
|
...action.settings.input.objectRecord,
|
||||||
|
});
|
||||||
|
}, [action.settings.input]);
|
||||||
|
|
||||||
|
const selectedObjectMetadataItemNameSingular = formData.objectName;
|
||||||
|
|
||||||
|
const selectedObjectMetadataItem = activeObjectMetadataItems.find(
|
||||||
|
(item) => item.nameSingular === selectedObjectMetadataItemNameSingular,
|
||||||
|
);
|
||||||
|
if (!isDefined(selectedObjectMetadataItem)) {
|
||||||
|
throw new Error('Should have found the metadata item');
|
||||||
|
}
|
||||||
|
|
||||||
|
const editableFields = selectedObjectMetadataItem.fields.filter(
|
||||||
|
(field) =>
|
||||||
|
field.type !== FieldMetadataType.Relation &&
|
||||||
|
!field.isSystem &&
|
||||||
|
field.isActive,
|
||||||
|
);
|
||||||
|
|
||||||
|
const saveAction = useDebouncedCallback(
|
||||||
|
async (formData: SendEmailFormData) => {
|
||||||
|
if (actionOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { objectName: updatedObjectName, ...updatedOtherFields } = formData;
|
||||||
|
|
||||||
|
actionOptions.onActionUpdate({
|
||||||
|
...action,
|
||||||
|
settings: {
|
||||||
|
...action.settings,
|
||||||
|
input: {
|
||||||
|
type: 'CREATE',
|
||||||
|
objectName: updatedObjectName,
|
||||||
|
objectRecord: updatedOtherFields,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
1_000,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
saveAction.flush();
|
||||||
|
};
|
||||||
|
}, [saveAction]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WorkflowEditGenericFormBase
|
||||||
|
HeaderIcon={
|
||||||
|
<IconAddressBook
|
||||||
|
color={theme.font.color.tertiary}
|
||||||
|
stroke={theme.icon.stroke.sm}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
headerTitle="Record Create"
|
||||||
|
headerType="Action"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
dropdownId="workflow-edit-action-record-create-object-name"
|
||||||
|
label="Object"
|
||||||
|
fullWidth
|
||||||
|
disabled={isFormDisabled}
|
||||||
|
value={formData.objectName}
|
||||||
|
emptyOption={{ label: 'Select an option', value: '' }}
|
||||||
|
options={availableMetadata}
|
||||||
|
onChange={(updatedObjectName) => {
|
||||||
|
const newFormData: SendEmailFormData = {
|
||||||
|
objectName: updatedObjectName,
|
||||||
|
};
|
||||||
|
|
||||||
|
setFormData(newFormData);
|
||||||
|
|
||||||
|
saveAction(newFormData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HorizontalSeparator noMargin />
|
||||||
|
|
||||||
|
{editableFields.map((field) => (
|
||||||
|
<FormFieldInput
|
||||||
|
key={field.id}
|
||||||
|
recordFieldInputdId={field.id}
|
||||||
|
label={field.label}
|
||||||
|
value={formData[field.name] as string}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleFieldChange(field.name, value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</WorkflowEditGenericFormBase>
|
||||||
|
);
|
||||||
|
};
|
@ -7,7 +7,7 @@ import { Select, SelectOption } from '@/ui/input/components/Select';
|
|||||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||||
import { VariableTagInput } from '@/workflow/search-variables/components/VariableTagInput';
|
import { VariableTagInput } from '@/workflow/search-variables/components/VariableTagInput';
|
||||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||||
import { WorkflowSendEmailStep } from '@/workflow/types/Workflow';
|
import { WorkflowSendEmailAction } from '@/workflow/types/Workflow';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
@ -15,16 +15,17 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { IconMail, IconPlus, isDefined } from 'twenty-ui';
|
import { IconMail, IconPlus, isDefined } from 'twenty-ui';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
type WorkflowEditActionFormSendEmailProps =
|
type WorkflowEditActionFormSendEmailProps = {
|
||||||
| {
|
action: WorkflowSendEmailAction;
|
||||||
action: WorkflowSendEmailStep;
|
actionOptions:
|
||||||
readonly: true;
|
| {
|
||||||
}
|
readonly: true;
|
||||||
| {
|
}
|
||||||
action: WorkflowSendEmailStep;
|
| {
|
||||||
readonly?: false;
|
readonly?: false;
|
||||||
onActionUpdate: (action: WorkflowSendEmailStep) => void;
|
onActionUpdate: (action: WorkflowSendEmailAction) => void;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type SendEmailFormData = {
|
type SendEmailFormData = {
|
||||||
connectedAccountId: string;
|
connectedAccountId: string;
|
||||||
@ -33,9 +34,10 @@ type SendEmailFormData = {
|
|||||||
body: string;
|
body: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormSendEmail = (
|
export const WorkflowEditActionFormSendEmail = ({
|
||||||
props: WorkflowEditActionFormSendEmailProps,
|
action,
|
||||||
) => {
|
actionOptions,
|
||||||
|
}: WorkflowEditActionFormSendEmailProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
const { triggerApisOAuth } = useTriggerApisOAuth();
|
const { triggerApisOAuth } = useTriggerApisOAuth();
|
||||||
@ -50,7 +52,7 @@ export const WorkflowEditActionFormSendEmail = (
|
|||||||
subject: '',
|
subject: '',
|
||||||
body: '',
|
body: '',
|
||||||
},
|
},
|
||||||
disabled: props.readonly,
|
disabled: actionOptions.readonly,
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkConnectedAccountScopes = async (
|
const checkConnectedAccountScopes = async (
|
||||||
@ -77,23 +79,23 @@ export const WorkflowEditActionFormSendEmail = (
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.setValue(
|
form.setValue(
|
||||||
'connectedAccountId',
|
'connectedAccountId',
|
||||||
props.action.settings.input.connectedAccountId ?? '',
|
action.settings.input.connectedAccountId ?? '',
|
||||||
);
|
);
|
||||||
form.setValue('email', props.action.settings.input.email ?? '');
|
form.setValue('email', action.settings.input.email ?? '');
|
||||||
form.setValue('subject', props.action.settings.input.subject ?? '');
|
form.setValue('subject', action.settings.input.subject ?? '');
|
||||||
form.setValue('body', props.action.settings.input.body ?? '');
|
form.setValue('body', action.settings.input.body ?? '');
|
||||||
}, [props.action.settings, form]);
|
}, [action.settings, form]);
|
||||||
|
|
||||||
const saveAction = useDebouncedCallback(
|
const saveAction = useDebouncedCallback(
|
||||||
async (formData: SendEmailFormData, checkScopes = false) => {
|
async (formData: SendEmailFormData, checkScopes = false) => {
|
||||||
if (props.readonly === true) {
|
if (actionOptions.readonly === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
props.onActionUpdate({
|
actionOptions.onActionUpdate({
|
||||||
...props.action,
|
...action,
|
||||||
settings: {
|
settings: {
|
||||||
...props.action.settings,
|
...action.settings,
|
||||||
input: {
|
input: {
|
||||||
connectedAccountId: formData.connectedAccountId,
|
connectedAccountId: formData.connectedAccountId,
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
@ -132,12 +134,12 @@ export const WorkflowEditActionFormSendEmail = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(props.action.settings.input.connectedAccountId) &&
|
isDefined(action.settings.input.connectedAccountId) &&
|
||||||
props.action.settings.input.connectedAccountId !== ''
|
action.settings.input.connectedAccountId !== ''
|
||||||
) {
|
) {
|
||||||
filter.or.push({
|
filter.or.push({
|
||||||
id: {
|
id: {
|
||||||
eq: props.action.settings.input.connectedAccountId,
|
eq: action.settings.input.connectedAccountId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions';
|
import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions';
|
||||||
import { setNestedValue } from '@/workflow/utils/setNestedValue';
|
|
||||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||||
import VariableTagInput from '@/workflow/search-variables/components/VariableTagInput';
|
import VariableTagInput from '@/workflow/search-variables/components/VariableTagInput';
|
||||||
import { WorkflowCodeStep } from '@/workflow/types/Workflow';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import { IconCode, isDefined, HorizontalSeparator } from 'twenty-ui';
|
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
|
||||||
import { getDefaultFunctionInputFromInputSchema } from '@/workflow/utils/getDefaultFunctionInputFromInputSchema';
|
|
||||||
import { FunctionInput } from '@/workflow/types/FunctionInput';
|
import { FunctionInput } from '@/workflow/types/FunctionInput';
|
||||||
|
import { WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||||
|
import { getDefaultFunctionInputFromInputSchema } from '@/workflow/utils/getDefaultFunctionInputFromInputSchema';
|
||||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/utils/mergeDefaultFunctionInputAndFunctionInput';
|
import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||||
|
import { setNestedValue } from '@/workflow/utils/setNestedValue';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { HorizontalSeparator, IconCode, isDefined } from 'twenty-ui';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -37,20 +37,22 @@ const StyledInputContainer = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type WorkflowEditActionFormServerlessFunctionProps =
|
type WorkflowEditActionFormServerlessFunctionProps = {
|
||||||
| {
|
action: WorkflowCodeAction;
|
||||||
action: WorkflowCodeStep;
|
actionOptions:
|
||||||
readonly: true;
|
| {
|
||||||
}
|
readonly: true;
|
||||||
| {
|
}
|
||||||
action: WorkflowCodeStep;
|
| {
|
||||||
readonly?: false;
|
readonly?: false;
|
||||||
onActionUpdate: (action: WorkflowCodeStep) => void;
|
onActionUpdate: (action: WorkflowCodeAction) => void;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormServerlessFunction = (
|
export const WorkflowEditActionFormServerlessFunction = ({
|
||||||
props: WorkflowEditActionFormServerlessFunctionProps,
|
action,
|
||||||
) => {
|
actionOptions,
|
||||||
|
}: WorkflowEditActionFormServerlessFunctionProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { serverlessFunctions } = useGetManyServerlessFunctions();
|
const { serverlessFunctions } = useGetManyServerlessFunctions();
|
||||||
|
|
||||||
@ -66,8 +68,7 @@ export const WorkflowEditActionFormServerlessFunction = (
|
|||||||
const defaultFunctionInput =
|
const defaultFunctionInput =
|
||||||
getDefaultFunctionInputFromInputSchema(inputSchema);
|
getDefaultFunctionInputFromInputSchema(inputSchema);
|
||||||
|
|
||||||
const existingFunctionInput =
|
const existingFunctionInput = action.settings.input.serverlessFunctionInput;
|
||||||
props.action.settings.input.serverlessFunctionInput;
|
|
||||||
|
|
||||||
return mergeDefaultFunctionInputAndFunctionInput({
|
return mergeDefaultFunctionInputAndFunctionInput({
|
||||||
defaultFunctionInput,
|
defaultFunctionInput,
|
||||||
@ -76,21 +77,21 @@ export const WorkflowEditActionFormServerlessFunction = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const functionInput = getFunctionInput(
|
const functionInput = getFunctionInput(
|
||||||
props.action.settings.input.serverlessFunctionId,
|
action.settings.input.serverlessFunctionId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateFunctionInput = useDebouncedCallback(
|
const updateFunctionInput = useDebouncedCallback(
|
||||||
async (newFunctionInput: object) => {
|
async (newFunctionInput: object) => {
|
||||||
if (props.readonly === true) {
|
if (actionOptions.readonly === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
props.onActionUpdate({
|
actionOptions.onActionUpdate({
|
||||||
...props.action,
|
...action,
|
||||||
settings: {
|
settings: {
|
||||||
...props.action.settings,
|
...action.settings,
|
||||||
input: {
|
input: {
|
||||||
...props.action.settings.input,
|
...action.settings.input,
|
||||||
serverlessFunctionInput: newFunctionInput,
|
serverlessFunctionInput: newFunctionInput,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -116,14 +117,18 @@ export const WorkflowEditActionFormServerlessFunction = (
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleFunctionChange = (newServerlessFunctionId: string) => {
|
const handleFunctionChange = (newServerlessFunctionId: string) => {
|
||||||
|
if (actionOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const serverlessFunction = serverlessFunctions.find(
|
const serverlessFunction = serverlessFunctions.find(
|
||||||
(f) => f.id === newServerlessFunctionId,
|
(f) => f.id === newServerlessFunctionId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const newProps = {
|
const newProps = {
|
||||||
...props.action,
|
...action,
|
||||||
settings: {
|
settings: {
|
||||||
...props.action.settings,
|
...action.settings,
|
||||||
input: {
|
input: {
|
||||||
serverlessFunctionId: newServerlessFunctionId,
|
serverlessFunctionId: newServerlessFunctionId,
|
||||||
serverlessFunctionVersion:
|
serverlessFunctionVersion:
|
||||||
@ -133,9 +138,7 @@ export const WorkflowEditActionFormServerlessFunction = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!props.readonly) {
|
actionOptions.onActionUpdate(newProps);
|
||||||
props.onActionUpdate(newProps);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderFields = (
|
const renderFields = (
|
||||||
@ -203,10 +206,10 @@ export const WorkflowEditActionFormServerlessFunction = (
|
|||||||
dropdownId="select-serverless-function-id"
|
dropdownId="select-serverless-function-id"
|
||||||
label="Function"
|
label="Function"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={props.action.settings.input.serverlessFunctionId}
|
value={action.settings.input.serverlessFunctionId}
|
||||||
options={availableFunctions}
|
options={availableFunctions}
|
||||||
emptyOption={{ label: 'None', value: '' }}
|
emptyOption={{ label: 'None', value: '' }}
|
||||||
disabled={props.readonly}
|
disabled={actionOptions.readonly}
|
||||||
onChange={handleFunctionChange}
|
onChange={handleFunctionChange}
|
||||||
/>
|
/>
|
||||||
{renderFields(functionInput)}
|
{renderFields(functionInput)}
|
||||||
|
@ -45,22 +45,22 @@ const StyledTriggerSettings = styled.div`
|
|||||||
row-gap: ${({ theme }) => theme.spacing(4)};
|
row-gap: ${({ theme }) => theme.spacing(4)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type WorkflowEditTriggerDatabaseEventFormProps =
|
type WorkflowEditTriggerDatabaseEventFormProps = {
|
||||||
| {
|
trigger: WorkflowDatabaseEventTrigger;
|
||||||
trigger: WorkflowDatabaseEventTrigger;
|
triggerOptions:
|
||||||
readonly: true;
|
| {
|
||||||
onTriggerUpdate?: undefined;
|
readonly: true;
|
||||||
}
|
onTriggerUpdate?: undefined;
|
||||||
| {
|
}
|
||||||
trigger: WorkflowDatabaseEventTrigger;
|
| {
|
||||||
readonly?: false;
|
readonly?: false;
|
||||||
onTriggerUpdate: (trigger: WorkflowDatabaseEventTrigger) => void;
|
onTriggerUpdate: (trigger: WorkflowDatabaseEventTrigger) => void;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const WorkflowEditTriggerDatabaseEventForm = ({
|
export const WorkflowEditTriggerDatabaseEventForm = ({
|
||||||
trigger,
|
trigger,
|
||||||
readonly,
|
triggerOptions,
|
||||||
onTriggerUpdate,
|
|
||||||
}: WorkflowEditTriggerDatabaseEventFormProps) => {
|
}: WorkflowEditTriggerDatabaseEventFormProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -112,16 +112,16 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
|
|||||||
dropdownId="workflow-edit-trigger-record-type"
|
dropdownId="workflow-edit-trigger-record-type"
|
||||||
label="Record Type"
|
label="Record Type"
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={readonly}
|
disabled={triggerOptions.readonly}
|
||||||
value={triggerEvent?.objectType}
|
value={triggerEvent?.objectType}
|
||||||
emptyOption={{ label: 'Select an option', value: '' }}
|
emptyOption={{ label: 'Select an option', value: '' }}
|
||||||
options={availableMetadata}
|
options={availableMetadata}
|
||||||
onChange={(updatedRecordType) => {
|
onChange={(updatedRecordType) => {
|
||||||
if (readonly === true) {
|
if (triggerOptions.readonly === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTriggerUpdate(
|
triggerOptions.onTriggerUpdate(
|
||||||
isDefined(trigger) && isDefined(triggerEvent)
|
isDefined(trigger) && isDefined(triggerEvent)
|
||||||
? {
|
? {
|
||||||
...trigger,
|
...trigger,
|
||||||
@ -147,13 +147,13 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
|
|||||||
value={triggerEvent?.event}
|
value={triggerEvent?.event}
|
||||||
emptyOption={{ label: 'Select an option', value: '' }}
|
emptyOption={{ label: 'Select an option', value: '' }}
|
||||||
options={OBJECT_EVENT_TRIGGERS}
|
options={OBJECT_EVENT_TRIGGERS}
|
||||||
disabled={readonly}
|
disabled={triggerOptions.readonly}
|
||||||
onChange={(updatedEvent) => {
|
onChange={(updatedEvent) => {
|
||||||
if (readonly === true) {
|
if (triggerOptions.readonly === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTriggerUpdate(
|
triggerOptions.onTriggerUpdate(
|
||||||
isDefined(trigger) && isDefined(triggerEvent)
|
isDefined(trigger) && isDefined(triggerEvent)
|
||||||
? {
|
? {
|
||||||
...trigger,
|
...trigger,
|
||||||
|
@ -10,22 +10,22 @@ import { getManualTriggerDefaultSettings } from '@/workflow/utils/getManualTrigg
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { IconHandMove, isDefined, useIcons } from 'twenty-ui';
|
import { IconHandMove, isDefined, useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
type WorkflowEditTriggerManualFormProps =
|
type WorkflowEditTriggerManualFormProps = {
|
||||||
| {
|
trigger: WorkflowManualTrigger;
|
||||||
trigger: WorkflowManualTrigger;
|
triggerOptions:
|
||||||
readonly: true;
|
| {
|
||||||
onTriggerUpdate?: undefined;
|
readonly: true;
|
||||||
}
|
onTriggerUpdate?: undefined;
|
||||||
| {
|
}
|
||||||
trigger: WorkflowManualTrigger;
|
| {
|
||||||
readonly?: false;
|
readonly?: false;
|
||||||
onTriggerUpdate: (trigger: WorkflowManualTrigger) => void;
|
onTriggerUpdate: (trigger: WorkflowManualTrigger) => void;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const WorkflowEditTriggerManualForm = ({
|
export const WorkflowEditTriggerManualForm = ({
|
||||||
trigger,
|
trigger,
|
||||||
readonly,
|
triggerOptions,
|
||||||
onTriggerUpdate,
|
|
||||||
}: WorkflowEditTriggerManualFormProps) => {
|
}: WorkflowEditTriggerManualFormProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
@ -54,15 +54,15 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
dropdownId="workflow-edit-manual-trigger-availability"
|
dropdownId="workflow-edit-manual-trigger-availability"
|
||||||
label="Available"
|
label="Available"
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={readonly}
|
disabled={triggerOptions.readonly}
|
||||||
value={manualTriggerAvailability}
|
value={manualTriggerAvailability}
|
||||||
options={MANUAL_TRIGGER_AVAILABILITY_OPTIONS}
|
options={MANUAL_TRIGGER_AVAILABILITY_OPTIONS}
|
||||||
onChange={(updatedTriggerType) => {
|
onChange={(updatedTriggerType) => {
|
||||||
if (readonly === true) {
|
if (triggerOptions.readonly === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTriggerUpdate({
|
triggerOptions.onTriggerUpdate({
|
||||||
...trigger,
|
...trigger,
|
||||||
settings: getManualTriggerDefaultSettings({
|
settings: getManualTriggerDefaultSettings({
|
||||||
availability: updatedTriggerType,
|
availability: updatedTriggerType,
|
||||||
@ -79,13 +79,13 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
fullWidth
|
fullWidth
|
||||||
value={trigger.settings.objectType}
|
value={trigger.settings.objectType}
|
||||||
options={availableMetadata}
|
options={availableMetadata}
|
||||||
disabled={readonly}
|
disabled={triggerOptions.readonly}
|
||||||
onChange={(updatedObject) => {
|
onChange={(updatedObject) => {
|
||||||
if (readonly === true) {
|
if (triggerOptions.readonly === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTriggerUpdate({
|
triggerOptions.onTriggerUpdate({
|
||||||
...trigger,
|
...trigger,
|
||||||
settings: {
|
settings: {
|
||||||
objectType: updatedObject,
|
objectType: updatedObject,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { WorkflowEditActionFormRecordCreate } from '@/workflow/components/WorkflowEditActionFormRecordCreate';
|
||||||
import { WorkflowEditActionFormSendEmail } from '@/workflow/components/WorkflowEditActionFormSendEmail';
|
import { WorkflowEditActionFormSendEmail } from '@/workflow/components/WorkflowEditActionFormSendEmail';
|
||||||
import { WorkflowEditActionFormServerlessFunction } from '@/workflow/components/WorkflowEditActionFormServerlessFunction';
|
import { WorkflowEditActionFormServerlessFunction } from '@/workflow/components/WorkflowEditActionFormServerlessFunction';
|
||||||
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/components/WorkflowEditTriggerDatabaseEventForm';
|
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/components/WorkflowEditTriggerDatabaseEventForm';
|
||||||
@ -9,6 +10,7 @@ import {
|
|||||||
} from '@/workflow/types/Workflow';
|
} from '@/workflow/types/Workflow';
|
||||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||||
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
||||||
|
import { isWorkflowRecordCreateAction } from '@/workflow/utils/isWorkflowRecordCreateAction';
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
type WorkflowStepDetailProps =
|
type WorkflowStepDetailProps =
|
||||||
@ -53,8 +55,7 @@ export const WorkflowStepDetail = ({
|
|||||||
return (
|
return (
|
||||||
<WorkflowEditTriggerDatabaseEventForm
|
<WorkflowEditTriggerDatabaseEventForm
|
||||||
trigger={stepDefinition.definition}
|
trigger={stepDefinition.definition}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
triggerOptions={props}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -62,8 +63,7 @@ export const WorkflowStepDetail = ({
|
|||||||
return (
|
return (
|
||||||
<WorkflowEditTriggerManualForm
|
<WorkflowEditTriggerManualForm
|
||||||
trigger={stepDefinition.definition}
|
trigger={stepDefinition.definition}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
triggerOptions={props}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -80,8 +80,7 @@ export const WorkflowStepDetail = ({
|
|||||||
return (
|
return (
|
||||||
<WorkflowEditActionFormServerlessFunction
|
<WorkflowEditActionFormServerlessFunction
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
actionOptions={props}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -89,11 +88,22 @@ export const WorkflowStepDetail = ({
|
|||||||
return (
|
return (
|
||||||
<WorkflowEditActionFormSendEmail
|
<WorkflowEditActionFormSendEmail
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
actionOptions={props}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'RECORD_CRUD': {
|
||||||
|
if (isWorkflowRecordCreateAction(stepDefinition.definition)) {
|
||||||
|
return (
|
||||||
|
<WorkflowEditActionFormRecordCreate
|
||||||
|
action={stepDefinition.definition}
|
||||||
|
actionOptions={props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return assertUnreachable(
|
return assertUnreachable(
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { WorkflowStepType } from '@/workflow/types/Workflow';
|
import { WorkflowStepType } from '@/workflow/types/Workflow';
|
||||||
import { IconComponent, IconSettingsAutomation } from 'twenty-ui';
|
import {
|
||||||
|
IconAddressBook,
|
||||||
|
IconComponent,
|
||||||
|
IconSettingsAutomation,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
export const ACTIONS: Array<{
|
export const ACTIONS: Array<{
|
||||||
label: string;
|
label: string;
|
||||||
@ -16,4 +20,9 @@ export const ACTIONS: Array<{
|
|||||||
type: 'SEND_EMAIL',
|
type: 'SEND_EMAIL',
|
||||||
icon: IconSettingsAutomation,
|
icon: IconSettingsAutomation,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Create Record',
|
||||||
|
type: 'RECORD_CRUD.CREATE',
|
||||||
|
icon: IconAddressBook,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
|
import { useComputeStepOutputSchema } from '@/workflow/hooks/useComputeStepOutputSchema';
|
||||||
import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion';
|
import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion';
|
||||||
import { workflowCreateStepFromParentStepIdState } from '@/workflow/states/workflowCreateStepFromParentStepIdState';
|
import { workflowCreateStepFromParentStepIdState } from '@/workflow/states/workflowCreateStepFromParentStepIdState';
|
||||||
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/states/workflowDiagramTriggerNodeSelectionState';
|
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/states/workflowDiagramTriggerNodeSelectionState';
|
||||||
@ -16,7 +18,6 @@ import { getStepDefaultDefinition } from '@/workflow/utils/getStepDefaultDefinit
|
|||||||
import { insertStep } from '@/workflow/utils/insertStep';
|
import { insertStep } from '@/workflow/utils/insertStep';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined } from 'twenty-ui';
|
||||||
import { useComputeStepOutputSchema } from '@/workflow/hooks/useComputeStepOutputSchema';
|
|
||||||
|
|
||||||
export const useCreateStep = ({
|
export const useCreateStep = ({
|
||||||
workflow,
|
workflow,
|
||||||
@ -43,6 +44,8 @@ export const useCreateStep = ({
|
|||||||
|
|
||||||
const { computeStepOutputSchema } = useComputeStepOutputSchema();
|
const { computeStepOutputSchema } = useComputeStepOutputSchema();
|
||||||
|
|
||||||
|
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||||
|
|
||||||
const insertNodeAndSave = async ({
|
const insertNodeAndSave = async ({
|
||||||
parentNodeId,
|
parentNodeId,
|
||||||
nodeToAdd,
|
nodeToAdd,
|
||||||
@ -86,7 +89,10 @@ export const useCreateStep = ({
|
|||||||
throw new Error('Select a step to create a new step from first.');
|
throw new Error('Select a step to create a new step from first.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const newStep = getStepDefaultDefinition(newStepType);
|
const newStep = getStepDefaultDefinition({
|
||||||
|
type: newStepType,
|
||||||
|
activeObjectMetadataItems,
|
||||||
|
});
|
||||||
|
|
||||||
const outputSchema = (
|
const outputSchema = (
|
||||||
await computeStepOutputSchema({
|
await computeStepOutputSchema({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
type BaseWorkflowStepSettings = {
|
type BaseWorkflowActionSettings = {
|
||||||
input: object;
|
input: object;
|
||||||
outputSchema: object;
|
outputSchema: object;
|
||||||
errorHandlingOptions: {
|
errorHandlingOptions: {
|
||||||
@ -11,7 +11,7 @@ type BaseWorkflowStepSettings = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowCodeStepSettings = BaseWorkflowStepSettings & {
|
export type WorkflowCodeActionSettings = BaseWorkflowActionSettings & {
|
||||||
input: {
|
input: {
|
||||||
serverlessFunctionId: string;
|
serverlessFunctionId: string;
|
||||||
serverlessFunctionVersion: string;
|
serverlessFunctionVersion: string;
|
||||||
@ -21,7 +21,7 @@ export type WorkflowCodeStepSettings = BaseWorkflowStepSettings & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowSendEmailStepSettings = BaseWorkflowStepSettings & {
|
export type WorkflowSendEmailActionSettings = BaseWorkflowActionSettings & {
|
||||||
input: {
|
input: {
|
||||||
connectedAccountId: string;
|
connectedAccountId: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -30,29 +30,83 @@ export type WorkflowSendEmailStepSettings = BaseWorkflowStepSettings & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type BaseWorkflowStep = {
|
type ObjectRecord = Record<string, any>;
|
||||||
|
|
||||||
|
export type WorkflowCreateRecordActionInput = {
|
||||||
|
type: 'CREATE';
|
||||||
|
objectName: string;
|
||||||
|
objectRecord: ObjectRecord;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowUpdateRecordActionInput = {
|
||||||
|
type: 'UPDATE';
|
||||||
|
objectName: string;
|
||||||
|
objectRecord: ObjectRecord;
|
||||||
|
objectRecordId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowDeleteRecordActionInput = {
|
||||||
|
type: 'DELETE';
|
||||||
|
objectName: string;
|
||||||
|
objectRecordId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowRecordCRUDActionInput =
|
||||||
|
| WorkflowCreateRecordActionInput
|
||||||
|
| WorkflowUpdateRecordActionInput
|
||||||
|
| WorkflowDeleteRecordActionInput;
|
||||||
|
|
||||||
|
export type WorkflowRecordCRUDType = WorkflowRecordCRUDActionInput['type'];
|
||||||
|
|
||||||
|
export type WorkflowRecordCRUDActionSettings = BaseWorkflowActionSettings & {
|
||||||
|
input: WorkflowRecordCRUDActionInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BaseWorkflowAction = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowCodeStep = BaseWorkflowStep & {
|
export type WorkflowCodeAction = BaseWorkflowAction & {
|
||||||
type: 'CODE';
|
type: 'CODE';
|
||||||
settings: WorkflowCodeStepSettings;
|
settings: WorkflowCodeActionSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowSendEmailStep = BaseWorkflowStep & {
|
export type WorkflowSendEmailAction = BaseWorkflowAction & {
|
||||||
type: 'SEND_EMAIL';
|
type: 'SEND_EMAIL';
|
||||||
settings: WorkflowSendEmailStepSettings;
|
settings: WorkflowSendEmailActionSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowAction = WorkflowCodeStep | WorkflowSendEmailStep;
|
export type WorkflowRecordCRUDAction = BaseWorkflowAction & {
|
||||||
|
type: 'RECORD_CRUD';
|
||||||
|
settings: WorkflowRecordCRUDActionSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowRecordCreateAction = WorkflowRecordCRUDAction & {
|
||||||
|
settings: { input: { type: 'CREATE' } };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowRecordUpdateAction = WorkflowRecordCRUDAction & {
|
||||||
|
settings: { input: { type: 'UPDATE' } };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowRecordDeleteAction = WorkflowRecordCRUDAction & {
|
||||||
|
settings: { input: { type: 'DELETE' } };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowAction =
|
||||||
|
| WorkflowCodeAction
|
||||||
|
| WorkflowSendEmailAction
|
||||||
|
| WorkflowRecordCRUDAction;
|
||||||
|
|
||||||
export type WorkflowStep = WorkflowAction;
|
export type WorkflowStep = WorkflowAction;
|
||||||
|
|
||||||
export type WorkflowActionType = WorkflowAction['type'];
|
export type WorkflowActionType =
|
||||||
|
| Exclude<WorkflowAction['type'], WorkflowRecordCRUDAction['type']>
|
||||||
|
| `${WorkflowRecordCRUDAction['type']}.${WorkflowRecordCRUDType}`;
|
||||||
|
|
||||||
export type WorkflowStepType = WorkflowStep['type'];
|
export type WorkflowStepType = WorkflowActionType;
|
||||||
|
|
||||||
type BaseTrigger = {
|
type BaseTrigger = {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId';
|
import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId';
|
||||||
import { WorkflowStep, WorkflowTrigger } from '@/workflow/types/Workflow';
|
import {
|
||||||
|
WorkflowActionType,
|
||||||
|
WorkflowStep,
|
||||||
|
WorkflowTrigger,
|
||||||
|
} from '@/workflow/types/Workflow';
|
||||||
import {
|
import {
|
||||||
WorkflowDiagram,
|
WorkflowDiagram,
|
||||||
WorkflowDiagramEdge,
|
WorkflowDiagramEdge,
|
||||||
@ -30,12 +34,26 @@ export const generateWorkflowDiagram = ({
|
|||||||
yPos: number,
|
yPos: number,
|
||||||
) => {
|
) => {
|
||||||
const nodeId = step.id;
|
const nodeId = step.id;
|
||||||
|
|
||||||
|
let nodeActionType: WorkflowActionType;
|
||||||
|
if (step.type === 'RECORD_CRUD') {
|
||||||
|
nodeActionType = `RECORD_CRUD.${step.settings.input.type}`;
|
||||||
|
} else {
|
||||||
|
nodeActionType = step.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nodeLabel = step.name;
|
||||||
|
if (step.type === 'RECORD_CRUD') {
|
||||||
|
// FIXME: use activeObjectMetadataItems to get labelSingular
|
||||||
|
nodeLabel = `${capitalize(step.settings.input.type.toLowerCase())} ${capitalize(step.settings.input.objectName)}`;
|
||||||
|
}
|
||||||
|
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: nodeId,
|
id: nodeId,
|
||||||
data: {
|
data: {
|
||||||
nodeType: 'action',
|
nodeType: 'action',
|
||||||
actionType: step.type,
|
actionType: nodeActionType,
|
||||||
label: step.name,
|
label: nodeLabel,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: xPos,
|
x: xPos,
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { WorkflowStep, WorkflowStepType } from '@/workflow/types/Workflow';
|
import { WorkflowStep, WorkflowStepType } from '@/workflow/types/Workflow';
|
||||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
export const getStepDefaultDefinition = (
|
export const getStepDefaultDefinition = ({
|
||||||
type: WorkflowStepType,
|
type,
|
||||||
): WorkflowStep => {
|
activeObjectMetadataItems,
|
||||||
|
}: {
|
||||||
|
type: WorkflowStepType;
|
||||||
|
activeObjectMetadataItems: ObjectMetadataItem[];
|
||||||
|
}): WorkflowStep => {
|
||||||
const newStepId = v4();
|
const newStepId = v4();
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -57,6 +62,34 @@ export const getStepDefaultDefinition = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case 'RECORD_CRUD.CREATE': {
|
||||||
|
return {
|
||||||
|
id: newStepId,
|
||||||
|
name: 'Record Create',
|
||||||
|
type: 'RECORD_CRUD',
|
||||||
|
valid: false,
|
||||||
|
settings: {
|
||||||
|
input: {
|
||||||
|
type: 'CREATE',
|
||||||
|
objectName: activeObjectMetadataItems[0].nameSingular,
|
||||||
|
objectRecord: {},
|
||||||
|
},
|
||||||
|
outputSchema: {},
|
||||||
|
errorHandlingOptions: {
|
||||||
|
continueOnFailure: {
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
retryOnFailure: {
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'RECORD_CRUD.DELETE':
|
||||||
|
case 'RECORD_CRUD.UPDATE': {
|
||||||
|
throw new Error('Not implemented yet');
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return assertUnreachable(type, `Unknown type: ${type}`);
|
return assertUnreachable(type, `Unknown type: ${type}`);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import {
|
||||||
|
WorkflowAction,
|
||||||
|
WorkflowRecordCreateAction,
|
||||||
|
} from '@/workflow/types/Workflow';
|
||||||
|
|
||||||
|
export const isWorkflowRecordCreateAction = (
|
||||||
|
action: WorkflowAction,
|
||||||
|
): action is WorkflowRecordCreateAction => {
|
||||||
|
return (
|
||||||
|
action.type === 'RECORD_CRUD' && action.settings.input.type === 'CREATE'
|
||||||
|
);
|
||||||
|
};
|
@ -3,12 +3,22 @@ import { useTheme } from '@emotion/react';
|
|||||||
import IconAddressBookRaw from '@ui/display/icon/assets/address-book.svg?react';
|
import IconAddressBookRaw from '@ui/display/icon/assets/address-book.svg?react';
|
||||||
import { IconComponentProps } from '@ui/display/icon/types/IconComponent';
|
import { IconComponentProps } from '@ui/display/icon/types/IconComponent';
|
||||||
|
|
||||||
type IconAddressBookProps = Pick<IconComponentProps, 'size' | 'stroke'>;
|
type IconAddressBookProps = Pick<
|
||||||
|
IconComponentProps,
|
||||||
|
'size' | 'stroke' | 'color'
|
||||||
|
>;
|
||||||
|
|
||||||
export const IconAddressBook = (props: IconAddressBookProps) => {
|
export const IconAddressBook = (props: IconAddressBookProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const size = props.size ?? 24;
|
const size = props.size ?? 24;
|
||||||
const stroke = props.stroke ?? theme.icon.stroke.md;
|
const stroke = props.stroke ?? theme.icon.stroke.md;
|
||||||
|
|
||||||
return <IconAddressBookRaw height={size} width={size} strokeWidth={stroke} />;
|
return (
|
||||||
|
<IconAddressBookRaw
|
||||||
|
height={size}
|
||||||
|
width={size}
|
||||||
|
stroke={props.color ?? 'currentColor'}
|
||||||
|
strokeWidth={stroke}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user