mirror of
https://github.com/twentyhq/twenty.git
synced 2026-06-12 01:46:39 -04:00
Use workflow inputSchema to render boolean, number, and enum fields in code/logic function steps (#20439)
<img width="415" height="772" alt="Screenshot 2026-05-11 at 4 44 08 PM" src="https://github.com/user-attachments/assets/32dbdd3c-e60b-4c43-90bc-18be05f22dcf" /> <img width="414" height="371" alt="Screenshot 2026-05-11 at 4 48 24 PM" src="https://github.com/user-attachments/assets/83be062c-7ed3-4953-98bb-e4290865040b" />
This commit is contained in:
@@ -8,10 +8,8 @@ import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/wo
|
||||
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue';
|
||||
|
||||
import { WorkflowStepCmdEnterButton } from '@/workflow/workflow-steps/components/WorkflowStepCmdEnterButton';
|
||||
import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
|
||||
import { LogicFunctionLogs } from '@/logic-functions/components/LogicFunctionLogs';
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
@@ -19,15 +17,19 @@ import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotke
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowStepCmdEnterButton } from '@/workflow/workflow-steps/components/WorkflowStepCmdEnterButton';
|
||||
import { WorkflowCodeEditor } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowCodeEditor';
|
||||
import { WorkflowEditActionCodeFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields';
|
||||
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
|
||||
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
|
||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers';
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { LogicFunctionTestInputInitEffect } from '@/logic-functions/components/LogicFunctionTestInputInitEffect';
|
||||
import { useExecuteLogicFunction } from '@/logic-functions/hooks/useExecuteLogicFunction';
|
||||
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';
|
||||
@@ -35,20 +37,18 @@ import { type editor } from 'monaco-editor';
|
||||
import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||
import { useState } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
getOutputSchemaFromValue,
|
||||
jsonSchemaToInputSchema,
|
||||
type InputJsonSchema,
|
||||
} from 'twenty-shared/logic-function';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getFunctionInputFromInputSchema } from 'twenty-shared/workflow';
|
||||
import { IconCode, IconPlayerPlay } from 'twenty-ui/display';
|
||||
import { CodeEditor } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { getFunctionInputFromInputSchema } from 'twenty-shared/workflow';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { LogicFunctionTestInputInitEffect } from '@/logic-functions/components/LogicFunctionTestInputInitEffect';
|
||||
import { useExecuteLogicFunction } from '@/logic-functions/hooks/useExecuteLogicFunction';
|
||||
|
||||
const StyledCodeEditorContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -341,6 +341,7 @@ export const WorkflowEditActionCode = ({
|
||||
<div data-globally-prevent-click-outside="true">
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={functionInput}
|
||||
inputSchema={formValues.workflowActionTriggerSettings?.inputSchema}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
onInputChange={handleInputChange}
|
||||
readonly={actionOptions.readonly}
|
||||
@@ -381,6 +382,9 @@ export const WorkflowEditActionCode = ({
|
||||
<>
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={functionInput}
|
||||
inputSchema={
|
||||
formValues.workflowActionTriggerSettings?.inputSchema
|
||||
}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
onInputChange={handleInputChange}
|
||||
readonly={actionOptions.readonly}
|
||||
@@ -406,6 +410,9 @@ export const WorkflowEditActionCode = ({
|
||||
<>
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={logicFunctionTestData.input}
|
||||
inputSchema={
|
||||
formValues.workflowActionTriggerSettings?.inputSchema
|
||||
}
|
||||
onInputChange={handleTestInputChange}
|
||||
readonly={actionOptions.readonly}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { FormBooleanFieldInput } from '@/object-record/record-field/ui/form-types/components/FormBooleanFieldInput';
|
||||
import { FormNestedFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormNestedFieldInputContainer';
|
||||
import { FormNumberFieldInput } from '@/object-record/record-field/ui/form-types/components/FormNumberFieldInput';
|
||||
import { FormSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormSelectFieldInput';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
|
||||
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { type FunctionInput } from 'twenty-shared/workflow';
|
||||
import { getInputSchemaPropertyAtPath } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getInputSchemaPropertyAtPath';
|
||||
import { getWorkflowCodeFieldsEnumSelectOptions } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsEnumSelectOptions';
|
||||
import { getWorkflowCodeFieldsLeafKind } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsLeafKind';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isObject } from '@sniptt/guards';
|
||||
import { isNonEmptyArray, isObject } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type FunctionInput, type InputSchema } from 'twenty-shared/workflow';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledContainer = styled.div<{ fullWidth?: boolean }>`
|
||||
@@ -26,6 +33,7 @@ type WorkflowEditActionCodeFieldsProps = {
|
||||
onInputChange?: (value: any, path: string[]) => void | Promise<void>;
|
||||
VariablePicker?: VariablePickerComponent;
|
||||
fullWidth?: boolean;
|
||||
inputSchema?: InputSchema;
|
||||
};
|
||||
|
||||
export const WorkflowEditActionCodeFields = ({
|
||||
@@ -35,6 +43,7 @@ export const WorkflowEditActionCodeFields = ({
|
||||
onInputChange,
|
||||
VariablePicker,
|
||||
fullWidth,
|
||||
inputSchema,
|
||||
}: WorkflowEditActionCodeFieldsProps) => {
|
||||
return (
|
||||
<StyledContainer fullWidth={fullWidth}>
|
||||
@@ -54,12 +63,84 @@ export const WorkflowEditActionCodeFields = ({
|
||||
onInputChange={onInputChange}
|
||||
VariablePicker={VariablePicker}
|
||||
fullWidth={fullWidth}
|
||||
inputSchema={inputSchema}
|
||||
/>
|
||||
</FormNestedFieldInputContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const schemaProperty = getInputSchemaPropertyAtPath(
|
||||
inputSchema,
|
||||
currentPath,
|
||||
);
|
||||
const leafKind = getWorkflowCodeFieldsLeafKind(schemaProperty);
|
||||
|
||||
if (leafKind === 'boolean') {
|
||||
return (
|
||||
<FormBooleanFieldInput
|
||||
key={pathKey}
|
||||
label={inputKey}
|
||||
defaultValue={
|
||||
!isDefined(inputValue)
|
||||
? undefined
|
||||
: typeof inputValue === 'boolean' ||
|
||||
typeof inputValue === 'string'
|
||||
? inputValue
|
||||
: undefined
|
||||
}
|
||||
readonly={readonly}
|
||||
onChange={(value) => onInputChange?.(value, currentPath)}
|
||||
VariablePicker={VariablePicker}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (leafKind === 'number') {
|
||||
return (
|
||||
<FormNumberFieldInput
|
||||
key={pathKey}
|
||||
label={inputKey}
|
||||
defaultValue={
|
||||
!isDefined(inputValue)
|
||||
? undefined
|
||||
: typeof inputValue === 'number' ||
|
||||
typeof inputValue === 'string'
|
||||
? inputValue
|
||||
: undefined
|
||||
}
|
||||
readonly={readonly}
|
||||
onChange={(value) => onInputChange?.(value, currentPath)}
|
||||
VariablePicker={VariablePicker}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (leafKind === 'enum' && isDefined(schemaProperty)) {
|
||||
const enumOptions =
|
||||
getWorkflowCodeFieldsEnumSelectOptions(schemaProperty);
|
||||
|
||||
if (isNonEmptyArray(enumOptions)) {
|
||||
return (
|
||||
<FormSelectFieldInput
|
||||
key={pathKey}
|
||||
label={inputKey}
|
||||
defaultValue={
|
||||
!isDefined(inputValue)
|
||||
? undefined
|
||||
: typeof inputValue === 'string'
|
||||
? inputValue
|
||||
: String(inputValue)
|
||||
}
|
||||
readonly={readonly}
|
||||
onChange={(value) => onInputChange?.(value, currentPath)}
|
||||
VariablePicker={VariablePicker}
|
||||
options={enumOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FormTextFieldInput
|
||||
key={pathKey}
|
||||
@@ -69,6 +150,7 @@ export const WorkflowEditActionCodeFields = ({
|
||||
readonly={readonly}
|
||||
onChange={(value) => onInputChange?.(value, currentPath)}
|
||||
VariablePicker={VariablePicker}
|
||||
multiline={schemaProperty?.multiline === true}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useGetAvailablePackages } from '@/logic-functions/hooks/useGetAvailablePackages';
|
||||
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
import { useGetLogicFunctionSourceCode } from '@/logic-functions/hooks/useGetLogicFunctionSourceCode';
|
||||
import { useGetOneLogicFunction } from '@/logic-functions/hooks/useGetOneLogicFunction';
|
||||
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowEditActionCodeFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields';
|
||||
@@ -29,6 +30,10 @@ export const WorkflowReadonlyActionCode = ({
|
||||
id: logicFunctionId,
|
||||
});
|
||||
|
||||
const { logicFunction } = useGetOneLogicFunction({
|
||||
id: logicFunctionId,
|
||||
});
|
||||
|
||||
const { sourceHandlerCode, loading } = useGetLogicFunctionSourceCode({
|
||||
logicFunctionId,
|
||||
});
|
||||
@@ -55,6 +60,9 @@ export const WorkflowReadonlyActionCode = ({
|
||||
<WorkflowStepBody>
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={action.settings.input.logicFunctionInput}
|
||||
inputSchema={
|
||||
logicFunction?.workflowActionTriggerSettings?.inputSchema
|
||||
}
|
||||
readonly
|
||||
/>
|
||||
<StyledCodeEditorContainer>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { type InputSchema } from 'twenty-shared/workflow';
|
||||
|
||||
import { getInputSchemaPropertyAtPath } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getInputSchemaPropertyAtPath';
|
||||
|
||||
describe('getInputSchemaPropertyAtPath', () => {
|
||||
const inputSchema = [
|
||||
{
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
slackChannelId: { type: 'string' as const },
|
||||
messageFormat: {
|
||||
type: 'string' as const,
|
||||
enum: ['plain', 'markdown'],
|
||||
},
|
||||
count: { type: 'number' as const },
|
||||
nested: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
inner: { type: 'string' as const },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
] as InputSchema;
|
||||
|
||||
it('should return property at top-level path', () => {
|
||||
expect(
|
||||
getInputSchemaPropertyAtPath(inputSchema, ['messageFormat']),
|
||||
).toEqual({
|
||||
type: 'string',
|
||||
enum: ['plain', 'markdown'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return property at nested path', () => {
|
||||
expect(
|
||||
getInputSchemaPropertyAtPath(inputSchema, ['nested', 'inner']),
|
||||
).toEqual({
|
||||
type: 'string',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined when path is invalid', () => {
|
||||
expect(
|
||||
getInputSchemaPropertyAtPath(inputSchema, ['missing']),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
getInputSchemaPropertyAtPath(inputSchema, ['slackChannelId', 'tooDeep']),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when inputSchema is missing or empty', () => {
|
||||
expect(getInputSchemaPropertyAtPath(undefined, ['a'])).toBeUndefined();
|
||||
expect(getInputSchemaPropertyAtPath([], ['a'])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { getWorkflowCodeFieldsEnumSelectOptions } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsEnumSelectOptions';
|
||||
|
||||
describe('getWorkflowCodeFieldsEnumSelectOptions', () => {
|
||||
it('should build select options from schema enum', () => {
|
||||
expect(
|
||||
getWorkflowCodeFieldsEnumSelectOptions({
|
||||
type: 'string',
|
||||
enum: ['plain', 'markdown'],
|
||||
}),
|
||||
).toEqual([
|
||||
{ value: 'plain', label: 'plain' },
|
||||
{ value: 'markdown', label: 'markdown' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty array when property has no enum', () => {
|
||||
expect(getWorkflowCodeFieldsEnumSelectOptions({ type: 'string' })).toEqual(
|
||||
[],
|
||||
);
|
||||
expect(getWorkflowCodeFieldsEnumSelectOptions(undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { getWorkflowCodeFieldsLeafKind } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsLeafKind';
|
||||
|
||||
describe('getWorkflowCodeFieldsLeafKind', () => {
|
||||
it('should map schema types to leaf editor kind', () => {
|
||||
expect(getWorkflowCodeFieldsLeafKind({ type: 'boolean' })).toBe('boolean');
|
||||
expect(
|
||||
getWorkflowCodeFieldsLeafKind({ type: FieldMetadataType.BOOLEAN }),
|
||||
).toBe('boolean');
|
||||
expect(getWorkflowCodeFieldsLeafKind({ type: 'number' })).toBe('number');
|
||||
expect(
|
||||
getWorkflowCodeFieldsLeafKind({ type: FieldMetadataType.NUMBER }),
|
||||
).toBe('number');
|
||||
expect(
|
||||
getWorkflowCodeFieldsLeafKind({ type: FieldMetadataType.NUMERIC }),
|
||||
).toBe('number');
|
||||
expect(getWorkflowCodeFieldsLeafKind({ type: 'string' })).toBe('text');
|
||||
expect(
|
||||
getWorkflowCodeFieldsLeafKind({
|
||||
type: 'string',
|
||||
enum: ['plain', 'markdown'],
|
||||
}),
|
||||
).toBe('enum');
|
||||
expect(
|
||||
getWorkflowCodeFieldsLeafKind({
|
||||
type: FieldMetadataType.TEXT,
|
||||
enum: ['a', 'b'],
|
||||
}),
|
||||
).toBe('enum');
|
||||
expect(getWorkflowCodeFieldsLeafKind({ type: 'string', enum: [] })).toBe(
|
||||
'text',
|
||||
);
|
||||
expect(getWorkflowCodeFieldsLeafKind(undefined)).toBe('text');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
type InputSchema,
|
||||
type InputSchemaProperty,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getInputSchemaPropertyAtPath = (
|
||||
inputSchema: InputSchema | undefined,
|
||||
path: string[],
|
||||
): InputSchemaProperty | undefined => {
|
||||
if (!isDefined(inputSchema) || inputSchema.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let current: InputSchemaProperty | undefined = inputSchema[0];
|
||||
|
||||
for (const segment of path) {
|
||||
if (!isDefined(current)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (current.type === 'object' && isDefined(current.properties?.[segment])) {
|
||||
current = current.properties[segment];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type InputSchemaProperty } from 'twenty-shared/workflow';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
|
||||
export const getWorkflowCodeFieldsEnumSelectOptions = (
|
||||
property: InputSchemaProperty | undefined,
|
||||
): SelectOption[] => {
|
||||
if (!isDefined(property) || !isNonEmptyArray(property.enum)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return property.enum.map((value) => ({
|
||||
value,
|
||||
label: value,
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type InputSchemaProperty } from 'twenty-shared/workflow';
|
||||
|
||||
type WorkflowCodeFieldsLeafKind = 'boolean' | 'enum' | 'number' | 'text';
|
||||
|
||||
export const getWorkflowCodeFieldsLeafKind = (
|
||||
property: InputSchemaProperty | undefined,
|
||||
): WorkflowCodeFieldsLeafKind => {
|
||||
if (!isDefined(property)) {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
if (
|
||||
(property.type === 'string' || property.type === FieldMetadataType.TEXT) &&
|
||||
isNonEmptyArray(property.enum)
|
||||
) {
|
||||
return 'enum';
|
||||
}
|
||||
|
||||
switch (property.type) {
|
||||
case 'boolean':
|
||||
case FieldMetadataType.BOOLEAN:
|
||||
return 'boolean';
|
||||
case 'number':
|
||||
case FieldMetadataType.NUMBER:
|
||||
case FieldMetadataType.NUMERIC:
|
||||
return 'number';
|
||||
default:
|
||||
return 'text';
|
||||
}
|
||||
};
|
||||
@@ -213,6 +213,9 @@ export const WorkflowEditActionLogicFunction = ({
|
||||
<>
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={testInput}
|
||||
inputSchema={
|
||||
logicFunction?.workflowActionTriggerSettings?.inputSchema
|
||||
}
|
||||
onInputChange={handleTestInputChange}
|
||||
readonly={actionOptions.readonly}
|
||||
/>
|
||||
@@ -237,6 +240,9 @@ export const WorkflowEditActionLogicFunction = ({
|
||||
{hasInputFields ? (
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={functionInput}
|
||||
inputSchema={
|
||||
logicFunction?.workflowActionTriggerSettings?.inputSchema
|
||||
}
|
||||
readonly={actionOptions.readonly}
|
||||
onInputChange={handleInputChange}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
|
||||
@@ -81,4 +81,31 @@ describe('jsonSchemaToInputSchema', () => {
|
||||
enum: ['a', 'b'],
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves multiline on string properties when true', () => {
|
||||
const result = jsonSchemaToInputSchema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
body: { type: 'string', multiline: true },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result[0].properties?.body).toEqual({
|
||||
type: 'string',
|
||||
multiline: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not set multiline when false or omitted', () => {
|
||||
const result = jsonSchemaToInputSchema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string', multiline: false },
|
||||
other: { type: 'string' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result[0].properties?.title).toEqual({ type: 'string' });
|
||||
expect(result[0].properties?.other).toEqual({ type: 'string' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,4 +15,5 @@ export type InputJsonSchema = {
|
||||
additionalProperties?: boolean | InputJsonSchema;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
multiline?: boolean;
|
||||
};
|
||||
|
||||
@@ -46,6 +46,10 @@ const convertProperty = (jsonSchema: InputJsonSchema): InputSchemaProperty => {
|
||||
);
|
||||
}
|
||||
|
||||
if (jsonSchema.multiline === true) {
|
||||
property.multiline = true;
|
||||
}
|
||||
|
||||
return property;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type LeafType, type NodeType } from '@/workflow';
|
||||
import { type FieldMetadataType } from '@/types';
|
||||
import { type LeafType, type NodeType } from '@/workflow';
|
||||
|
||||
export type InputSchemaPropertyType = LeafType | NodeType | FieldMetadataType;
|
||||
|
||||
@@ -8,6 +8,7 @@ export type InputSchemaProperty = {
|
||||
enum?: string[];
|
||||
items?: InputSchemaProperty;
|
||||
properties?: Properties;
|
||||
multiline?: boolean;
|
||||
};
|
||||
|
||||
type Properties = {
|
||||
|
||||
Reference in New Issue
Block a user