From f166396f07b49bf94122713b5984eff5c66c5d8d Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 8 Jan 2025 15:06:44 -0700 Subject: [PATCH 01/13] work --- .../impl/assistant/api/product_docs/const.ts | 11 +++ .../use_get_product_doc_status.ts | 32 +++++++ .../product_docs/use_install_product_doc.ts | 48 ++++++++++ .../settings/product_documentation/index.tsx | 93 +++++++++++++++++++ .../product_documentation/translations.ts | 28 ++++++ .../impl/assistant_context/index.tsx | 6 ++ .../index.tsx | 2 + .../plugins/security_solution/kibana.jsonc | 3 +- .../public/assistant/provider.tsx | 2 + .../plugins/security_solution/public/types.ts | 2 + .../plugins/security_solution/tsconfig.json | 3 +- 11 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/const.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/const.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/const.ts new file mode 100644 index 0000000000000..89ccf285229da --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/const.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export const REACT_QUERY_KEYS = { + GET_PRODUCT_DOC_STATUS: 'get_product_doc_status', + INSTALL_PRODUCT_DOC: 'install_product_doc', +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.ts new file mode 100644 index 0000000000000..5e516a4207cfa --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.ts @@ -0,0 +1,32 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { REACT_QUERY_KEYS } from './const'; +import { useAssistantContext } from '../../../..'; + +export function useGetProductDocStatus() { + const { productDocBase } = useAssistantContext(); + + const { isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery({ + queryKey: [REACT_QUERY_KEYS.GET_PRODUCT_DOC_STATUS], + queryFn: async () => { + return productDocBase.installation.getStatus(); + }, + keepPreviousData: false, + refetchOnWindowFocus: false, + }); + + return { + status: data, + refetch, + isLoading, + isRefetching, + isSuccess, + isError, + }; +} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.ts new file mode 100644 index 0000000000000..b17dab7826c48 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.ts @@ -0,0 +1,48 @@ +/* + * 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 { useMutation, useQueryClient } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; +import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; +import type { PerformInstallResponse } from '@kbn/product-doc-base-plugin/common/http_api/installation'; +import { REACT_QUERY_KEYS } from './const'; +import { useAssistantContext } from '../../../..'; + +type ServerError = IHttpFetchError; + +export function useInstallProductDoc() { + const { productDocBase, toasts } = useAssistantContext(); + const queryClient = useQueryClient(); + + return useMutation( + [REACT_QUERY_KEYS.INSTALL_PRODUCT_DOC], + () => { + return productDocBase.installation.install(); + }, + { + onSuccess: () => { + toasts?.addSuccess( + i18n.translate('xpack.elasticAssistant.kb.installProductDoc.successNotification', { + defaultMessage: 'The Elastic documentation was successfully installed', + }) + ); + + queryClient.invalidateQueries({ + queryKey: [REACT_QUERY_KEYS.GET_PRODUCT_DOC_STATUS], + refetchType: 'all', + }); + }, + onError: (error) => { + toasts?.addError(new Error(error.body?.message ?? error.message), { + title: i18n.translate('xpack.elasticAssistant.kb.installProductDoc.errorNotification', { + defaultMessage: 'Something went wrong while installing the Elastic documentation', + }), + }); + }, + } + ); +} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx new file mode 100644 index 0000000000000..e1e8324622b76 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx @@ -0,0 +1,93 @@ +/* + * 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 { + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import React, { useEffect, useState, useCallback, useMemo } from 'react'; +import { useInstallProductDoc } from '../../api/product_docs/use_install_product_doc'; +import { useGetProductDocStatus } from '../../api/product_docs/use_get_product_doc_status'; +import * as i18n from './translations'; + +export const ProductDocumentationManagement: React.FC = React.memo(() => { + const [isInstalled, setInstalled] = useState(true); + const [isInstalling, setInstalling] = useState(false); + + const { mutateAsync: installProductDoc } = useInstallProductDoc(); + const { status, isLoading: isStatusLoading } = useGetProductDocStatus(); + + useEffect(() => { + if (status) { + setInstalled(status.overall === 'installed'); + } + }, [status]); + + const onClickInstall = useCallback(() => { + setInstalling(true); + installProductDoc().then( + () => { + setInstalling(false); + setInstalled(true); + }, + () => { + setInstalling(false); + setInstalled(false); + } + ); + }, [installProductDoc]); + + const content = useMemo(() => { + if (isStatusLoading) { + return ; + } + if (isInstalling) { + return ( + + + {i18n.INSTALLING} + + ); + } + return ( + + + + {i18n.INSTALL} + + + + ); + }, [isInstalling, isStatusLoading, onClickInstall]); + + if (isInstalled) { + return null; + } + return ( + <> + + + {i18n.DESCRIPTION} + + + {content} + + + + ); +}); + +ProductDocumentationManagement.displayName = 'ProductDocumentationManagement'; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts new file mode 100644 index 0000000000000..bf32da4deb963 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts @@ -0,0 +1,28 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const LABEL = i18n.translate('xpack.elasticAssistant.assistant.settings.productDocLabel', { + defaultMessage: 'Elastic documentation is not installed', +}); +export const DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.productDocDescription', + { + defaultMessage: "Install Elastic documentation to improve the assistant's efficiency.", + } +); + +export const INSTALL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.installProductDocButtonLabel', + { defaultMessage: 'Install' } +); + +export const INSTALLING = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.installingText', + { defaultMessage: 'Installing...' } +); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx index b6fa6a4859f41..ebf85e0f86a90 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/index.tsx @@ -15,6 +15,7 @@ import useSessionStorage from 'react-use/lib/useSessionStorage'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { AssistantFeatures, defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; import { ChromeStart, NavigateToAppOptions, UserProfileService } from '@kbn/core/public'; +import type { ProductDocBasePluginStart } from '@kbn/product-doc-base-plugin/public'; import { useQuery } from '@tanstack/react-query'; import { updatePromptContexts } from './helpers'; import type { @@ -78,6 +79,7 @@ export interface AssistantProviderProps { title?: string; toasts?: IToasts; currentAppId: string; + productDocBase: ProductDocBasePluginStart; userProfileService: UserProfileService; chrome: ChromeStart; } @@ -131,6 +133,7 @@ export interface UseAssistantContext { unRegisterPromptContext: UnRegisterPromptContext; currentAppId: string; codeBlockRef: React.MutableRefObject<(codeBlock: string) => void>; + productDocBase: ProductDocBasePluginStart; userProfileService: UserProfileService; chrome: ChromeStart; } @@ -153,6 +156,7 @@ export const AssistantProvider: React.FC = ({ baseConversations, navigateToApp, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, + productDocBase, title = DEFAULT_ASSISTANT_TITLE, toasts, currentAppId, @@ -291,6 +295,7 @@ export const AssistantProvider: React.FC = ({ promptContexts, navigateToApp, nameSpace, + productDocBase, registerPromptContext, selectedSettingsTab, // can be undefined from localStorage, if not defined, default to true @@ -331,6 +336,7 @@ export const AssistantProvider: React.FC = ({ promptContexts, navigateToApp, nameSpace, + productDocBase, registerPromptContext, selectedSettingsTab, localStorageStreaming, diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx index 183e74a18247a..b47c7649dcefd 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings_management/index.tsx @@ -31,6 +31,7 @@ import { import { css } from '@emotion/react'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; import useAsync from 'react-use/lib/useAsync'; +import { ProductDocumentationManagement } from '../../assistant/settings/product_documentation'; import { KnowledgeBaseTour } from '../../tour/knowledge_base'; import { AlertsSettingsManagement } from '../../assistant/settings/alerts_settings/alerts_settings_management'; import { useKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_knowledge_base_entries'; @@ -332,6 +333,7 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(({ d return ( <> + > = ({ children }) docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, userProfile, chrome, + productDocBase, } = useKibana().services; let inferenceEnabled = false; @@ -235,6 +236,7 @@ export const AssistantProvider: FC> = ({ children }) http={http} inferenceEnabled={inferenceEnabled} navigateToApp={navigateToApp} + productDocBase={productDocBase} title={ASSISTANT_TITLE} toasts={toasts} currentAppId={currentAppId ?? 'securitySolutionUI'} diff --git a/x-pack/solutions/security/plugins/security_solution/public/types.ts b/x-pack/solutions/security/plugins/security_solution/public/types.ts index 2bf3edf64a592..bf3d44f48a762 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/types.ts @@ -21,6 +21,7 @@ import type { Storage } from '@kbn/kibana-utils-plugin/public'; import type { FleetStart } from '@kbn/fleet-plugin/public'; import type { PluginStart as ListsPluginStart } from '@kbn/lists-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { ProductDocBasePluginStart } from '@kbn/product-doc-base-plugin/public'; import type { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, TriggersAndActionsUIPublicPluginStart as TriggersActionsStart, @@ -200,6 +201,7 @@ export type StartServices = CoreStart & topValuesPopover: TopValuesPopoverService; timelineDataService: DataPublicPluginStart; siemMigrations: SiemMigrationsService; + productDocBase: ProductDocBasePluginStart; }; export type StartRenderServices = Pick< diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index ab99f07246fb2..61d3a199798ac 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -238,6 +238,7 @@ "@kbn/core-chrome-browser-mocks", "@kbn/ai-assistant-icon", "@kbn/llm-tasks-plugin", - "@kbn/charts-theme" + "@kbn/charts-theme", + "@kbn/product-doc-common" ] } From c49c41f9f6a91c0dc934373749dc8aee00f31e97 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 8 Jan 2025 22:23:11 +0000 Subject: [PATCH 02/13] [CI] Auto-commit changed files from 'node scripts/notice' --- .../platform/packages/shared/kbn-elastic-assistant/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json b/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json index c3513b4537f68..95c51c0d85119 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/tsconfig.json @@ -37,5 +37,6 @@ "@kbn/core-chrome-browser-mocks", "@kbn/core-chrome-browser", "@kbn/ai-assistant-icon", + "@kbn/product-doc-base-plugin", ] } From ad372e4e06c4c87cc5d5fb51dd59d977a3e17ac9 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 8 Jan 2025 15:28:59 -0700 Subject: [PATCH 03/13] add tests --- .../use_get_product_doc_status.test.ts | 60 +++++++++++++ .../use_install_product_doc.test.ts | 67 ++++++++++++++ .../product_documentation/index.test.tsx | 87 +++++++++++++++++++ .../settings/product_documentation/index.tsx | 4 +- 4 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.test.tsx diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts new file mode 100644 index 0000000000000..bdc6126be9ef7 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { useGetProductDocStatus } from './use_get_product_doc_status'; +import { useAssistantContext } from '../../../..'; +import { TestProviders } from '../../../mock/test_providers/test_providers'; + +jest.mock('../../../..', () => ({ + useAssistantContext: jest.fn(), +})); + +describe('useGetProductDocStatus', () => { + const mockGetStatus = jest.fn(); + + beforeEach(() => { + (useAssistantContext as jest.Mock).mockReturnValue({ + productDocBase: { + installation: { + getStatus: mockGetStatus, + }, + }, + }); + }); + + it('returns loading state initially', async () => { + mockGetStatus.mockResolvedValueOnce('status'); + const { result, waitFor } = renderHook(() => useGetProductDocStatus(), { + wrapper: TestProviders, + }); + + expect(result.current.isLoading).toBe(true); + await waitFor(() => result.current.isSuccess); + }); + + it('returns success state with data', async () => { + mockGetStatus.mockResolvedValueOnce('status'); + const { result, waitFor } = renderHook(() => useGetProductDocStatus(), { + wrapper: TestProviders, + }); + + await waitFor(() => result.current.isSuccess); + expect(result.current.status).toBe('status'); + expect(result.current.isSuccess).toBe(true); + }); + + it('returns error state when query fails', async () => { + mockGetStatus.mockRejectedValueOnce(new Error('error')); + const { result, waitFor } = renderHook(() => useGetProductDocStatus(), { + wrapper: TestProviders, + }); + + await waitFor(() => result.current.isError); + expect(result.current.isError).toBe(true); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts new file mode 100644 index 0000000000000..36630ee550a94 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts @@ -0,0 +1,67 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { useInstallProductDoc } from './use_install_product_doc'; +import { useAssistantContext } from '../../../..'; +import { TestProviders } from '../../../mock/test_providers/test_providers'; + +jest.mock('../../../..', () => ({ + useAssistantContext: jest.fn(), +})); + +describe('useInstallProductDoc', () => { + const mockInstall = jest.fn(); + const mockAddSuccess = jest.fn(); + const mockAddError = jest.fn(); + + beforeEach(() => { + (useAssistantContext as jest.Mock).mockReturnValue({ + productDocBase: { + installation: { + install: mockInstall, + }, + }, + toasts: { + addSuccess: mockAddSuccess, + addError: mockAddError, + }, + }); + }); + + it('returns success state and shows success toast on successful installation', async () => { + mockInstall.mockResolvedValueOnce({}); + const { result, waitFor } = renderHook(() => useInstallProductDoc(), { + wrapper: TestProviders, + }); + + result.current.mutate(); + await waitFor(() => result.current.isSuccess); + + expect(mockAddSuccess).toHaveBeenCalledWith( + 'The Elastic documentation was successfully installed' + ); + }); + + it('returns error state and shows error toast on failed installation', async () => { + const error = new Error('error message'); + mockInstall.mockRejectedValueOnce(error); + const { result, waitFor } = renderHook(() => useInstallProductDoc(), { + wrapper: TestProviders, + }); + + result.current.mutate(); + await waitFor(() => result.current.isError); + + expect(mockAddError).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'error message', + }), + { title: 'Something went wrong while installing the Elastic documentation' } + ); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.test.tsx new file mode 100644 index 0000000000000..c8700f995862f --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.test.tsx @@ -0,0 +1,87 @@ +/* + * 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 React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { ProductDocumentationManagement } from '.'; +import * as i18n from './translations'; +import { useInstallProductDoc } from '../../api/product_docs/use_install_product_doc'; +import { useGetProductDocStatus } from '../../api/product_docs/use_get_product_doc_status'; + +jest.mock('../../api/product_docs/use_install_product_doc'); +jest.mock('../../api/product_docs/use_get_product_doc_status'); + +describe('ProductDocumentationManagement', () => { + const mockInstallProductDoc = jest.fn().mockResolvedValue({}); + + beforeEach(() => { + (useInstallProductDoc as jest.Mock).mockReturnValue({ mutateAsync: mockInstallProductDoc }); + (useGetProductDocStatus as jest.Mock).mockReturnValue({ status: null, isLoading: false }); + jest.clearAllMocks(); + }); + + it('renders loading spinner when status is loading', async () => { + (useGetProductDocStatus as jest.Mock).mockReturnValue({ + status: { overall: 'not_installed' }, + isLoading: true, + }); + render(); + expect(screen.getByTestId('statusLoading')).toBeInTheDocument(); + }); + + it('renders install button when not installed', () => { + (useGetProductDocStatus as jest.Mock).mockReturnValue({ + status: { overall: 'not_installed' }, + isLoading: false, + }); + render(); + expect(screen.getByText(i18n.INSTALL)).toBeInTheDocument(); + }); + + it('does not render anything when already installed', () => { + (useGetProductDocStatus as jest.Mock).mockReturnValue({ + status: { overall: 'installed' }, + isLoading: false, + }); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it('shows installing spinner and text when installing', async () => { + (useGetProductDocStatus as jest.Mock).mockReturnValue({ + status: { overall: 'not_installed' }, + isLoading: false, + }); + render(); + fireEvent.click(screen.getByText(i18n.INSTALL)); + await waitFor(() => { + expect(screen.getByTestId('installing')).toBeInTheDocument(); + expect(screen.getByText(i18n.INSTALLING)).toBeInTheDocument(); + }); + }); + + it('sets installed state to true after successful installation', async () => { + (useGetProductDocStatus as jest.Mock).mockReturnValue({ + status: { overall: 'not_installed' }, + isLoading: false, + }); + mockInstallProductDoc.mockResolvedValueOnce({}); + render(); + fireEvent.click(screen.getByText(i18n.INSTALL)); + await waitFor(() => expect(screen.queryByText(i18n.INSTALL)).not.toBeInTheDocument()); + }); + + it('sets installed state to false after failed installation', async () => { + (useGetProductDocStatus as jest.Mock).mockReturnValue({ + status: { overall: 'not_installed' }, + isLoading: false, + }); + mockInstallProductDoc.mockRejectedValueOnce(new Error('Installation failed')); + render(); + fireEvent.click(screen.getByText(i18n.INSTALL)); + await waitFor(() => expect(screen.getByText(i18n.INSTALL)).toBeInTheDocument()); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx index e1e8324622b76..a81ef1262cb6b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx @@ -48,12 +48,12 @@ export const ProductDocumentationManagement: React.FC = React.memo(() => { const content = useMemo(() => { if (isStatusLoading) { - return ; + return ; } if (isInstalling) { return ( - + {i18n.INSTALLING} ); From d673b1c478715ee33c3894e0efb9626b8d98171e Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 8 Jan 2025 22:33:17 +0000 Subject: [PATCH 04/13] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../solutions/security/plugins/security_solution/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index 61d3a199798ac..3c550f68fe84c 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -239,6 +239,6 @@ "@kbn/ai-assistant-icon", "@kbn/llm-tasks-plugin", "@kbn/charts-theme", - "@kbn/product-doc-common" + "@kbn/product-doc-base-plugin" ] } From a019e4f6a49234a4320253c4ac90ab68f442ebaa Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 8 Jan 2025 17:51:18 -0700 Subject: [PATCH 05/13] Update test providers --- .../impl/mock/test_providers/test_providers.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index c058e30e2dddc..fa99679c0900f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -88,6 +88,9 @@ export const TestProvidersComponent: React.FC = ({ navigateToApp={mockNavigateToApp} {...providerContext} currentAppId={'test'} + productDocBase={{ + installation: { getStatus: jest.fn(), install: jest.fn(), uninstall: jest.fn() }, + }} userProfileService={jest.fn() as unknown as UserProfileService} chrome={chrome} > From 655a3d0f3176655f36af980f210dcd45bce53fa6 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 9 Jan 2025 08:31:17 -0700 Subject: [PATCH 06/13] reduce setState calls --- .../settings/product_documentation/index.tsx | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx index a81ef1262cb6b..45dc67c59784f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/index.tsx @@ -20,30 +20,31 @@ import { useGetProductDocStatus } from '../../api/product_docs/use_get_product_d import * as i18n from './translations'; export const ProductDocumentationManagement: React.FC = React.memo(() => { - const [isInstalled, setInstalled] = useState(true); - const [isInstalling, setInstalling] = useState(false); + const [{ isInstalled, isInstalling }, setState] = useState({ + isInstalled: true, + isInstalling: false, + }); const { mutateAsync: installProductDoc } = useInstallProductDoc(); const { status, isLoading: isStatusLoading } = useGetProductDocStatus(); useEffect(() => { if (status) { - setInstalled(status.overall === 'installed'); + setState((prevState) => ({ + ...prevState, + isInstalled: status.overall === 'installed', + })); } }, [status]); - const onClickInstall = useCallback(() => { - setInstalling(true); - installProductDoc().then( - () => { - setInstalling(false); - setInstalled(true); - }, - () => { - setInstalling(false); - setInstalled(false); - } - ); + const onClickInstall = useCallback(async () => { + setState((prevState) => ({ ...prevState, isInstalling: true })); + try { + await installProductDoc(); + setState({ isInstalled: true, isInstalling: false }); + } catch { + setState({ isInstalled: false, isInstalling: false }); + } }, [installProductDoc]); const content = useMemo(() => { From 12c37bf9f65cf076543db635a0233e2b2a15c99b Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 9 Jan 2025 08:32:47 -0700 Subject: [PATCH 07/13] fix lint/type --- .../api/product_docs/use_get_product_doc_status.test.ts | 2 +- .../assistant/api/product_docs/use_install_product_doc.test.ts | 2 +- .../data_quality_panel/mock/test_providers/test_providers.tsx | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts index bdc6126be9ef7..ba1e5b087dce7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useGetProductDocStatus } from './use_get_product_doc_status'; import { useAssistantContext } from '../../../..'; import { TestProviders } from '../../../mock/test_providers/test_providers'; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts index 36630ee550a94..0218b0a5d97d9 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useInstallProductDoc } from './use_install_product_doc'; import { useAssistantContext } from '../../../..'; import { TestProviders } from '../../../mock/test_providers/test_providers'; diff --git a/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index 7165e699d059e..8aaf7012ebf17 100644 --- a/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/solutions/security/packages/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -88,6 +88,9 @@ const TestExternalProvidersComponent: React.FC = ({ http={mockHttp} baseConversations={{}} navigateToApp={mockNavigateToApp} + productDocBase={{ + installation: { getStatus: jest.fn(), install: jest.fn(), uninstall: jest.fn() }, + }} currentAppId={'securitySolutionUI'} userProfileService={jest.fn() as unknown as UserProfileService} chrome={chrome} From 908fabeacebe7934c5d0a457d68b3fd451b2afa8 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 9 Jan 2025 09:01:00 -0700 Subject: [PATCH 08/13] text update --- .../assistant/settings/product_documentation/translations.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts index bf32da4deb963..196eef04a2fdf 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/product_documentation/translations.ts @@ -13,7 +13,8 @@ export const LABEL = i18n.translate('xpack.elasticAssistant.assistant.settings.p export const DESCRIPTION = i18n.translate( 'xpack.elasticAssistant.assistant.settings.productDocDescription', { - defaultMessage: "Install Elastic documentation to improve the assistant's efficiency.", + defaultMessage: + 'The Elastic Documentation has been uninstalled. Please reinstall to ensure the most accurate results from the AI Assistant.', } ); From c8a8f5a8ec22d97002ebdac85d040d5c26b495b2 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 9 Jan 2025 09:09:51 -0700 Subject: [PATCH 09/13] fix test --- .../use_get_product_doc_status.test.ts | 20 ++++++++++--------- .../use_install_product_doc.test.ts | 6 +++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts index ba1e5b087dce7..1a6dc829c767a 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_get_product_doc_status.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useGetProductDocStatus } from './use_get_product_doc_status'; import { useAssistantContext } from '../../../..'; import { TestProviders } from '../../../mock/test_providers/test_providers'; @@ -29,7 +29,7 @@ describe('useGetProductDocStatus', () => { it('returns loading state initially', async () => { mockGetStatus.mockResolvedValueOnce('status'); - const { result, waitFor } = renderHook(() => useGetProductDocStatus(), { + const { result } = renderHook(() => useGetProductDocStatus(), { wrapper: TestProviders, }); @@ -39,22 +39,24 @@ describe('useGetProductDocStatus', () => { it('returns success state with data', async () => { mockGetStatus.mockResolvedValueOnce('status'); - const { result, waitFor } = renderHook(() => useGetProductDocStatus(), { + const { result } = renderHook(() => useGetProductDocStatus(), { wrapper: TestProviders, }); - await waitFor(() => result.current.isSuccess); - expect(result.current.status).toBe('status'); - expect(result.current.isSuccess).toBe(true); + await waitFor(() => { + expect(result.current.status).toBe('status'); + expect(result.current.isSuccess).toBe(true); + }); }); it('returns error state when query fails', async () => { mockGetStatus.mockRejectedValueOnce(new Error('error')); - const { result, waitFor } = renderHook(() => useGetProductDocStatus(), { + const { result } = renderHook(() => useGetProductDocStatus(), { wrapper: TestProviders, }); - await waitFor(() => result.current.isError); - expect(result.current.isError).toBe(true); + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); }); }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts index 0218b0a5d97d9..3b3c12d6b9dc8 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/product_docs/use_install_product_doc.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useInstallProductDoc } from './use_install_product_doc'; import { useAssistantContext } from '../../../..'; import { TestProviders } from '../../../mock/test_providers/test_providers'; @@ -35,7 +35,7 @@ describe('useInstallProductDoc', () => { it('returns success state and shows success toast on successful installation', async () => { mockInstall.mockResolvedValueOnce({}); - const { result, waitFor } = renderHook(() => useInstallProductDoc(), { + const { result } = renderHook(() => useInstallProductDoc(), { wrapper: TestProviders, }); @@ -50,7 +50,7 @@ describe('useInstallProductDoc', () => { it('returns error state and shows error toast on failed installation', async () => { const error = new Error('error message'); mockInstall.mockRejectedValueOnce(error); - const { result, waitFor } = renderHook(() => useInstallProductDoc(), { + const { result } = renderHook(() => useInstallProductDoc(), { wrapper: TestProviders, }); From da69efb29a4927ab73a325663a676df571a27c17 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 9 Jan 2025 11:36:07 -0700 Subject: [PATCH 10/13] fix type --- .../public/common/mock/mock_assistant_provider.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 7bfc76bfb4880..c793f7722780a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -61,6 +61,9 @@ export const MockAssistantProviderComponent: React.FC = ({ navigateToApp={mockNavigateToApp} baseConversations={BASE_SECURITY_CONVERSATIONS} currentAppId={'test'} + productDocBase={{ + installation: { getStatus: jest.fn(), install: jest.fn(), uninstall: jest.fn() }, + }} userProfileService={mockUserProfileService} chrome={chrome} > From a59a543137e041a2a9ef6149defe412c793bd29b Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 9 Jan 2025 13:35:08 -0700 Subject: [PATCH 11/13] fix type again... --- .../rule_status_failed_callout.test.tsx | 39 +------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx index 036d7499aab72..0fa6ecacea9d3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/rule_execution_status/rule_status_failed_callout.test.tsx @@ -12,15 +12,10 @@ import { render } from '@testing-library/react'; import type { RuleExecutionStatus } from '../../../../../common/api/detection_engine/rule_monitoring'; import { RuleExecutionStatusEnum } from '../../../../../common/api/detection_engine/rule_monitoring'; import { RuleStatusFailedCallOut } from './rule_status_failed_callout'; -import { AssistantProvider } from '@kbn/elastic-assistant'; -import type { AssistantAvailability } from '@kbn/elastic-assistant'; -import { httpServiceMock } from '@kbn/core-http-browser-mocks'; -import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { BASE_SECURITY_CONVERSATIONS } from '../../../../assistant/content/conversations'; -import type { UserProfileService } from '@kbn/core-user-profile-browser'; import { chromeServiceMock } from '@kbn/core/public/mocks'; import { of } from 'rxjs'; +import { MockAssistantProviderComponent } from '../../../../common/mock/mock_assistant_provider'; jest.mock('../../../../common/lib/kibana'); @@ -28,18 +23,6 @@ const TEST_ID = 'ruleStatusFailedCallOut'; const DATE = '2022-01-27T15:03:31.176Z'; const MESSAGE = 'This rule is attempting to query data but...'; -const actionTypeRegistry = actionTypeRegistryMock.create(); -const mockGetComments = jest.fn(() => []); -const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' }); -const mockNavigationToApp = jest.fn(); -const mockAssistantAvailability: AssistantAvailability = { - hasAssistantPrivilege: false, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - hasUpdateAIAssistantAnonymization: true, - hasManageGlobalKnowledgeBase: true, - isAssistantEnabled: true, -}; const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -58,25 +41,7 @@ const ContextWrapper: FC> = ({ children }) => { chrome.getChromeStyle$.mockReturnValue(of('classic')); return ( - - {children} - + {children} ); }; From 644f72f50a7940ff61265885d48116603274e5d9 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:47:31 +0000 Subject: [PATCH 12/13] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../solutions/security/plugins/security_solution/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index 3c550f68fe84c..356f224934028 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -226,7 +226,6 @@ "@kbn/core-saved-objects-server-mocks", "@kbn/core-security-server-mocks", "@kbn/serverless", - "@kbn/core-user-profile-browser", "@kbn/data-stream-adapter", "@kbn/core-lifecycle-server", "@kbn/core-user-profile-common", From 0d3a5de99570609b51989f7470890a53c4760d01 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 9 Jan 2025 15:57:33 -0700 Subject: [PATCH 13/13] ? --- .../security/plugins/security_solution/public/plugin_services.ts | 1 + .../solutions/security/plugins/security_solution/public/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/plugin_services.ts b/x-pack/solutions/security/plugins/security_solution/public/plugin_services.ts index cd066da31f549..24140efe59599 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/plugin_services.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/plugin_services.ts @@ -153,6 +153,7 @@ export class PluginServices { customDataService, timelineDataService, topValuesPopover: new TopValuesPopoverService(), + productDocBase: startPlugins.productDocBase, siemMigrations: await createSiemMigrationsService(coreStart, startPlugins), ...(params && { onAppLeave: params.onAppLeave, diff --git a/x-pack/solutions/security/plugins/security_solution/public/types.ts b/x-pack/solutions/security/plugins/security_solution/public/types.ts index bf3d44f48a762..9cb9e80d85445 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/types.ts @@ -164,6 +164,7 @@ export interface StartPlugins { core: CoreStart; integrationAssistant?: IntegrationAssistantPluginStart; serverless?: ServerlessPluginStart; + productDocBase: ProductDocBasePluginStart; } export interface StartPluginsDependencies extends StartPlugins {