Support define is tool logic function (#17926)
- supports isTool and timeout settings in defineLogicFunction in apps and in setting tabs definition - compute for all toolInputSchema for logic funciton, in settings and in code steps <img width="991" height="802" alt="image" src="https://github.com/user-attachments/assets/05dc1221-cac9-45a3-87b0-3b13161446fd" />
This commit is contained in:
+46
-65
@@ -1,8 +1,5 @@
|
||||
import { useGetAvailablePackages } from '@/logic-functions/hooks/useGetAvailablePackages';
|
||||
import {
|
||||
type LogicFunctionFormValues,
|
||||
useLogicFunctionUpdateFormState,
|
||||
} from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
|
||||
import { useLogicFunctionEditor } from '@/logic-functions/hooks/useLogicFunctionEditor';
|
||||
import { useFullScreenModal } from '@/ui/layout/fullscreen/hooks/useFullScreenModal';
|
||||
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
|
||||
@@ -13,8 +10,7 @@ import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-
|
||||
|
||||
import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton';
|
||||
import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
|
||||
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
@@ -36,8 +32,6 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { useExecuteLogicFunction } from '@/logic-functions/hooks/useExecuteLogicFunction';
|
||||
import { usePersistLogicFunction } from '@/logic-functions/hooks/usePersistLogicFunction';
|
||||
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
|
||||
import { CODE_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/CodeAction';
|
||||
import { type Monaco } from '@monaco-editor/react';
|
||||
@@ -47,11 +41,15 @@ import { useEffect, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { buildOutputSchemaFromValue } from 'twenty-shared/workflow';
|
||||
import {
|
||||
getOutputSchemaFromValue,
|
||||
type InputJsonSchema,
|
||||
} from 'twenty-shared/logic-function';
|
||||
import { IconCode, IconPlayerPlay } from 'twenty-ui/display';
|
||||
import { CodeEditor } from 'twenty-ui/input';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { getFunctionInputFromInputSchema } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getFunctionInputFromInputSchema';
|
||||
|
||||
const CODE_EDITOR_MIN_HEIGHT = 343;
|
||||
|
||||
@@ -103,7 +101,6 @@ export const WorkflowEditActionCode = ({
|
||||
activeTabIdComponentState,
|
||||
WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
const { updateLogicFunction } = usePersistLogicFunction();
|
||||
const { getUpdatableWorkflowVersion } =
|
||||
useGetUpdatableWorkflowVersionOrThrow();
|
||||
|
||||
@@ -111,6 +108,24 @@ export const WorkflowEditActionCode = ({
|
||||
workflowVisualizerWorkflowIdComponentState,
|
||||
);
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
|
||||
|
||||
const updateOutputSchemaFromTestResult = async (testResult: object) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
const newOutputSchema = getOutputSchemaFromValue(testResult);
|
||||
updateAction({
|
||||
...action,
|
||||
settings: { ...action.settings, outputSchema: newOutputSchema },
|
||||
});
|
||||
};
|
||||
|
||||
const { formValues, loading, executeLogicFunction, onChange, isExecuting } =
|
||||
useLogicFunctionEditor({
|
||||
logicFunctionId,
|
||||
executeCallback: updateOutputSchemaFromTestResult,
|
||||
});
|
||||
|
||||
const { availablePackages } = useGetAvailablePackages({
|
||||
id: logicFunctionId,
|
||||
});
|
||||
@@ -125,52 +140,8 @@ export const WorkflowEditActionCode = ({
|
||||
action.settings.input.logicFunctionInput,
|
||||
);
|
||||
|
||||
const { formValues, setFormValues, loading } =
|
||||
useLogicFunctionUpdateFormState({ logicFunctionId });
|
||||
|
||||
const updateOutputSchemaFromTestResult = async (testResult: object) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
const newOutputSchema = buildOutputSchemaFromValue(testResult);
|
||||
updateAction({
|
||||
...action,
|
||||
settings: { ...action.settings, outputSchema: newOutputSchema },
|
||||
});
|
||||
};
|
||||
|
||||
const { executeLogicFunction, isExecuting } = useExecuteLogicFunction({
|
||||
logicFunctionId,
|
||||
callback: updateOutputSchemaFromTestResult,
|
||||
});
|
||||
|
||||
const handleSave = useDebouncedCallback(async () => {
|
||||
await updateLogicFunction({
|
||||
input: {
|
||||
id: logicFunctionId,
|
||||
update: {
|
||||
sourceHandlerCode: formValues.code,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
|
||||
const onCodeChange = async (newCode: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
setFormValues((prevState: LogicFunctionFormValues) => {
|
||||
return {
|
||||
...prevState,
|
||||
code: newCode,
|
||||
};
|
||||
});
|
||||
await handleSave();
|
||||
await handleUpdateFunctionInputSchema(newCode);
|
||||
};
|
||||
|
||||
const handleUpdateFunctionInputSchema = useDebouncedCallback(
|
||||
async (sourceCode: string) => {
|
||||
async (sourceCode: string, toolInputSchema: InputJsonSchema) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
@@ -179,7 +150,12 @@ export const WorkflowEditActionCode = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const newFunctionInput = await getFunctionInputFromSourceCode(sourceCode);
|
||||
const schemaArray = Array.isArray(toolInputSchema)
|
||||
? toolInputSchema
|
||||
: [toolInputSchema];
|
||||
|
||||
const newFunctionInput = getFunctionInputFromInputSchema(schemaArray)[0];
|
||||
|
||||
const newMergedInput = mergeDefaultFunctionInputAndFunctionInput({
|
||||
newInput: newFunctionInput,
|
||||
oldInput: action.settings.input.logicFunctionInput,
|
||||
@@ -190,6 +166,7 @@ export const WorkflowEditActionCode = ({
|
||||
});
|
||||
|
||||
setFunctionInput(newMergedInput);
|
||||
|
||||
setLogicFunctionTestData((prev) => ({
|
||||
...prev,
|
||||
input: newMergedTestInput,
|
||||
@@ -251,14 +228,12 @@ export const WorkflowEditActionCode = ({
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRunFunction = async () => {
|
||||
const handleTestFunction = async () => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isExecuting) {
|
||||
await executeLogicFunction();
|
||||
}
|
||||
await executeLogicFunction();
|
||||
};
|
||||
|
||||
const handleEditorDidMount = async (
|
||||
@@ -288,12 +263,18 @@ export const WorkflowEditActionCode = ({
|
||||
500,
|
||||
);
|
||||
|
||||
const handleCodeChange = async (value: string) => {
|
||||
const handleCodeChange = async (newCode: string) => {
|
||||
if (actionOptions.readonly === true || !isDefined(workflow)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolInputSchema = await onChange('sourceHandlerCode')(newCode);
|
||||
|
||||
await getUpdatableWorkflowVersion();
|
||||
await onCodeChange(value);
|
||||
|
||||
if (isDefined(toolInputSchema)) {
|
||||
await handleUpdateFunctionInputSchema(newCode, toolInputSchema);
|
||||
}
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
@@ -382,7 +363,7 @@ export const WorkflowEditActionCode = ({
|
||||
<StyledFullScreenCodeEditorContainer>
|
||||
<CodeEditor
|
||||
height="100%"
|
||||
value={formValues.code}
|
||||
value={formValues.sourceHandlerCode}
|
||||
language="typescript"
|
||||
onChange={handleCodeChange}
|
||||
onMount={handleEditorDidMount}
|
||||
@@ -417,7 +398,7 @@ export const WorkflowEditActionCode = ({
|
||||
readonly={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowCodeEditor
|
||||
value={formValues.code}
|
||||
value={formValues.sourceHandlerCode}
|
||||
onChange={handleCodeChange}
|
||||
onMount={handleEditorDidMount}
|
||||
options={{
|
||||
@@ -469,7 +450,7 @@ export const WorkflowEditActionCode = ({
|
||||
? [
|
||||
<CmdEnterActionButton
|
||||
title={t`Test`}
|
||||
onClick={handleRunFunction}
|
||||
onClick={handleTestFunction}
|
||||
disabled={isExecuting}
|
||||
/>,
|
||||
]
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ export const WorkflowReadonlyActionCode = ({
|
||||
id: logicFunctionId,
|
||||
});
|
||||
|
||||
const { code, loading } = useGetLogicFunctionSourceCode({
|
||||
const { sourceHandlerCode, loading } = useGetLogicFunctionSourceCode({
|
||||
logicFunctionId,
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ export const WorkflowReadonlyActionCode = ({
|
||||
<StyledCodeEditorContainer>
|
||||
<CodeEditor
|
||||
height={343}
|
||||
value={code ?? undefined}
|
||||
value={sourceHandlerCode ?? undefined}
|
||||
language="typescript"
|
||||
onMount={handleEditorDidMount}
|
||||
setMarkers={getWrongExportedFunctionMarkers}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { type InputSchema } from '@/workflow/types/InputSchema';
|
||||
import { getFunctionInputFromInputSchema } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getFunctionInputFromInputSchema';
|
||||
|
||||
describe('getDefaultFunctionInputFromInputSchema', () => {
|
||||
it('should init function input properly', () => {
|
||||
const inputSchema = [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: {
|
||||
type: 'string',
|
||||
},
|
||||
b: {
|
||||
type: 'number',
|
||||
},
|
||||
c: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
d: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
da: { type: 'string', enum: ['my', 'enum'] },
|
||||
db: { type: 'number' },
|
||||
},
|
||||
},
|
||||
e: { type: 'object' },
|
||||
},
|
||||
},
|
||||
] as InputSchema;
|
||||
const expectedResult = [
|
||||
{
|
||||
a: null,
|
||||
b: null,
|
||||
c: [],
|
||||
d: { da: 'my', db: null },
|
||||
e: {},
|
||||
},
|
||||
];
|
||||
expect(getFunctionInputFromInputSchema(inputSchema)).toEqual(
|
||||
expectedResult,
|
||||
);
|
||||
});
|
||||
});
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
|
||||
describe('mergeDefaultFunctionInputAndFunctionInput', () => {
|
||||
it('should merge properly', () => {
|
||||
const newInput = {
|
||||
a: null,
|
||||
b: null,
|
||||
c: { cc: null },
|
||||
d: null,
|
||||
e: { ee: null },
|
||||
};
|
||||
const oldInput = { a: 'a', c: 'c', d: { da: null }, e: { ee: 'ee' } };
|
||||
const expectedResult = {
|
||||
a: 'a',
|
||||
b: null,
|
||||
c: { cc: null },
|
||||
d: null,
|
||||
e: { ee: 'ee' },
|
||||
};
|
||||
expect(
|
||||
mergeDefaultFunctionInputAndFunctionInput({
|
||||
newInput: newInput,
|
||||
oldInput: oldInput,
|
||||
}),
|
||||
).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { type InputSchema } from '@/workflow/types/InputSchema';
|
||||
import { type FunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/types/FunctionInput';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import type { InputJsonSchema } from 'twenty-shared/logic-function';
|
||||
|
||||
export const getFunctionInputFromInputSchema = (
|
||||
inputSchema: InputSchema | InputJsonSchema[],
|
||||
): FunctionInput => {
|
||||
return inputSchema.map((param) => {
|
||||
if (
|
||||
isDefined(param.type) &&
|
||||
['string', 'number', 'boolean'].includes(param.type)
|
||||
) {
|
||||
return param.enum && param.enum.length > 0 ? param.enum[0] : null;
|
||||
} else if (param.type === 'object') {
|
||||
const result: FunctionInput = {};
|
||||
if (isDefined(param.properties)) {
|
||||
Object.entries(param.properties).forEach(([key, val]) => {
|
||||
result[key] = getFunctionInputFromInputSchema([val])[0];
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} else if (param.type === 'array' && isDefined(param.items)) {
|
||||
return [];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { type FunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/types/FunctionInput';
|
||||
import { isObject } from '@sniptt/guards';
|
||||
|
||||
export const mergeDefaultFunctionInputAndFunctionInput = ({
|
||||
newInput,
|
||||
oldInput,
|
||||
}: {
|
||||
newInput: FunctionInput;
|
||||
oldInput: FunctionInput;
|
||||
}): FunctionInput => {
|
||||
const result: FunctionInput = {};
|
||||
|
||||
for (const key of Object.keys(newInput)) {
|
||||
const newValue = newInput[key];
|
||||
const oldValue = oldInput[key];
|
||||
|
||||
if (!(key in oldInput)) {
|
||||
result[key] = newValue;
|
||||
} else if (newValue === null && isObject(oldValue)) {
|
||||
result[key] = null;
|
||||
} else if (isObject(newValue)) {
|
||||
result[key] = mergeDefaultFunctionInputAndFunctionInput({
|
||||
newInput: newValue,
|
||||
oldInput: isObject(oldValue) ? oldValue : {},
|
||||
});
|
||||
} else {
|
||||
result[key] = oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
import { getDefaultFunctionInputFromInputSchema } from '@/logic-functions/utils/getDefaultFunctionInputFromInputSchema';
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
import { getFunctionInputFromInputSchema } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getFunctionInputFromInputSchema';
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
import { useGetOneLogicFunction } from '@/logic-functions/hooks/useGetOneLogicFunction';
|
||||
import { type WorkflowLogicFunctionAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
@@ -56,7 +56,7 @@ export const WorkflowEditActionLogicFunction = ({
|
||||
? toolInputSchema
|
||||
: [toolInputSchema];
|
||||
|
||||
const defaultInput = getDefaultFunctionInputFromInputSchema(schemaArray)[0];
|
||||
const defaultInput = getFunctionInputFromInputSchema(schemaArray)[0];
|
||||
|
||||
if (!isObject(defaultInput)) {
|
||||
return action.settings.input.logicFunctionInput ?? {};
|
||||
|
||||
Reference in New Issue
Block a user