Add record picker with variables (#8813)
- Add update actions - Create a folder for workflow actions - Add a SingleRecordPicker with variables handler https://github.com/user-attachments/assets/9fd57ce1-1b8d-424a-8aa1-69468d684fa1
This commit is contained in:
@ -0,0 +1,185 @@
|
||||
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 { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker';
|
||||
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 { JsonValue } from 'type-fest';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
type WorkflowEditActionFormRecordCreateProps = {
|
||||
action: WorkflowRecordCreateAction;
|
||||
actionOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
}
|
||||
| {
|
||||
readonly?: false;
|
||||
onActionUpdate: (action: WorkflowRecordCreateAction) => void;
|
||||
};
|
||||
};
|
||||
|
||||
type CreateRecordFormData = {
|
||||
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<CreateRecordFormData>({
|
||||
objectName: action.settings.input.objectName,
|
||||
...action.settings.input.objectRecord,
|
||||
});
|
||||
const isFormDisabled = actionOptions.readonly;
|
||||
|
||||
const handleFieldChange = (
|
||||
fieldName: keyof CreateRecordFormData,
|
||||
updatedValue: JsonValue,
|
||||
) => {
|
||||
const newFormData: CreateRecordFormData = {
|
||||
...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: CreateRecordFormData) => {
|
||||
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]);
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : `Create Record`;
|
||||
|
||||
return (
|
||||
<WorkflowEditGenericFormBase
|
||||
onTitleChange={(newName: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
name: newName,
|
||||
});
|
||||
}}
|
||||
HeaderIcon={
|
||||
<IconAddressBook
|
||||
color={theme.font.color.tertiary}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
headerTitle={headerTitle}
|
||||
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: CreateRecordFormData = {
|
||||
objectName: updatedObjectName,
|
||||
};
|
||||
|
||||
setFormData(newFormData);
|
||||
|
||||
saveAction(newFormData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<HorizontalSeparator noMargin />
|
||||
|
||||
{editableFields.map((field) => {
|
||||
const currentValue = formData[field.name] as JsonValue;
|
||||
|
||||
return (
|
||||
<FormFieldInput
|
||||
key={field.id}
|
||||
defaultValue={currentValue}
|
||||
field={field}
|
||||
onPersist={(value) => {
|
||||
handleFieldChange(field.name, value);
|
||||
}}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</WorkflowEditGenericFormBase>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,179 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker';
|
||||
import { WorkflowRecordUpdateAction } from '@/workflow/types/Workflow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
HorizontalSeparator,
|
||||
IconAddressBook,
|
||||
isDefined,
|
||||
useIcons,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { JsonValue } from 'type-fest';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
type WorkflowEditActionFormRecordUpdateProps = {
|
||||
action: WorkflowRecordUpdateAction;
|
||||
actionOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
}
|
||||
| {
|
||||
readonly?: false;
|
||||
onActionUpdate: (action: WorkflowRecordUpdateAction) => void;
|
||||
};
|
||||
};
|
||||
|
||||
type UpdateRecordFormData = {
|
||||
objectName: string;
|
||||
objectRecordId: string;
|
||||
[field: string]: unknown;
|
||||
};
|
||||
|
||||
export const WorkflowEditActionFormRecordUpdate = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionFormRecordUpdateProps) => {
|
||||
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<UpdateRecordFormData>({
|
||||
objectName: action.settings.input.objectName,
|
||||
objectRecordId: action.settings.input.objectRecordId,
|
||||
...action.settings.input.objectRecord,
|
||||
});
|
||||
const isFormDisabled = actionOptions.readonly;
|
||||
|
||||
const handleFieldChange = (
|
||||
fieldName: keyof UpdateRecordFormData,
|
||||
updatedValue: JsonValue,
|
||||
) => {
|
||||
const newFormData: UpdateRecordFormData = {
|
||||
...formData,
|
||||
[fieldName]: updatedValue,
|
||||
};
|
||||
|
||||
setFormData(newFormData);
|
||||
|
||||
saveAction(newFormData);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFormData({
|
||||
objectName: action.settings.input.objectName,
|
||||
objectRecordId: action.settings.input.objectRecordId,
|
||||
...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 saveAction = useDebouncedCallback(
|
||||
async (formData: UpdateRecordFormData) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
objectName: updatedObjectName,
|
||||
objectRecordId: updatedObjectRecordId,
|
||||
...updatedOtherFields
|
||||
} = formData;
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
settings: {
|
||||
...action.settings,
|
||||
input: {
|
||||
type: 'UPDATE',
|
||||
objectName: updatedObjectName,
|
||||
objectRecordId: updatedObjectRecordId ?? '',
|
||||
objectRecord: updatedOtherFields,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
1_000,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
saveAction.flush();
|
||||
};
|
||||
}, [saveAction]);
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : `Update Record`;
|
||||
|
||||
return (
|
||||
<WorkflowEditGenericFormBase
|
||||
onTitleChange={(newName: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
name: newName,
|
||||
});
|
||||
}}
|
||||
HeaderIcon={
|
||||
<IconAddressBook
|
||||
color={theme.font.color.tertiary}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
headerTitle={headerTitle}
|
||||
headerType="Action"
|
||||
>
|
||||
<Select
|
||||
dropdownId="workflow-edit-action-record-update-object-name"
|
||||
label="Object"
|
||||
fullWidth
|
||||
disabled={isFormDisabled}
|
||||
value={formData.objectName}
|
||||
emptyOption={{ label: 'Select an option', value: '' }}
|
||||
options={availableMetadata}
|
||||
onChange={(updatedObjectName) => {
|
||||
const newFormData: UpdateRecordFormData = {
|
||||
objectName: updatedObjectName,
|
||||
objectRecordId: '',
|
||||
};
|
||||
|
||||
setFormData(newFormData);
|
||||
|
||||
saveAction(newFormData);
|
||||
}}
|
||||
/>
|
||||
|
||||
<HorizontalSeparator noMargin />
|
||||
|
||||
<WorkflowSingleRecordPicker
|
||||
label="Record"
|
||||
onChange={(objectRecordId) =>
|
||||
handleFieldChange('objectRecordId', objectRecordId)
|
||||
}
|
||||
objectNameSingular={formData.objectName}
|
||||
defaultValue={formData.objectRecordId}
|
||||
/>
|
||||
</WorkflowEditGenericFormBase>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,268 @@
|
||||
import { GMAIL_SEND_SCOPE } from '@/accounts/constants/GmailSendScope';
|
||||
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
|
||||
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker';
|
||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||
import { WorkflowSendEmailAction } from '@/workflow/types/Workflow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
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';
|
||||
|
||||
type WorkflowEditActionFormSendEmailProps = {
|
||||
action: WorkflowSendEmailAction;
|
||||
actionOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
}
|
||||
| {
|
||||
readonly?: false;
|
||||
onActionUpdate: (action: WorkflowSendEmailAction) => void;
|
||||
};
|
||||
};
|
||||
|
||||
type SendEmailFormData = {
|
||||
connectedAccountId: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
body: string;
|
||||
};
|
||||
|
||||
export const WorkflowEditActionFormSendEmail = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionFormSendEmailProps) => {
|
||||
const theme = useTheme();
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { triggerApisOAuth } = useTriggerApisOAuth();
|
||||
|
||||
const workflowId = useRecoilValue(workflowIdState);
|
||||
const redirectUrl = `/object/workflow/${workflowId}`;
|
||||
|
||||
const form = useForm<SendEmailFormData>({
|
||||
defaultValues: {
|
||||
connectedAccountId: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
},
|
||||
disabled: actionOptions.readonly,
|
||||
});
|
||||
|
||||
const checkConnectedAccountScopes = async (
|
||||
connectedAccountId: string | null,
|
||||
) => {
|
||||
const connectedAccount = accounts.find(
|
||||
(account) => account.id === connectedAccountId,
|
||||
);
|
||||
if (!isDefined(connectedAccount)) {
|
||||
return;
|
||||
}
|
||||
const scopes = connectedAccount.scopes;
|
||||
if (
|
||||
!isDefined(scopes) ||
|
||||
!isDefined(scopes.find((scope) => scope === GMAIL_SEND_SCOPE))
|
||||
) {
|
||||
await triggerApisOAuth('google', {
|
||||
redirectLocation: redirectUrl,
|
||||
loginHint: connectedAccount.handle,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue(
|
||||
'connectedAccountId',
|
||||
action.settings.input.connectedAccountId ?? '',
|
||||
);
|
||||
form.setValue('email', action.settings.input.email ?? '');
|
||||
form.setValue('subject', action.settings.input.subject ?? '');
|
||||
form.setValue('body', action.settings.input.body ?? '');
|
||||
}, [action.settings, form]);
|
||||
|
||||
const saveAction = useDebouncedCallback(
|
||||
async (formData: SendEmailFormData, checkScopes = false) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
settings: {
|
||||
...action.settings,
|
||||
input: {
|
||||
connectedAccountId: formData.connectedAccountId,
|
||||
email: formData.email,
|
||||
subject: formData.subject,
|
||||
body: formData.body,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (checkScopes === true) {
|
||||
await checkConnectedAccountScopes(formData.connectedAccountId);
|
||||
}
|
||||
},
|
||||
1_000,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
saveAction.flush();
|
||||
};
|
||||
}, [saveAction]);
|
||||
|
||||
const handleSave = (checkScopes = false) =>
|
||||
form.handleSubmit((formData: SendEmailFormData) =>
|
||||
saveAction(formData, checkScopes),
|
||||
)();
|
||||
|
||||
const filter: { or: object[] } = {
|
||||
or: [
|
||||
{
|
||||
accountOwnerId: {
|
||||
eq: currentWorkspaceMember?.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (
|
||||
isDefined(action.settings.input.connectedAccountId) &&
|
||||
action.settings.input.connectedAccountId !== ''
|
||||
) {
|
||||
filter.or.push({
|
||||
id: {
|
||||
eq: action.settings.input.connectedAccountId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { records: accounts, loading } = useFindManyRecords<ConnectedAccount>({
|
||||
objectNameSingular: 'connectedAccount',
|
||||
filter,
|
||||
});
|
||||
|
||||
let emptyOption: SelectOption<string | null> = { label: 'None', value: null };
|
||||
const connectedAccountOptions: SelectOption<string | null>[] = [];
|
||||
|
||||
accounts.forEach((account) => {
|
||||
const selectOption = {
|
||||
label: account.handle,
|
||||
value: account.id,
|
||||
};
|
||||
if (account.accountOwnerId === currentWorkspaceMember?.id) {
|
||||
connectedAccountOptions.push(selectOption);
|
||||
} else {
|
||||
// This handle the case when the current connected account does not belong to the currentWorkspaceMember
|
||||
// In that case, current connected account email is displayed, but cannot be selected
|
||||
emptyOption = selectOption;
|
||||
}
|
||||
});
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : 'Send Email';
|
||||
|
||||
return (
|
||||
!loading && (
|
||||
<WorkflowEditGenericFormBase
|
||||
onTitleChange={(newName: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
name: newName,
|
||||
});
|
||||
}}
|
||||
HeaderIcon={<IconMail color={theme.color.blue} />}
|
||||
headerTitle={headerTitle}
|
||||
headerType="Email"
|
||||
>
|
||||
<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: () =>
|
||||
triggerApisOAuth('google', { redirectLocation: redirectUrl }),
|
||||
Icon: IconPlus,
|
||||
text: 'Add account',
|
||||
}}
|
||||
onChange={(connectedAccountId) => {
|
||||
field.onChange(connectedAccountId);
|
||||
handleSave(true);
|
||||
}}
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="email"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormTextFieldInput
|
||||
label="Email"
|
||||
placeholder="Enter receiver email"
|
||||
readonly={actionOptions.readonly}
|
||||
defaultValue={field.value}
|
||||
onPersist={(value) => {
|
||||
field.onChange(value);
|
||||
handleSave();
|
||||
}}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="subject"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormTextFieldInput
|
||||
label="Subject"
|
||||
placeholder="Enter email subject"
|
||||
readonly={actionOptions.readonly}
|
||||
defaultValue={field.value}
|
||||
onPersist={(value) => {
|
||||
field.onChange(value);
|
||||
handleSave();
|
||||
}}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="body"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormTextFieldInput
|
||||
label="Body"
|
||||
placeholder="Enter email body"
|
||||
readonly={actionOptions.readonly}
|
||||
defaultValue={field.value}
|
||||
onPersist={(value) => {
|
||||
field.onChange(value);
|
||||
handleSave();
|
||||
}}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</WorkflowEditGenericFormBase>
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions';
|
||||
import { WorkflowEditActionFormServerlessFunctionInner } from '@/workflow/components/WorkflowEditActionFormServerlessFunctionInner';
|
||||
import { WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
|
||||
type WorkflowEditActionFormServerlessFunctionProps = {
|
||||
action: WorkflowCodeAction;
|
||||
actionOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
}
|
||||
| {
|
||||
readonly?: false;
|
||||
onActionUpdate: (action: WorkflowCodeAction) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const WorkflowEditActionFormServerlessFunction = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionFormServerlessFunctionProps) => {
|
||||
const { loading: isLoadingServerlessFunctions } =
|
||||
useGetManyServerlessFunctions();
|
||||
|
||||
if (isLoadingServerlessFunctions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkflowEditActionFormServerlessFunctionInner
|
||||
action={action}
|
||||
actionOptions={actionOptions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user