diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/mcp_tools_selection_table.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/mcp_tools_selection_table.tsx index 0b2f17fa6e6b2..92256feeff0c0 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/mcp_tools_selection_table.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/mcp_tools_selection_table.tsx @@ -5,31 +5,25 @@ * 2.0. */ -import type { CriteriaWithPagination, EuiSearchBarOnChangeArgs, UseEuiTheme } from '@elastic/eui'; +import type { CriteriaWithPagination } from '@elastic/eui'; import { type EuiBasicTableColumn, - EuiButtonEmpty, - EuiContextMenuItem, - EuiContextMenuPanel, EuiFlexGroup, EuiFlexItem, EuiHighlight, EuiInMemoryTable, type EuiInMemoryTableProps, EuiPanel, - EuiPopover, - EuiSkeletonLoading, - EuiSkeletonText, EuiText, } from '@elastic/eui'; import { css } from '@emotion/react'; -import { FormattedMessage } from '@kbn/i18n-react'; import type { Tool as McpTool } from '@kbn/mcp-client'; -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import useToggle from 'react-use/lib/useToggle'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { labels } from '../../../utils/i18n'; import { truncateAtSentence } from '../../../utils/truncate_at_sentence'; +import { McpToolsSelectionTableHeader } from './mcp_tools_selection_table_header'; import type { McpToolField } from './types'; +import { useMcpToolsSearch } from './use_mcp_tools_search'; const DEFAULT_PAGE_SIZE = 10; const PAGE_SIZE_OPTIONS = [10, 25, 50]; @@ -42,23 +36,6 @@ const tableContainerStyles = (isDisabled = false) => css` `} `; -const tableHeaderContainerStyles = ({ euiTheme }: UseEuiTheme) => css` - margin-block-start: -${euiTheme.size.s}; -`; - -const tableHeaderStyles = css` - min-height: 24px; -`; - -const tableHeaderSkeletonStyles = css` - display: inline-block; - width: 200px; -`; - -const tableHeaderButtonStyles = ({ euiTheme }: UseEuiTheme) => css` - font-weight: ${euiTheme.font.weight.semiBold}; -`; - export interface McpToolsSelectionTableProps { tools: readonly McpTool[]; selectedTools: McpToolField[]; @@ -78,16 +55,25 @@ export const McpToolsSelectionTable: React.FC = ({ isDisabled, disabledMessage, }) => { - const [searchQuery, setSearchQuery] = useState(''); const [tablePageIndex, setTablePageIndex] = useState(0); const [tablePageSize, setTablePageSize] = useState(DEFAULT_PAGE_SIZE); - const [isSelectionPopoverOpen, toggleSelectionPopover] = useToggle(false); // Track when "select all" is active to prevent the table's internal selection // mechanism from limiting selection to only the visible page items. // Using a ref to avoid stale closure issues in the selection change callback. const isSelectAllActiveRef = useRef(false); + const { + searchConfig, + searchQuery, + results: filteredTools, + } = useMcpToolsSearch({ tools, isDisabled }); + + // Reset page index when filtered results change + useEffect(() => { + setTablePageIndex(0); + }, [filteredTools]); + const selectedMcpTools = useMemo(() => { const selectedNames = new Set(selectedTools.map((tool) => tool.name)); return tools.filter((tool) => selectedNames.has(tool.name)); @@ -127,13 +113,6 @@ export const McpToolsSelectionTable: React.FC = ({ [searchQuery] ); - const paginationStart = Math.min(tablePageIndex * tablePageSize + 1, tools.length); - const paginationEnd = Math.min((tablePageIndex + 1) * tablePageSize, tools.length); - - const handleSearchChange = useCallback(({ queryText }: EuiSearchBarOnChangeArgs) => { - setSearchQuery(queryText); - }, []); - const handleSelectionChange = useCallback( (newSelection: McpTool[]) => { // When "select all" is active, the table fires onSelectionChange twice: @@ -149,19 +128,13 @@ export const McpToolsSelectionTable: React.FC = ({ ); const handleClearSelection = useCallback(() => { - isSelectAllActiveRef.current = false; onChange([]); }, [onChange]); const handleSelectAll = useCallback(() => { isSelectAllActiveRef.current = true; onChange([...tools]); - toggleSelectionPopover(false); - }, [onChange, tools, toggleSelectionPopover]); - - const closeSelectionPopover = useCallback(() => { - toggleSelectionPopover(false); - }, [toggleSelectionPopover]); + }, [onChange, tools]); const selection: EuiInMemoryTableProps['selection'] = useMemo( () => ({ @@ -173,19 +146,6 @@ export const McpToolsSelectionTable: React.FC = ({ [isDisabled, handleSelectionChange, selectedMcpTools] ); - const search: EuiInMemoryTableProps['search'] = useMemo( - () => ({ - onChange: handleSearchChange, - box: { - incremental: true, - placeholder: labels.tools.bulkImportMcp.sourceSection.searchPlaceholder, - disabled: isDisabled || tools.length === 0, - 'data-test-subj': 'bulkImportMcpToolsSearchInput', - }, - }), - [handleSearchChange, isDisabled, tools.length] - ); - const emptyMessage = useMemo(() => { if (isLoading) { return labels.tools.bulkImportMcp.sourceSection.loadingToolsMessage; @@ -193,104 +153,35 @@ export const McpToolsSelectionTable: React.FC = ({ if (isDisabled) { return disabledMessage ?? null; } - if (searchQuery && tools.length > 0) { + if (searchQuery && tools.length > 0 && filteredTools.length === 0) { return labels.tools.bulkImportMcp.sourceSection.noMatchingToolsMessage; } if (tools.length === 0) { return labels.tools.bulkImportMcp.sourceSection.noToolsMessage; } return undefined; - }, [isDisabled, isLoading, searchQuery, tools.length, disabledMessage]); + }, [isDisabled, isLoading, searchQuery, tools.length, filteredTools.length, disabledMessage]); const tableHeader = ( - } - loadedContent={ - tools.length > 0 ? ( - - - - {paginationStart}, - end: {paginationEnd}, - total: tools.length, - }} - /> - - - {selectedMcpTools.length > 0 && ( - - - - {labels.tools.bulkImportMcp.sourceSection.selectedCount( - selectedMcpTools.length - )} - - } - isOpen={isSelectionPopoverOpen} - closePopover={closeSelectionPopover} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - {labels.tools.selectAllToolsButtonLabel} - , - ]} - /> - - - - - {labels.tools.bulkImportMcp.sourceSection.clearSelection} - - - - )} - - ) : null - } + pageIndex={tablePageIndex} + pageSize={tablePageSize} + totalCount={filteredTools.length} + selectedCount={selectedMcpTools.length} + onSelectAll={handleSelectAll} + onClearSelection={handleClearSelection} /> ); return ( ) => { if (page) { setTablePageIndex(page.index); diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/mcp_tools_selection_table_header.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/mcp_tools_selection_table_header.tsx new file mode 100644 index 0000000000000..9f5fd9e3c9003 --- /dev/null +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/mcp_tools_selection_table_header.tsx @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UseEuiTheme } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiSkeletonLoading, + EuiSkeletonText, + EuiText, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { memo, useCallback } from 'react'; +import useToggle from 'react-use/lib/useToggle'; +import { labels } from '../../../utils/i18n'; + +const tableHeaderContainerStyles = ({ euiTheme }: UseEuiTheme) => css` + margin-block-start: -${euiTheme.size.s}; +`; + +const tableHeaderStyles = css` + min-height: 24px; +`; + +const tableHeaderSkeletonStyles = css` + display: inline-block; + width: 200px; +`; + +const tableHeaderButtonStyles = ({ euiTheme }: UseEuiTheme) => css` + font-weight: ${euiTheme.font.weight.semiBold}; +`; + +export interface McpToolsSelectionTableHeaderProps { + isLoading: boolean; + pageIndex: number; + pageSize: number; + totalCount: number; + selectedCount: number; + onSelectAll: () => void; + onClearSelection: () => void; +} + +export const McpToolsSelectionTableHeader = memo( + ({ + isLoading, + pageIndex, + pageSize, + totalCount, + selectedCount, + onSelectAll, + onClearSelection, + }) => { + const [isPopoverOpen, togglePopover] = useToggle(false); + + const closePopover = useCallback(() => { + togglePopover(false); + }, [togglePopover]); + + const handleSelectAll = useCallback(() => { + onSelectAll(); + togglePopover(false); + }, [onSelectAll, togglePopover]); + + const paginationStart = pageIndex * pageSize + 1; + const paginationEnd = Math.min((pageIndex + 1) * pageSize, totalCount); + + return ( + } + loadedContent={ + totalCount > 0 ? ( + + + + {paginationStart}, + end: {paginationEnd}, + total: totalCount, + }} + /> + + + {selectedCount > 0 && ( + + + + {labels.tools.bulkImportMcp.sourceSection.selectedCount(selectedCount)} + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + {labels.tools.selectAllToolsButtonLabel} + , + ]} + /> + + + + + {labels.tools.bulkImportMcp.sourceSection.clearSelection} + + + + )} + + ) : null + } + /> + ); + } +); diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/sections/source.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/sections/source.tsx index d689563f6d45a..ed7880c0a2588 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/sections/source.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/sections/source.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import type { EuiComboBoxOptionOption, UseEuiTheme } from '@elastic/eui'; import { EuiComboBox, EuiFormRow, EuiLink, euiFontSize, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; +import type { ActionConnector } from '@kbn/alerts-ui-shared'; import { CONNECTOR_ID as MCP_CONNECTOR_TYPE } from '@kbn/connector-schemas/mcp/constants'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { useListConnectors, useListMcpTools } from '../../../../hooks/tools/use_mcp_connectors'; @@ -28,11 +29,20 @@ export const SourceSection = () => { const { control, formState, setValue } = useFormContext(); const { errors } = formState; + const handleConnectorCreated = useCallback( + (connector: ActionConnector) => { + setValue('connectorId', connector.id, { shouldValidate: true }); + // Clear tool selection when connector changes + setValue('tools', []); + }, + [setValue] + ); + const { openFlyout: openCreateMcpServerFlyout, isOpen: isCreateMcpServerFlyoutOpen, flyout: createMcpServerFlyout, - } = useAddMcpServerFlyout(); + } = useAddMcpServerFlyout({ onConnectorCreated: handleConnectorCreated }); const { connectors, isLoading: isLoadingConnectors } = useListConnectors({ type: MCP_CONNECTOR_TYPE, diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/use_mcp_tools_search.ts b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/use_mcp_tools_search.ts new file mode 100644 index 0000000000000..8ac5aec0e971a --- /dev/null +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/bulk_import/use_mcp_tools_search.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiSearchBarOnChangeArgs, Search } from '@elastic/eui'; +import { EuiSearchBar } from '@elastic/eui'; +import type { Tool as McpTool } from '@kbn/mcp-client'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { labels } from '../../../utils/i18n'; + +export interface McpToolsSearch { + searchConfig: Search; + searchQuery: string; + results: McpTool[]; +} + +export interface UseMcpToolsSearchOptions { + tools: readonly McpTool[]; + isDisabled?: boolean; +} + +export const useMcpToolsSearch = ({ + tools, + isDisabled = false, +}: UseMcpToolsSearchOptions): McpToolsSearch => { + const [searchQuery, setSearchQuery] = useState(''); + const [results, setResults] = useState([...tools]); + + useEffect(() => { + setResults([...tools]); + }, [tools]); + + const handleChange = useCallback( + ({ query, queryText, error: searchError }: EuiSearchBarOnChangeArgs) => { + if (searchError) { + return; + } + + const newItems = query + ? EuiSearchBar.Query.execute(query, tools as McpTool[], { + defaultFields: ['name', 'description'], + }) + : [...tools]; + + setSearchQuery(queryText); + setResults(newItems); + }, + [tools] + ); + + const searchConfig: Search = useMemo( + () => ({ + onChange: handleChange, + box: { + incremental: true, + placeholder: labels.tools.bulkImportMcp.sourceSection.searchPlaceholder, + disabled: isDisabled || tools.length === 0, + 'data-test-subj': 'bulkImportMcpToolsSearchInput', + }, + }), + [handleChange, isDisabled, tools.length] + ); + + return { + searchConfig, + searchQuery, + results, + }; +}; diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_editable_fields.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_editable_fields.tsx index 48bdcc136d959..bd5a2c92b0da4 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_editable_fields.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_editable_fields.tsx @@ -18,6 +18,7 @@ import { useUpdateEffect, } from '@elastic/eui'; import { css } from '@emotion/react'; +import type { ActionConnector } from '@kbn/alerts-ui-shared'; import { CONNECTOR_ID as MCP_CONNECTOR_TYPE } from '@kbn/connector-schemas/mcp/constants'; import type { Tool } from '@kbn/mcp-client'; import React, { useCallback, useEffect, useMemo } from 'react'; @@ -50,12 +51,6 @@ export const McpEditableFields = ({ const euiThemeContext = useEuiTheme(); const { navigateToOnechatUrl } = useNavigation(); - const { - openFlyout: openCreateMcpServerFlyout, - isOpen: isCreateMcpServerFlyoutOpen, - flyout: createMcpServerFlyout, - } = useAddMcpServerFlyout(); - const { control, formState: { errors }, @@ -63,6 +58,19 @@ export const McpEditableFields = ({ setValue, } = useFormContext(); + const handleConnectorCreated = useCallback( + (connector: ActionConnector) => { + setValue('connectorId', connector.id, { shouldValidate: true }); + }, + [setValue] + ); + + const { + openFlyout: openCreateMcpServerFlyout, + isOpen: isCreateMcpServerFlyoutOpen, + flyout: createMcpServerFlyout, + } = useAddMcpServerFlyout({ onConnectorCreated: handleConnectorCreated }); + const handleBulkImportClick = useCallback(() => { navigateToOnechatUrl(appPaths.tools.bulkImportMcp); }, [navigateToOnechatUrl]); diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_readonly_fields.tsx b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_readonly_fields.tsx index 48325b8faf7b1..2ddd44799b86c 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_readonly_fields.tsx +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/components/mcp/mcp_readonly_fields.tsx @@ -34,12 +34,12 @@ export const McpReadOnlyFields = ({ name: ['connectorId', 'mcpToolName', 'toolId'], }); - const { toolHealth } = useToolHealth({ toolId }); + const { toolHealth, isLoading: isLoadingToolHealth } = useToolHealth({ toolId }); const { connector, isLoading: isLoadingConnector, - isError: isLoadingConnectorError, + failureReason: loadingConnectorError, } = useGetConnector({ connectorId, }); @@ -53,16 +53,16 @@ export const McpReadOnlyFields = ({ const { mcpTools, isLoading: isLoadingMcpTools, - isError: isLoadingMcpToolsError, + failureReason: loadingMcpToolsError, } = useListMcpTools({ connectorId }); useEffect(() => { - if (isLoadingConnector || isLoadingMcpTools) { + if (isLoadingConnector || isLoadingMcpTools || isLoadingToolHealth) { return; } // MCP connector deleted - if (isLoadingConnectorError) { + if (loadingConnectorError) { setMcpHealthStatus(McpToolHealthStatus.ConnectorNotFound); setError('connectorId', { message: labels.tools.mcpHealthStatus.connectorNotFound.title, @@ -71,7 +71,7 @@ export const McpReadOnlyFields = ({ } // MCP tools not found - if (isLoadingMcpToolsError) { + if (loadingMcpToolsError) { setMcpHealthStatus(McpToolHealthStatus.ListToolsFailed); setError('connectorId', { message: labels.tools.mcpHealthStatus.listToolsFailed.title, @@ -103,10 +103,11 @@ export const McpReadOnlyFields = ({ mcpToolName, mcpTools, toolHealth, - isLoadingConnectorError, - isLoadingMcpToolsError, + loadingConnectorError, + loadingMcpToolsError, isLoadingConnector, isLoadingMcpTools, + isLoadingToolHealth, setMcpHealthStatus, setError, clearErrors, diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/hooks/use_add_mcp_server_flyout.ts b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/hooks/use_add_mcp_server_flyout.ts index 88884ddf387ed..ee128ede2fdcb 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/hooks/use_add_mcp_server_flyout.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/hooks/use_add_mcp_server_flyout.ts @@ -13,7 +13,13 @@ import { useKibana } from '../../../../hooks/use_kibana'; import { useFlyoutState } from '../../../../hooks/use_flyout_state'; import { queryKeys } from '../../../../query_keys'; -export const useAddMcpServerFlyout = () => { +export interface UseAddMcpServerFlyoutOptions { + onConnectorCreated?: (connector: ActionConnector) => void; +} + +export const useAddMcpServerFlyout = ({ + onConnectorCreated, +}: UseAddMcpServerFlyoutOptions = {}) => { const { services: { plugins: { triggersActionsUi }, @@ -25,12 +31,13 @@ export const useAddMcpServerFlyout = () => { // Refresh the list of MCP connectors when a new MCP connector is created const handleConnectorCreated = useCallback( - (_: ActionConnector) => { + (connector: ActionConnector) => { queryClient.invalidateQueries({ queryKey: queryKeys.tools.connectors.list(MCP_CONNECTOR_TYPE), }); + onConnectorCreated?.(connector); }, - [queryClient] + [queryClient, onConnectorCreated] ); const flyout = useMemo( diff --git a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/bulk_import_mcp_tool_form_validation.ts b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/bulk_import_mcp_tool_form_validation.ts index 45e9012a01a5f..319660986a74a 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/bulk_import_mcp_tool_form_validation.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/components/tools/form/validation/bulk_import_mcp_tool_form_validation.ts @@ -6,8 +6,10 @@ */ import { i18n } from '@kbn/i18n'; -import { z } from '@kbn/zod'; +import { isInProtectedNamespace, hasNamespaceName } from '@kbn/onechat-common/base/namespaces'; +import { toolIdRegexp, toolIdMaxLength } from '@kbn/onechat-common/tools'; import { useQueryClient } from '@kbn/react-query'; +import { z } from '@kbn/zod'; import { useOnechatServices } from '../../../../hooks/use_onechat_service'; import { queryKeys } from '../../../../query_keys'; @@ -35,6 +37,28 @@ export const bulkImportMcpI18nMessages = { defaultMessage: 'Namespace is required.', } ), + tooLongError: i18n.translate( + 'xpack.onechat.tools.bulkImportMcp.validation.namespace.tooLongError', + { + defaultMessage: 'Namespace must be {maxLength} characters or less.', + values: { maxLength: toolIdMaxLength }, + } + ), + formatError: i18n.translate( + 'xpack.onechat.tools.bulkImportMcp.validation.namespace.formatError', + { + defaultMessage: + 'Namespace must start with a letter and contain only lowercase letters, numbers, and hyphens.', + } + ), + protectedNamespaceError: (name: string) => + i18n.translate( + 'xpack.onechat.tools.bulkImportMcp.validation.namespace.protectedNamespaceError', + { + defaultMessage: '"{name}" is a protected namespace and cannot be used.', + values: { name }, + } + ), conflictError: i18n.translate( 'xpack.onechat.tools.bulkImportMcp.validation.namespace.conflictError', { @@ -63,6 +87,14 @@ export const useBulkImportMcpToolFormValidationSchema = () => { namespace: z .string() .min(1, { message: bulkImportMcpI18nMessages.namespace.requiredError }) + .max(toolIdMaxLength, { message: bulkImportMcpI18nMessages.namespace.tooLongError }) + .regex(toolIdRegexp, { message: bulkImportMcpI18nMessages.namespace.formatError }) + .refine( + (name) => !isInProtectedNamespace(name) && !hasNamespaceName(name), + (name) => ({ + message: bulkImportMcpI18nMessages.namespace.protectedNamespaceError(name), + }) + ) .superRefine(async (value, ctx) => { if (value.length > 0) { const { isValid } = await queryClient.fetchQuery({ diff --git a/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_mcp_connectors.ts b/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_mcp_connectors.ts index 3b77f7e7c7e3a..b53ea5dac5a9b 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_mcp_connectors.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_mcp_connectors.ts @@ -19,44 +19,38 @@ export interface UseListConnectorsOptions { } export const useListConnectors = ({ type }: UseListConnectorsOptions) => { const { toolsService } = useOnechatServices(); - const { data, isLoading, error, isError } = useQuery({ + const { data, ...queryFields } = useQuery({ queryKey: queryKeys.tools.connectors.list(type), queryFn: () => toolsService.listConnectors({ type }), }); return { connectors: data?.connectors ?? EMPTY_CONNECTORS, - isLoading, - error, - isError, + ...queryFields, }; }; export const useGetConnector = ({ connectorId }: { connectorId: string }) => { const { toolsService } = useOnechatServices(); - const { data, isLoading, error, isError } = useQuery({ + const { data, ...queryFields } = useQuery({ queryKey: queryKeys.tools.connectors.get(connectorId), queryFn: () => toolsService.getConnector({ connectorId }), enabled: !!connectorId, }); return { connector: data?.connector, - isLoading, - error, - isError, + ...queryFields, }; }; export const useListMcpTools = ({ connectorId }: { connectorId: string }) => { const { toolsService } = useOnechatServices(); - const { data, isLoading, error, isError } = useQuery({ + const { data, ...queryFields } = useQuery({ queryKey: queryKeys.tools.connectors.listMcpTools(connectorId), queryFn: () => toolsService.listMcpTools({ connectorId }), enabled: !!connectorId, }); return { mcpTools: data?.mcpTools ?? EMPTY_TOOLS, - isLoading, - error, - isError, + ...queryFields, }; }; diff --git a/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_tools_health.ts b/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_tools_health.ts index 71249a7e9cb74..dc834ca796993 100644 --- a/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_tools_health.ts +++ b/x-pack/platform/plugins/shared/onechat/public/application/hooks/tools/use_tools_health.ts @@ -17,17 +17,15 @@ const EMPTY_HEALTH_STATES: readonly ToolHealthState[] = []; export const useToolsHealth = () => { const { toolsService } = useOnechatServices(); - const { data, isLoading, error, isError, refetch } = useQuery({ + const { data, ...queryFields } = useQuery({ queryKey: queryKeys.tools.health.list(), queryFn: () => toolsService.listToolsHealth(), + retry: false, }); return { healthStates: data?.results ?? EMPTY_HEALTH_STATES, - isLoading, - error, - isError, - refetch, + ...queryFields, }; }; @@ -39,35 +37,31 @@ export interface UseToolHealthOptions { export const useToolHealth = ({ toolId }: UseToolHealthOptions) => { const { toolsService } = useOnechatServices(); - const { data, isLoading, error, isError, refetch } = useQuery({ + const { data, ...queryFields } = useQuery({ queryKey: queryKeys.tools.health.byId(toolId), queryFn: () => toolsService.getToolHealth({ toolId }), enabled: !!toolId, + retry: false, }); return { toolHealth: data?.health, - isLoading, - error, - isError, - refetch, + ...queryFields, }; }; export const useMcpToolsHealth = ({ enabled = true }: { enabled?: boolean } = {}) => { const { toolsService } = useOnechatServices(); - const { data, isLoading, error, isError, refetch } = useQuery({ + const { data, ...queryFields } = useQuery({ queryKey: queryKeys.tools.health.mcp(), queryFn: () => toolsService.listMcpToolsHealth(), enabled, + retry: false, }); return { mcpHealthStates: data?.results ?? EMPTY_MCP_HEALTH_STATES, - isLoading, - error, - isError, - refetch, + ...queryFields, }; };