From ac5fab073de2a39569111b737d5acd427599ed10 Mon Sep 17 00:00:00 2001 From: Dennis Tismenko Date: Wed, 17 Sep 2025 16:08:56 -0400 Subject: [PATCH 1/3] [Agent Builder] Support optional ES|QL params, testing flyout UX improvements --- .../onechat/onechat-common/tools/esql.ts | 4 + .../agents/edit/tabs/settings_tab.tsx | 8 +- .../components/tools/execute/test_tools.tsx | 224 ++++++++++----- .../form/components/esql/esql_param_row.tsx | 18 ++ .../form/components/esql/esql_params.tsx | 7 + .../application/components/tools/form/i18n.ts | 3 + .../tools/form/types/tool_form_types.ts | 1 + .../validation/esql_tool_form_validation.ts | 1 + .../application/components/tools/tool.tsx | 264 +++++++++--------- .../hooks/tools/use_execute_tools.ts | 11 +- .../onechat/public/application/utils/i18n.ts | 19 +- .../utils/transform_esql_form_data.test.ts | 9 + .../utils/transform_esql_form_data.ts | 20 +- .../persisted/tool_types/esql/schemas.ts | 1 + .../tool_types/esql/to_tool_definition.ts | 8 +- 15 files changed, 374 insertions(+), 224 deletions(-) diff --git a/x-pack/platform/packages/shared/onechat/onechat-common/tools/esql.ts b/x-pack/platform/packages/shared/onechat/onechat-common/tools/esql.ts index 933744cbe1702..c779c1140d2db 100644 --- a/x-pack/platform/packages/shared/onechat/onechat-common/tools/esql.ts +++ b/x-pack/platform/packages/shared/onechat/onechat-common/tools/esql.ts @@ -34,6 +34,10 @@ export interface EsqlToolParam { * Description of the parameter's purpose or expected values. */ description: string; + /** + * Whether the parameter is optional. + */ + optional?: boolean; } // To make compatible with ToolDefinition['configuration'] diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/agents/edit/tabs/settings_tab.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/agents/edit/tabs/settings_tab.tsx index 36c74a709178b..1dfaf199879e1 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/agents/edit/tabs/settings_tab.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/agents/edit/tabs/settings_tab.tsx @@ -150,7 +150,7 @@ export const AgentSettingsTab: React.FC = ({ })} labelAppend={ - {labels.agents.settings.optionalLabel} + {labels.common.optional} } isInvalid={!!formState.errors.configuration?.instructions} @@ -212,7 +212,7 @@ export const AgentSettingsTab: React.FC = ({ })} labelAppend={ - {labels.agents.settings.optionalLabel} + {labels.common.optional} } isInvalid={!!formState.errors.labels} @@ -379,7 +379,7 @@ export const AgentSettingsTab: React.FC = ({ })} labelAppend={ - {labels.agents.settings.optionalLabel} + {labels.common.optional} } isInvalid={!!formState.errors.avatar_color} @@ -409,7 +409,7 @@ export const AgentSettingsTab: React.FC = ({ })} labelAppend={ - {labels.agents.settings.optionalLabel} + {labels.common.optional} } isInvalid={!!formState.errors.avatar_symbol} diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx index 7947f20f0702a..afe8325b75ff0 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx @@ -7,37 +7,37 @@ import { EuiButton, - EuiFieldText, + EuiButtonEmpty, + EuiCodeBlock, EuiFieldNumber, - EuiSwitch, + EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiFlyoutBody, + EuiFlyoutFooter, EuiFlyoutHeader, EuiForm, EuiFormRow, + EuiLink, + EuiLoadingSpinner, EuiSpacer, + EuiSwitch, + EuiText, EuiTitle, - EuiCodeBlock, - EuiLoadingSpinner, + useIsWithinBreakpoints, } from '@elastic/eui'; import { css } from '@emotion/react'; -import React, { useState } from 'react'; -import { useForm, FormProvider, Controller, type Control } from 'react-hook-form'; -import type { ToolDefinitionWithSchema } from '@kbn/onechat-common'; import { i18n } from '@kbn/i18n'; import { formatOnechatErrorMessage } from '@kbn/onechat-browser'; -import { useExecuteTool } from '../../../hooks/tools/use_execute_tools'; +import type { ToolDefinitionWithSchema } from '@kbn/onechat-common'; +import React, { useState } from 'react'; +import { Controller, FormProvider, useForm, type Control } from 'react-hook-form'; import type { ExecuteToolResponse } from '../../../../../common/http_api/tools'; +import { useExecuteTool } from '../../../hooks/tools/use_execute_tools'; import { useTool } from '../../../hooks/tools/use_tools'; - -interface ToolTestFlyoutProps { - isOpen: boolean; - isLoading?: boolean; - toolId: string; - onClose: () => void; -} +import { ToolFormMode } from '../form/tool_form'; +import { labels } from '../../../utils/i18n'; interface ToolParameter { name: string; @@ -45,6 +45,7 @@ interface ToolParameter { description: string; value: string; type: string; + optional: boolean; } enum ToolParameterType { @@ -69,13 +70,12 @@ const getComponentType = (schemaType: string): ToolParameterType => { }; const getParameters = (tool?: ToolDefinitionWithSchema): Array => { - if (!tool || !tool.schema || !tool.schema.properties) return []; + if (!tool?.schema?.properties) return []; - const { properties } = tool.schema; + const { properties, required } = tool.schema; + const requiredParams = new Set(required); - const fields: Array = []; - - Object.entries(properties).forEach(([paramName, paramSchema]) => { + return Object.entries(properties).map(([paramName, paramSchema]) => { let type = 'string'; // default fallback if (paramSchema && 'type' in paramSchema && paramSchema.type) { @@ -86,31 +86,32 @@ const getParameters = (tool?: ToolDefinitionWithSchema): Array => } } - fields.push({ + return { name: paramName, label: paramSchema?.title || paramName, value: '', description: paramSchema?.description || '', type, - }); + optional: !requiredParams.has(paramName), + }; }); - - return fields; }; -const renderFormField = ( - field: ToolParameter, - tool: ToolDefinitionWithSchema, - control: Control> -) => { - const componentType = getComponentType(field.type); - const isRequired = tool?.schema?.required?.includes(field.name); +const renderFormField = ({ + parameter, + control, +}: { + parameter: ToolParameter; + control: Control; +}) => { + const { label, name, optional, type } = parameter; + const componentType = getComponentType(type); const commonProps = { - name: field.name, + name, control, rules: { - required: isRequired ? `${field.label} is required` : false, + required: !optional ? `${parameter.label} is required` : false, }, }; @@ -119,13 +120,14 @@ const renderFormField = ( return ( ( + render={({ field: { onChange, value, ref, ...field } }) => ( onChange(e.target.valueAsNumber || e.target.value)} - placeholder={`Enter ${field.label.toLowerCase()}`} + placeholder={`Enter ${label.toLowerCase()}`} fullWidth /> )} @@ -136,14 +138,13 @@ const renderFormField = ( return ( ( + render={({ field: { onChange, value, ref, ...field } }) => ( onChange(e.target.checked)} - label={field.label} + label={label} /> )} /> @@ -154,12 +155,12 @@ const renderFormField = ( return ( ( + render={({ field: { value, ref, ...field } }) => ( onChange(e.target.value)} - placeholder={`Enter ${field.label.toLowerCase()}`} + {...field} + inputRef={ref} + value={value as string} + placeholder={`Enter ${label.toLowerCase()}`} fullWidth /> )} @@ -168,8 +169,18 @@ const renderFormField = ( } }; -export const ToolTestFlyout: React.FC = ({ toolId, onClose }) => { +export interface ToolTestFlyoutProps { + toolId: string; + onClose: () => void; + formMode?: ToolFormMode; +} + +export const ToolTestFlyout: React.FC = ({ toolId, onClose, formMode }) => { + const isSmallScreen = useIsWithinBreakpoints(['xs', 's', 'm']); const [response, setResponse] = useState('{}'); + // Re-mount new responses, needed for virtualized EuiCodeBlock + // https://github.com/elastic/eui/issues/9034 + const [responseKey, setResponseKey] = useState(0); const form = useForm>({ mode: 'onChange', @@ -177,8 +188,10 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose const { handleSubmit, + control, formState: { errors }, } = form; + const hasErrors = Object.keys(errors).length > 0; const { tool, isLoading } = useTool({ toolId }); @@ -189,6 +202,9 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose onError: (error: Error) => { setResponse(JSON.stringify({ error: formatOnechatErrorMessage(error) }, null, 2)); }, + onSettled: () => { + setResponseKey((key) => key + 1); + }, }); const onSubmit = async (formData: Record) => { @@ -201,9 +217,17 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose if (!tool) return null; return ( - + - +

@@ -213,6 +237,13 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose

+ + + {i18n.translate('xpack.onechat.tools.testFlyout.documentationLink', { + defaultMessage: 'Documentation - Testing tools', + })} + +
@@ -224,12 +255,16 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose ) : ( - + @@ -241,26 +276,56 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose - {getParameters(tool).map((field) => ( - - {renderFormField(field, tool, form.control)} - - ))} - - - {i18n.translate('xpack.onechat.tools.testTool.executeButton', { - defaultMessage: 'Submit', + + {getParameters(tool).map((parameter) => { + const { name, label, description, optional } = parameter; + return ( + + {labels.common.optional} + + ) + } + helpText={description} + isInvalid={!!errors[name]} + error={errors[name]?.message as string} + fullWidth + > + {renderFormField({ + parameter, + control, + })} + + ); })} - + + + + {i18n.translate('xpack.onechat.tools.testTool.executeButton', { + defaultMessage: 'Submit', + })} + + + - +
{i18n.translate('xpack.onechat.tools.testTool.responseTitle', { @@ -270,14 +335,14 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose {response} @@ -286,6 +351,17 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose )} + {formMode === ToolFormMode.Edit && ( + + + {labels.tools.testTool.backToEditToolButton} + + + )} ); }; diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/esql/esql_param_row.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/esql/esql_param_row.tsx index 74e566c691492..9e43db46a6287 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/esql/esql_param_row.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/esql/esql_param_row.tsx @@ -7,6 +7,7 @@ import { EuiButtonIcon, + EuiCheckbox, EuiFieldText, EuiFlexGroup, EuiFlexItem, @@ -240,6 +241,23 @@ export const EsqlParamRow: React.FC = ({ )} /> + + ( + { + onChange(e.target.checked); + }} + checked={value} + {...field} + /> + )} + /> + = ({ onAppend, onReplace description: '', type: ES_FIELD_TYPES.TEXT, source: EsqlParamSource.Inferred, + optional: false, }; } ); @@ -74,6 +75,7 @@ const EsqlParamActions: React.FC = ({ onAppend, onReplace <> = ({ onAppend, onReplace { description: '', type: ES_FIELD_TYPES.TEXT, source: EsqlParamSource.Custom, + optional: false, }); }, [appendParamField] @@ -201,6 +205,9 @@ export const EsqlParams = () => { {i18nMessages.paramTypeLabel} + + {i18nMessages.optionalParamLabel} + diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/i18n.ts b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/i18n.ts index 98455e2bbba29..d910dfe9e74d8 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/i18n.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/i18n.ts @@ -31,6 +31,9 @@ export const i18nMessages = { paramTypeLabel: i18n.translate('xpack.onechat.tools.newTool.paramTypeLabel', { defaultMessage: 'Type', }), + optionalParamLabel: i18n.translate('xpack.onechat.tools.newTool.optionalParamLabel', { + defaultMessage: 'Optional?', + }), removeParamButtonLabel: i18n.translate('xpack.onechat.tools.newTool.removeParamButtonLabel', { defaultMessage: 'Remove parameter', }), diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/types/tool_form_types.ts b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/types/tool_form_types.ts index 2f83465610f14..23e3d9c3c78b7 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/types/tool_form_types.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/types/tool_form_types.ts @@ -11,6 +11,7 @@ export interface EsqlParam { name: string; type: EsqlToolFieldTypes; description: string; + optional: boolean; } export enum EsqlParamSource { diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/esql_tool_form_validation.ts b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/esql_tool_form_validation.ts index b418e6824b66c..9da78b15cf4c4 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/esql_tool_form_validation.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/esql_tool_form_validation.ts @@ -86,6 +86,7 @@ export const esqlFormValidationSchema = z Object.values(EsqlToolFieldType).includes(data) ), source: z.nativeEnum(EsqlParamSource), + optional: z.boolean(), }) ) .superRefine((params, ctx) => { diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/tool.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/tool.tsx index 78c1568bc1b52..9ffa0c6d4e45e 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/tool.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/tool.tsx @@ -21,13 +21,13 @@ import { useIsWithinBreakpoints, } from '@elastic/eui'; import { css } from '@emotion/react'; -import { i18n } from '@kbn/i18n'; import type { ToolDefinitionWithSchema, ToolType } from '@kbn/onechat-common'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt'; import { defer } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { FormProvider } from 'react-hook-form'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { docLinks } from '../../../../common/doc_links'; import type { @@ -53,6 +53,7 @@ import { ToolTestFlyout } from './execute/test_tools'; import { ToolEditContextMenu } from './form/components/tool_edit_context_menu'; import { ToolForm, ToolFormMode } from './form/tool_form'; import type { ToolFormData } from './form/types/tool_form_types'; +import { useFlyoutState } from '../../hooks/use_flyout_state'; const BUTTON_IDS = { SAVE: 'save', @@ -111,8 +112,12 @@ export const Tool: React.FC = ({ mode, tool, isLoading, isSubmitting, const form = useToolForm(tool, initialToolType); const { reset, formState, watch, handleSubmit, getValues } = form; const { errors, isDirty, isSubmitSuccessful } = formState; - const [showTestFlyout, setShowTestFlyout] = useState(false); const [isCancelling, setIsCancelling] = useState(false); + const { + isOpen: showTestFlyout, + openFlyout: openTestFlyout, + closeFlyout: closeTestFlyout, + } = useFlyoutState(false); const [submittingButtonId, setSubmittingButtonId] = useState(); const { services } = useKibana(); const { @@ -127,10 +132,10 @@ export const Tool: React.FC = ({ mode, tool, isLoading, isSubmitting, // Handle opening test tool flyout on navigation useEffect(() => { if (openTestFlyoutParam && currentToolId && !showTestFlyout) { - setShowTestFlyout(true); + openTestFlyout(); setOpenTestFlyoutParam(false); } - }, [openTestFlyoutParam, currentToolId, showTestFlyout, setOpenTestFlyoutParam]); + }, [openTestFlyoutParam, currentToolId, showTestFlyout, setOpenTestFlyoutParam, openTestFlyout]); const handleCancel = useCallback(() => { setIsCancelling(true); @@ -166,8 +171,8 @@ export const Tool: React.FC = ({ mode, tool, isLoading, isSubmitting, ); const handleTestTool = useCallback(() => { - setShowTestFlyout(true); - }, []); + openTestFlyout(); + }, [openTestFlyout]); const handleSaveAndTest = useCallback( async (data: ToolFormData) => { @@ -222,7 +227,7 @@ export const Tool: React.FC = ({ mode, tool, isLoading, isSubmitting, form={toolFormId} disabled={hasErrors || isSubmitting || (mode === ToolFormMode.Edit && !isDirty)} isLoading={submittingButtonId === BUTTON_IDS.SAVE} - minWidth={mode === ToolFormMode.Create ? '124px' : '112px'} + minWidth="112px" > {labels.tools.saveButtonLabel} @@ -246,22 +251,18 @@ export const Tool: React.FC = ({ mode, tool, isLoading, isSubmitting, iconType: 'play', isDisabled: hasErrors || isSubmitting, }; - return isCreateMode ? ( + return isCreateMode || isDirty ? ( - {i18n.translate('xpack.onechat.tools.esqlToolFlyout.saveAndTestButtonLabel', { - defaultMessage: 'Save & test', - })} + {labels.tools.saveAndTestButtonLabel} ) : ( - {i18n.translate('xpack.onechat.tools.esqlToolFlyout.testButtonLabel', { - defaultMessage: 'Test', - })} + {labels.tools.testButtonLabel} ); }, @@ -273,6 +274,7 @@ export const Tool: React.FC = ({ mode, tool, isLoading, isSubmitting, hasErrors, isSubmitting, submittingButtonId, + isDirty, ] ); @@ -286,131 +288,129 @@ export const Tool: React.FC = ({ mode, tool, isLoading, isSubmitting, }); return ( - - - - - {[ToolFormMode.View, ToolFormMode.Edit].includes(mode) - ? tool?.id - : labels.tools.newToolTitle} - - {tool?.readonly && ( + <> + + + - - {labels.tools.readOnly} - + {[ToolFormMode.View, ToolFormMode.Edit].includes(mode) + ? tool?.id + : labels.tools.newToolTitle} - )} - - } - description={ - mode === ToolFormMode.Create ? ( - - {i18n.translate('xpack.onechat.tools.createToolDocumentation', { - defaultMessage: 'Learn more', - })} - - ), - }} - /> - ) : undefined - } - rightSideItems={[ - ...(mode !== ToolFormMode.View ? [renderSaveButton({ size: 'm' })] : []), - renderTestButton({ size: 'm' }), - ...(mode === ToolFormMode.Edit ? [] : []), - ]} - rightSideGroupProps={{ gutterSize: 's' }} - css={css` - background-color: ${euiTheme.colors.backgroundBasePlain}; - border-block-end: none; - `} - /> - - {isLoading ? ( - - - - ) : ( - <> - {isViewMode ? ( - - ) : ( - - )} - {showTestFlyout && currentToolId && ( - { - setShowTestFlyout(false); - if (mode === ToolFormMode.Create) { - navigateToOnechatUrl(appPaths.tools.list); - } + {tool?.readonly && ( + + + {labels.tools.readOnly} + + + )} + + } + description={ + mode === ToolFormMode.Create ? ( + + {i18n.translate('xpack.onechat.tools.createToolDocumentation', { + defaultMessage: 'Learn more', + })} + + ), }} /> - )} - - )} - ] : []), + ]} + rightSideGroupProps={{ gutterSize: 's' }} css={css` - height: ${!isMobile || isViewMode ? euiTheme.size.xxxxl : '144px'}; + background-color: ${euiTheme.colors.backgroundBasePlain}; + border-block-end: none; `} /> - - - - {mode !== ToolFormMode.View && ( - - - {labels.tools.cancelButtonLabel} - - - )} - {renderTestButton()} - {mode !== ToolFormMode.View && ( - {renderSaveButton()} + + {isLoading ? ( + + + + ) : ( + <> + {isViewMode ? ( + + ) : ( + + )} + + )} - - - - + + + + {mode !== ToolFormMode.View && ( + + + {labels.tools.cancelButtonLabel} + + + )} + {renderTestButton()} + {mode !== ToolFormMode.View && ( + {renderSaveButton()} + )} + + + + + {showTestFlyout && currentToolId && ( + { + closeTestFlyout(); + }} + /> + )} + ); }; diff --git a/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_execute_tools.ts b/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_execute_tools.ts index e07d6390bf60f..ee190e9c1bd1d 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_execute_tools.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_execute_tools.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { ExecuteToolResponse } from '../../../../common/http_api/tools'; import { useOnechatServices } from '../use_onechat_service'; @@ -14,15 +15,20 @@ export interface ExecuteToolParams { toolParams: Record; } -export type ExecuteToolSuccessCallback = (data: ExecuteToolResponse) => void; -export type ExecuteToolErrorCallback = (error: Error) => void; +type ExecuteToolMutationOptions = UseMutationOptions; + +export type ExecuteToolSuccessCallback = NonNullable; +export type ExecuteToolErrorCallback = NonNullable; +export type ExecuteToolSettledCallback = NonNullable; export const useExecuteTool = ({ onSuccess, onError, + onSettled, }: { onSuccess?: ExecuteToolSuccessCallback; onError?: ExecuteToolErrorCallback; + onSettled?: ExecuteToolSettledCallback; } = {}) => { const { toolsService } = useOnechatServices(); @@ -36,6 +42,7 @@ export const useExecuteTool = ({ >(mutationFn, { onSuccess, onError, + onSettled, }); return { diff --git a/x-pack/platform/plugins/shared/onechat/public/application/utils/i18n.ts b/x-pack/platform/plugins/shared/onechat/public/application/utils/i18n.ts index 3be5b735f992b..65992e3c159ab 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/utils/i18n.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/utils/i18n.ts @@ -8,6 +8,11 @@ import { i18n } from '@kbn/i18n'; export const labels = { + common: { + optional: i18n.translate('xpack.onechat.common.optional', { + defaultMessage: 'Optional', + }), + }, conversations: { title: i18n.translate('xpack.onechat.conversations.title', { defaultMessage: 'Agent Chat' }), manageAgents: i18n.translate('xpack.onechat.agents.manageAgents', { @@ -48,6 +53,12 @@ export const labels = { saveButtonLabel: i18n.translate('xpack.onechat.tools.saveButtonLabel', { defaultMessage: 'Save', }), + testButtonLabel: i18n.translate('xpack.onechat.tools.testButtonLabel', { + defaultMessage: 'Test', + }), + saveAndTestButtonLabel: i18n.translate('xpack.onechat.tools.saveAndTestButtonLabel', { + defaultMessage: 'Save & test', + }), cancelButtonLabel: i18n.translate('xpack.onechat.tools.cancelButtonLabel', { defaultMessage: 'Cancel', }), @@ -225,6 +236,11 @@ export const labels = { defaultMessage: "You can't recover deleted data.", } ), + testTool: { + backToEditToolButton: i18n.translate('xpack.onechat.tools.testTool.backToEditToolButton', { + defaultMessage: 'Back to edit tool', + }), + }, }, agents: { title: i18n.translate('xpack.onechat.agents.list.title', { defaultMessage: 'Agents' }), @@ -234,9 +250,6 @@ export const labels = { defaultMessage: 'Create Agent', }), settings: { - optionalLabel: i18n.translate('xpack.onechat.agents.form.settings.optionalLabel', { - defaultMessage: 'Optional', - }), cancelButtonLabel: i18n.translate('xpack.onechat.agents.form.settings.cancelButtonLabel', { defaultMessage: 'Cancel', }), diff --git a/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.test.ts b/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.test.ts index 0c804b6a36d18..0e7e40a782f6f 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.test.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.test.ts @@ -35,12 +35,14 @@ describe('transformEsqlFormData', () => { type: 'text', description: 'A string parameter.', source: EsqlParamSource.Custom, + optional: false, }, { name: 'param2', type: 'long', description: 'A number parameter.', source: EsqlParamSource.Custom, + optional: false, }, ], labels: ['test', 'esql'], @@ -57,10 +59,12 @@ describe('transformEsqlFormData', () => { param1: { type: 'text', description: 'A string parameter.', + optional: false, }, param2: { type: 'long', description: 'A number parameter.', + optional: false, }, }, }, @@ -89,6 +93,7 @@ describe('transformEsqlFormData', () => { description: 'An unused parameter.', type: 'text', source: EsqlParamSource.Custom, + optional: false, }); const expectedTool = { ...mockTool }; @@ -97,6 +102,7 @@ describe('transformEsqlFormData', () => { param1: { description: 'A string parameter.', type: 'text', + optional: false, }, }; @@ -150,12 +156,14 @@ describe('transformEsqlFormData', () => { type: 'text', description: 'A string parameter.', source: EsqlParamSource.Custom, + optional: false, }, { name: 'param2', type: 'long', description: 'A number parameter.', source: EsqlParamSource.Custom, + optional: false, }, ], labels: ['test', 'esql'], @@ -171,6 +179,7 @@ describe('transformEsqlFormData', () => { param1: { type: 'text', description: 'A string parameter.', + optional: false, }, }, }, diff --git a/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.ts b/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.ts index 7d2b82b71cf4a..510533597c507 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/utils/transform_esql_form_data.ts @@ -6,7 +6,7 @@ */ import { getESQLQueryVariables } from '@kbn/esql-utils'; -import type { EsqlToolDefinition, EsqlToolFieldTypes } from '@kbn/onechat-common'; +import type { EsqlToolDefinition, EsqlToolParam } from '@kbn/onechat-common'; import { ToolType } from '@kbn/onechat-common'; import { omit } from 'lodash'; import type { CreateToolPayload, UpdateToolPayload } from '../../../common/http_api/tools'; @@ -26,12 +26,15 @@ export const transformEsqlToolToFormData = (tool: EsqlToolDefinition): EsqlToolF description: tool.description, esql: tool.configuration.query, labels: tool.tags, - params: Object.entries(tool.configuration.params).map(([name, { type, description }]) => ({ - name, - type, - description, - source: EsqlParamSource.Custom, - })), + params: Object.entries(tool.configuration.params).map( + ([name, { type, description, optional }]) => ({ + name, + type, + description, + source: EsqlParamSource.Custom, + optional: optional ?? false, + }) + ), type: ToolType.esql, }; }; @@ -55,9 +58,10 @@ export const transformFormDataToEsqlTool = (data: EsqlToolFormData): EsqlToolDef paramsMap[param.name] = { type: param.type, description: param.description, + optional: param.optional, }; return paramsMap; - }, {} as Record), + }, {} as Record), }, type: ToolType.esql, tags: data.labels, diff --git a/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/schemas.ts b/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/schemas.ts index d6b005b15ef23..a47810d38e7ad 100644 --- a/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/schemas.ts +++ b/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/schemas.ts @@ -23,6 +23,7 @@ export const paramValueTypeSchema = schema.oneOf([ export const paramSchema = schema.object({ type: paramValueTypeSchema, description: schema.string(), + optional: schema.maybe(schema.boolean()), }); export const configurationSchema = schema.object({ diff --git a/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/to_tool_definition.ts b/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/to_tool_definition.ts index 27f56de5dfd43..7cc0adf81cfc3 100644 --- a/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/to_tool_definition.ts +++ b/x-pack/platform/plugins/shared/onechat/server/services/tools/persisted/tool_types/esql/to_tool_definition.ts @@ -29,7 +29,9 @@ export function toToolDefinition = z.ZodObject< schema: createSchemaFromParams(configuration.params) as TSchema, handler: async (params, { esClient }) => { const client = esClient.asCurrentUser; - const paramArray = Object.entries(params).map(([key, value]) => ({ [key]: value })); + const paramArray = Object.keys(configuration.params).map((param) => ({ + [param]: params[param] ?? null, + })); const result = await client.esql.query({ query: configuration.query, @@ -96,6 +98,10 @@ function createSchemaFromParams(params: EsqlToolConfig['params']): z.ZodObject Date: Fri, 19 Sep 2025 14:00:33 -0400 Subject: [PATCH 2/3] fix: i18n, styling, optional field label rename --- .../components/tools/execute/test_tools.tsx | 100 ++++++++++-------- .../application/components/tools/form/i18n.ts | 2 +- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx index afe8325b75ff0..2782eeb145792 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx @@ -39,6 +39,49 @@ import { useTool } from '../../../hooks/tools/use_tools'; import { ToolFormMode } from '../form/tool_form'; import { labels } from '../../../utils/i18n'; +const flyoutStyles = css` + .euiFlyoutBody__overflowContent { + height: 100%; + } +`; + +const flyoutContentStyles = css` + height: 100%; +`; + +const inputsColumnStyles = css` + overflow-y: auto; +`; + +const submitButtonContainerStyles = css` + align-self: flex-end; +`; + +const i18nMessages = { + fieldRequiredError: (label: string) => + i18n.translate('xpack.onechat.tools.testTool.fieldRequiredError', { + defaultMessage: '{label} is required', + values: { label }, + }), + inputPlaceholder: (label: string) => + i18n.translate('xpack.onechat.tools.testTool.inputPlaceholder', { + defaultMessage: 'Enter {label}', + values: { label }, + }), + title: i18n.translate('xpack.onechat.tools.testFlyout.title', { + defaultMessage: 'Test Tool', + }), + inputsTitle: i18n.translate('xpack.onechat.tools.testTool.inputsTitle', { + defaultMessage: 'Inputs', + }), + executeButton: i18n.translate('xpack.onechat.tools.testTool.executeButton', { + defaultMessage: 'Submit', + }), + responseTitle: i18n.translate('xpack.onechat.tools.testTool.responseTitle', { + defaultMessage: 'Response', + }), +}; + interface ToolParameter { name: string; label: string; @@ -111,7 +154,7 @@ const renderFormField = ({ name, control, rules: { - required: !optional ? `${parameter.label} is required` : false, + required: !optional ? i18nMessages.fieldRequiredError(parameter.label) : false, }, }; @@ -127,7 +170,7 @@ const renderFormField = ({ value={(value as number) ?? ''} type="number" onChange={(e) => onChange(e.target.valueAsNumber || e.target.value)} - placeholder={`Enter ${label.toLowerCase()}`} + placeholder={i18nMessages.inputPlaceholder(label)} fullWidth /> )} @@ -160,7 +203,7 @@ const renderFormField = ({ {...field} inputRef={ref} value={value as string} - placeholder={`Enter ${label.toLowerCase()}`} + placeholder={i18nMessages.inputPlaceholder(label)} fullWidth /> )} @@ -217,24 +260,12 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose, if (!tool) return null; return ( - + -

- {i18n.translate('xpack.onechat.tools.testFlyout.title', { - defaultMessage: 'Test Tool', - })} -

+

{i18nMessages.title}

@@ -258,21 +289,11 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose, - + -
- {i18n.translate('xpack.onechat.tools.testTool.inputsTitle', { - defaultMessage: 'Inputs', - })} -
+
{i18nMessages.inputsTitle}
@@ -303,23 +324,14 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose, ); })} - + - {i18n.translate('xpack.onechat.tools.testTool.executeButton', { - defaultMessage: 'Submit', - })} + {i18nMessages.executeButton}
@@ -327,11 +339,7 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose,
-
- {i18n.translate('xpack.onechat.tools.testTool.responseTitle', { - defaultMessage: 'Response', - })} -
+
{i18nMessages.responseTitle}
Date: Sat, 20 Sep 2025 12:38:48 -0400 Subject: [PATCH 3/3] fix: add documentation link --- .../public/application/components/tools/execute/test_tools.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx index 2782eeb145792..275915e872a4d 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/execute/test_tools.tsx @@ -33,6 +33,7 @@ import { formatOnechatErrorMessage } from '@kbn/onechat-browser'; import type { ToolDefinitionWithSchema } from '@kbn/onechat-common'; import React, { useState } from 'react'; import { Controller, FormProvider, useForm, type Control } from 'react-hook-form'; +import { docLinks } from '../../../../../common/doc_links'; import type { ExecuteToolResponse } from '../../../../../common/http_api/tools'; import { useExecuteTool } from '../../../hooks/tools/use_execute_tools'; import { useTool } from '../../../hooks/tools/use_tools'; @@ -269,7 +270,7 @@ export const ToolTestFlyout: React.FC = ({ toolId, onClose,
- + {i18n.translate('xpack.onechat.tools.testFlyout.documentationLink', { defaultMessage: 'Documentation - Testing tools', })}