Infer function input in workflow step (#8308)

- add `inputSchema` column in serverless function. This is an array of
parameters, with their name and type
- on serverless function id update, get the `inputSchema` + store empty
settings in step
- from step settings, build the form 

TODO in next PR:
- use field type to decide what kind of form should be printed
- have a strategy to handle object as input



https://github.com/user-attachments/assets/ed96f919-24b5-4baf-a051-31f76f45e575
This commit is contained in:
Thomas Trompette
2024-11-05 14:57:06 +01:00
committed by GitHub
parent d1531aa1b6
commit be8141ce5e
29 changed files with 334 additions and 90 deletions

View File

@ -1,9 +1,13 @@
import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions';
import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
import VariableTagInput from '@/workflow/search-variables/components/VariableTagInput';
import { WorkflowCodeStep } from '@/workflow/types/Workflow';
import { useTheme } from '@emotion/react';
import { useState } from 'react';
import { IconCode, isDefined } from 'twenty-ui';
import { useDebouncedCallback } from 'use-debounce';
import { capitalize } from '~/utils/string/capitalize';
type WorkflowEditActionFormServerlessFunctionProps =
| {
@ -23,6 +27,48 @@ export const WorkflowEditActionFormServerlessFunction = (
const { serverlessFunctions } = useGetManyServerlessFunctions();
const defaultFunctionInput =
props.action.settings.input.serverlessFunctionInput;
const [functionInput, setFunctionInput] =
useState<Record<string, any>>(defaultFunctionInput);
const [serverlessFunctionId, setServerlessFunctionId] = useState<string>(
props.action.settings.input.serverlessFunctionId,
);
const updateFunctionInput = useDebouncedCallback(
async (newFunctionInput: object) => {
if (props.readonly === true) {
return;
}
props.onActionUpdate({
...props.action,
settings: {
...props.action.settings,
input: {
serverlessFunctionId:
props.action.settings.input.serverlessFunctionId,
serverlessFunctionVersion:
props.action.settings.input.serverlessFunctionVersion,
serverlessFunctionInput: {
...props.action.settings.input.serverlessFunctionInput,
...newFunctionInput,
},
},
},
});
},
1_000,
);
const handleInputChange = (key: string, value: any) => {
const newFunctionInput = { ...functionInput, [key]: value };
setFunctionInput(newFunctionInput);
updateFunctionInput(newFunctionInput);
};
const availableFunctions: Array<SelectOption<string>> = [
{ label: 'None', value: '' },
...serverlessFunctions
@ -32,9 +78,43 @@ export const WorkflowEditActionFormServerlessFunction = (
.map((serverlessFunction) => ({
label: serverlessFunction.name,
value: serverlessFunction.id,
latestVersionInputSchema: serverlessFunction.latestVersionInputSchema,
})),
];
const handleFunctionChange = (newServerlessFunctionId: string) => {
setServerlessFunctionId(newServerlessFunctionId);
const serverlessFunction = serverlessFunctions.find(
(f) => f.id === newServerlessFunctionId,
);
const serverlessFunctionVersion =
serverlessFunction?.latestVersion || 'latest';
const defaultFunctionInput = serverlessFunction?.latestVersionInputSchema
? serverlessFunction.latestVersionInputSchema
.map((parameter) => parameter.name)
.reduce((acc, name) => ({ ...acc, [name]: null }), {})
: {};
if (!props.readonly) {
props.onActionUpdate({
...props.action,
settings: {
...props.action.settings,
input: {
serverlessFunctionId: newServerlessFunctionId,
serverlessFunctionVersion,
serverlessFunctionInput: defaultFunctionInput,
},
},
});
}
setFunctionInput(defaultFunctionInput);
};
return (
<WorkflowEditGenericFormBase
HeaderIcon={<IconCode color={theme.color.orange} />}
@ -42,31 +122,24 @@ export const WorkflowEditActionFormServerlessFunction = (
headerType="Code"
>
<Select
dropdownId="workflow-edit-action-function"
dropdownId="select-serverless-function-id"
label="Function"
fullWidth
value={props.action.settings.input.serverlessFunctionId}
value={serverlessFunctionId}
options={availableFunctions}
disabled={props.readonly}
onChange={(serverlessFunctionId) => {
if (props.readonly === true) {
return;
}
props.onActionUpdate({
...props.action,
settings: {
...props.action.settings,
input: {
serverlessFunctionId,
serverlessFunctionVersion:
serverlessFunctions.find((f) => f.id === serverlessFunctionId)
?.latestVersion || 'latest',
},
},
});
}}
onChange={handleFunctionChange}
/>
{functionInput &&
Object.entries(functionInput).map(([inputKey, inputValue]) => (
<VariableTagInput
inputId={`input-${inputKey}`}
label={capitalize(inputKey)}
placeholder="Enter value (use {{variable}} for dynamic content)"
value={inputValue ?? ''}
onChange={(value) => handleInputChange(inputKey, value)}
/>
))}
</WorkflowEditGenericFormBase>
);
};