Skip to content
Merged
6 changes: 3 additions & 3 deletions packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pageLoadAssetSize:
contentConnectors: 33014
contentManagement: 8350
controls: 12234
core: 566392
core: 565105
cps: 5918
crossClusterReplication: 12662
customIntegrations: 11715
Expand Down Expand Up @@ -75,7 +75,7 @@ pageLoadAssetSize:
filesManagement: 5208
fileUpload: 22957
fleet: 187942
genAiSettings: 5507
genAiSettings: 5663
globalSearch: 6890
globalSearchBar: 26986
globalSearchProviders: 4646
Expand Down Expand Up @@ -130,7 +130,7 @@ pageLoadAssetSize:
painlessLab: 6299
presentationPanel: 11418
presentationUtil: 9000
productDocBase: 3083
productDocBase: 5025
productIntercept: 9860
profiling: 20716
reindexService: 3469
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"browser": true,
"server": true,
"configPath": ["xpack", "genAiSettings"],
"requiredPlugins": ["management", "actions", "inference", "licensing"],
"requiredPlugins": ["management", "actions", "inference", "licensing", "productDocBase"],
"optionalPlugins": ["spaces"],
"requiredBundles": ["kibanaReact"]
"requiredBundles": ["kibanaReact", "productDocBase"]
}
}
1 change: 1 addition & 0 deletions x-pack/platform/plugins/private/gen_ai_settings/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ dependsOn:
- '@kbn/licensing-plugin'
- '@kbn/management-settings-components-field-row'
- '@kbn/react-query'
- '@kbn/product-doc-base-plugin'
- '@kbn/ai-assistant-common'
- '@kbn/ai-agent-confirmation-modal'
tags:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*
* 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 { coreMock } from '@kbn/core/public/mocks';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { QueryClient, QueryClientProvider } from '@kbn/react-query';
import { I18nProvider } from '@kbn/i18n-react';
import type { ProductDocBasePluginStart } from '@kbn/product-doc-base-plugin/public';
import { DocumentationSection } from './documentation_section';

describe('DocumentationSection', () => {
const coreStart = coreMock.createStart();

const mockProductDocBase: ProductDocBasePluginStart = {
installation: {
getStatus: jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'uninstalled',
perProducts: {},
}),
install: jest.fn().mockResolvedValue({ installed: true }),
uninstall: jest.fn().mockResolvedValue({ success: true }),
},
};

const createQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});

const renderComponent = (
productDocBase: ProductDocBasePluginStart = mockProductDocBase,
hasManagePrivilege: boolean = true
) => {
const queryClient = createQueryClient();
// Set capabilities directly on the mock before rendering
(coreStart.application.capabilities as Record<string, Record<string, boolean>>).agentBuilder = {
show: true,
manageAgents: hasManagePrivilege,
};
return render(
<QueryClientProvider client={queryClient}>
<I18nProvider>
<KibanaContextProvider services={coreStart}>
<DocumentationSection productDocBase={productDocBase} />
</KibanaContextProvider>
</I18nProvider>
</QueryClientProvider>
);
};

beforeEach(() => {
jest.clearAllMocks();
});

describe('rendering', () => {
it('should render the documentation section', async () => {
renderComponent(mockProductDocBase);

await waitFor(() => {
expect(screen.getByTestId('documentationSection')).toBeInTheDocument();
expect(screen.getByTestId('documentationTitle')).toBeInTheDocument();
expect(screen.getByTestId('documentationTable')).toBeInTheDocument();
});
});

it('should render all documentation items', async () => {
renderComponent(mockProductDocBase);

await waitFor(() => {
expect(screen.getByText('Elastic documents')).toBeInTheDocument();
// Security labs is commented out for now - will be enabled later
expect(screen.queryByText('Security labs')).not.toBeInTheDocument();
});
});
});

describe('status display', () => {
it('should show "Not installed" status when uninstalled', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'uninstalled',
perProducts: {},
});

renderComponent(mockProductDocBase);

await waitFor(() => {
const notInstalledBadges = screen.getAllByText('Not installed');
expect(notInstalledBadges.length).toBeGreaterThan(0);
});
});

it('should show "Installed" status when installed', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'installed',
perProducts: {},
});

renderComponent(mockProductDocBase);

await waitFor(() => {
expect(screen.getByText('Installed')).toBeInTheDocument();
});
});
});

describe('actions', () => {
it('should show install action for uninstalled items', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'uninstalled',
perProducts: {},
});

renderComponent(mockProductDocBase);

await waitFor(() => {
expect(screen.getByTestId('documentation-install-elastic_documents')).toBeInTheDocument();
});
});

it('should show uninstall action for installed items', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'installed',
perProducts: {},
});

renderComponent(mockProductDocBase);

await waitFor(() => {
expect(screen.getByTestId('documentation-uninstall-elastic_documents')).toBeInTheDocument();
});
});

it('should call install when install action is clicked', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'uninstalled',
perProducts: {},
});

renderComponent(mockProductDocBase, true);

await waitFor(() => {
expect(screen.getByTestId('documentation-install-elastic_documents')).toBeInTheDocument();
});

fireEvent.click(screen.getByTestId('documentation-install-elastic_documents'));

await waitFor(() => {
expect(mockProductDocBase.installation.install).toHaveBeenCalledWith({
inferenceId: '.elser-2-elasticsearch',
});
});
});

it('should call uninstall when uninstall action is clicked', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'installed',
perProducts: {},
});

renderComponent(mockProductDocBase, true);

await waitFor(() => {
expect(screen.getByTestId('documentation-uninstall-elastic_documents')).toBeInTheDocument();
});

fireEvent.click(screen.getByTestId('documentation-uninstall-elastic_documents'));

await waitFor(() => {
expect(mockProductDocBase.installation.uninstall).toHaveBeenCalledWith({
inferenceId: '.elser-2-elasticsearch',
});
});
});
});

describe('RBAC - insufficient privileges', () => {
it('should disable install button when user lacks manage privilege', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'uninstalled',
perProducts: {},
});

renderComponent(mockProductDocBase, false);

await waitFor(() => {
const installButton = screen.getByTestId('documentation-install-elastic_documents');
expect(installButton).toBeInTheDocument();
expect(installButton).toBeDisabled();
});
});

it('should disable uninstall button when user lacks manage privilege', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'installed',
perProducts: {},
});

renderComponent(mockProductDocBase, false);

await waitFor(() => {
const uninstallButton = screen.getByTestId('documentation-uninstall-elastic_documents');
expect(uninstallButton).toBeInTheDocument();
expect(uninstallButton).toBeDisabled();
});
});

it('should not call install when install button is clicked without privilege', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'uninstalled',
perProducts: {},
});

renderComponent(mockProductDocBase, false);

await waitFor(() => {
expect(screen.getByTestId('documentation-install-elastic_documents')).toBeInTheDocument();
});

fireEvent.click(screen.getByTestId('documentation-install-elastic_documents'));

// Wait a bit to ensure no call was made
await new Promise((resolve) => setTimeout(resolve, 100));

expect(mockProductDocBase.installation.install).not.toHaveBeenCalled();
});

it('should not call uninstall when uninstall button is clicked without privilege', async () => {
mockProductDocBase.installation.getStatus = jest.fn().mockResolvedValue({
inferenceId: '.elser-2-elasticsearch',
overall: 'installed',
perProducts: {},
});

renderComponent(mockProductDocBase, false);

await waitFor(() => {
expect(screen.getByTestId('documentation-uninstall-elastic_documents')).toBeInTheDocument();
});

fireEvent.click(screen.getByTestId('documentation-uninstall-elastic_documents'));

// Wait a bit to ensure no call was made
await new Promise((resolve) => setTimeout(resolve, 100));

expect(mockProductDocBase.installation.uninstall).not.toHaveBeenCalled();
});
});
});
Loading