Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5b709bc
added streamimg to observability ai insights
yuliia-fryshko Dec 29, 2025
f5b7701
added streamimg to error ai insights
yuliia-fryshko Dec 30, 2025
adfa0d8
added streamimg to alert ai insights
yuliia-fryshko Dec 30, 2025
2b61974
removed unused type and code
yuliia-fryshko Dec 30, 2025
be99599
Merge remote-tracking branch 'upstream/main' into add-streamimg-to-ai…
yuliia-fryshko Jan 2, 2026
d9dda81
Changes from node scripts/lint_ts_projects --fix
kibanamachine Jan 2, 2026
d3826a5
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Jan 2, 2026
d86255f
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 2, 2026
0fba3a9
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 4, 2026
a3ae8ff
use apiclient, rxjs instead of manually handling abort operations
yuliia-fryshko Jan 4, 2026
762e2c7
Changes from node scripts/lint_ts_projects --fix
kibanamachine Jan 4, 2026
43a3347
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Jan 4, 2026
a65342d
added hook for handling streaming operations
yuliia-fryshko Jan 4, 2026
d073803
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 4, 2026
b9100db
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 8, 2026
14613f2
added cursor loader
yuliia-fryshko Jan 8, 2026
42d1a76
added regenerate next to start conversation
yuliia-fryshko Jan 8, 2026
fd3fa36
added a hook use_api_client
yuliia-fryshko Jan 8, 2026
06f3188
fixed merge conflicts
yuliia-fryshko Jan 8, 2026
eb0cdf1
renamed to InsightResponse and InsightStreamEvent
yuliia-fryshko Jan 8, 2026
3693afb
refactor streamimg hook
yuliia-fryshko Jan 8, 2026
5164de5
exported getRequestAbortedSignal from Inference Plugin
yuliia-fryshko Jan 8, 2026
33d14d3
fixed check_types
yuliia-fryshko Jan 8, 2026
92ee2ef
fixed check type
yuliia-fryshko Jan 8, 2026
9dfda7a
Changes from node scripts/lint_ts_projects --fix
kibanamachine Jan 8, 2026
1e8bdcf
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Jan 8, 2026
d1247cf
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 8, 2026
13a658a
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 12, 2026
48e647c
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 12, 2026
fad07af
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 13, 2026
56559bd
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 19, 2026
ba6ed9f
added tests
yuliia-fryshko Jan 19, 2026
314e190
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 19, 2026
08625df
fixed review comments
yuliia-fryshko Jan 20, 2026
8f62336
removed markdown AST manipulation
yuliia-fryshko Jan 20, 2026
8e2f16a
addressed review comments
yuliia-fryshko Jan 20, 2026
5b2a8eb
fixed merge conflicts
yuliia-fryshko Jan 21, 2026
e14f2d6
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 21, 2026
005bf85
Merge branch 'main' into add-streamimg-to-ai-insight
viduni94 Jan 21, 2026
ea2958c
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 22, 2026
b579083
Start Conversation btn is not available is summary is empty
yuliia-fryshko Jan 22, 2026
43e59c9
fixed tests
yuliia-fryshko Jan 23, 2026
13faa71
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 23, 2026
563b593
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 23, 2026
acd099c
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 23, 2026
ff45961
Merge branch 'main' into add-streamimg-to-ai-insight
yuliia-fryshko Jan 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"licensing"
],
"optionalPlugins": ["ml", "spaces"],
"requiredBundles": ["kibanaReact"],
"requiredBundles": ["kibanaReact", "kibanaUtils"],
"extraPublicDirs": []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ dependsOn:
- '@kbn/licensing-types'
- '@kbn/licensing-plugin'
- '@kbn/observability-nav-icons'
- '@kbn/server-route-repository-client'
- '@kbn/agent-builder-browser'
- '@kbn/sse-utils-server'
- '@kbn/kibana-utils-plugin'
- '@kbn/server-route-repository-client'
tags:
- plugin
- prod
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* 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, fireEvent } from '@testing-library/react';
import { EuiThemeProvider } from '@elastic/eui';
import { AIChatExperience } from '@kbn/ai-assistant-common';
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
import { AiInsight } from './ai_insight';
import { useKibana } from '../../hooks/use_kibana';
import { useLicense } from '../../hooks/use_license';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
import { useStreamingAiInsight } from '../../hooks/use_streaming_ai_insight';
import { OBSERVABILITY_AGENT_ID } from '../../../common/constants';

jest.mock('@kbn/kibana-react-plugin/public', () => ({
useUiSetting$: jest.fn(),
}));

jest.mock('../../hooks/use_kibana');
jest.mock('../../hooks/use_license');
jest.mock('../../hooks/use_genai_connectors');
jest.mock('../../hooks/use_streaming_ai_insight');

const mockUseUiSetting$ = useUiSetting$ as jest.Mock;
const mockUseKibana = useKibana as jest.Mock;
const mockUseLicense = useLicense as jest.Mock;
const mockUseGenAIConnectors = useGenAIConnectors as jest.Mock;
const mockUseStreamingAiInsight = useStreamingAiInsight as jest.Mock;
const mockCreateStream = jest.fn();
const AiInsightTest = AiInsight as React.ComponentType<any>;

const renderComponent = () =>
render(
<EuiThemeProvider>
<AiInsightTest
title="AI Insight"
createStream={mockCreateStream}
buildAttachments={jest.fn().mockReturnValue([])}
/>
</EuiThemeProvider>
);

const mockOpenConversationFlyout = jest.fn();

const baseStreamingState = () => ({
isLoading: false,
error: undefined as string | undefined,
summary: '',
context: '',
wasStopped: false,
fetch: jest.fn(),
stop: jest.fn(),
regenerate: jest.fn(),
});

const createStreamingState = (overrides: Partial<ReturnType<typeof baseStreamingState>> = {}) => ({
...baseStreamingState(),
...overrides,
});

describe('AiInsight', () => {
beforeEach(() => {
jest.clearAllMocks();

mockUseUiSetting$.mockReturnValue([AIChatExperience.Agent]);
mockUseKibana.mockReturnValue({
services: {
agentBuilder: {
openConversationFlyout: mockOpenConversationFlyout,
},
application: {
capabilities: {
agentBuilder: { show: true },
},
},
},
});
mockUseLicense.mockReturnValue({
getLicense: () => ({
hasAtLeast: () => true,
}),
});
mockUseGenAIConnectors.mockReturnValue({
hasConnectors: true,
});
mockUseStreamingAiInsight.mockReturnValue(createStreamingState());
});

it('fetches insights when the accordion is opened', () => {
const fetch = jest.fn();
mockUseStreamingAiInsight.mockReturnValue(createStreamingState({ fetch }));

const { container, unmount } = renderComponent();
const toggle = container.querySelector('[data-test-subj="agentBuilderAiInsight"]');

expect(toggle).toBeTruthy();
fireEvent.click(toggle!);

expect(fetch).toHaveBeenCalledTimes(1);
unmount();
});

describe('when an error occurs', () => {
it('displays an error banner with error message', () => {
mockUseStreamingAiInsight.mockReturnValue(createStreamingState({ error: 'Boom' }));

const { container, getByText, unmount } = renderComponent();
const toggle = container.querySelector('[data-test-subj="agentBuilderAiInsight"]');
fireEvent.click(toggle!);

const errorBanner = container.querySelector('[data-test-subj="AiInsightErrorBanner"]');
expect(errorBanner).toBeTruthy();

expect(getByText('Failed to generate AI insight')).toBeTruthy();
expect(getByText('The AI insight could not be generated: Boom')).toBeTruthy();

const retryButton = container.querySelector(
'[data-test-subj="AiInsightErrorBannerRetryButton"]'
);
expect(retryButton).toBeTruthy();

unmount();
});

it('refetches insights when retry button is clicked', () => {
const fetch = jest.fn();
mockUseStreamingAiInsight.mockReturnValue(createStreamingState({ error: 'Boom', fetch }));

const { container, unmount } = renderComponent();
const toggle = container.querySelector('[data-test-subj="agentBuilderAiInsight"]');
fireEvent.click(toggle!);

const retryButton = container.querySelector(
'[data-test-subj="AiInsightErrorBannerRetryButton"]'
);
fireEvent.click(retryButton!);

expect(fetch).toHaveBeenCalledTimes(1);

unmount();
});
});

describe('when a summary has been generated', () => {
it('displays start conversation button', () => {
mockUseStreamingAiInsight.mockReturnValue(
createStreamingState({ summary: 'Hello world', context: 'context' })
);

const { container, unmount } = renderComponent();
const toggle = container.querySelector('[data-test-subj="agentBuilderAiInsight"]');
fireEvent.click(toggle!);

const startConversationButton = container.querySelector(
'[data-test-subj="aiAgentStartConversationButton"]'
);

expect(startConversationButton).toBeTruthy();

unmount();
});

it('opens the conversation flyout with correct attachments when start conversation is clicked', () => {
const buildAttachments = jest.fn().mockReturnValue([{ type: 'test', data: {} }]);
mockUseStreamingAiInsight.mockReturnValue(
createStreamingState({ summary: 'Hello world', context: 'context' })
);

const { container, unmount } = render(
<EuiThemeProvider>
<AiInsightTest
title="AI Insight"
createStream={mockCreateStream}
buildAttachments={buildAttachments}
/>
</EuiThemeProvider>
);

const toggle = container.querySelector('[data-test-subj="agentBuilderAiInsight"]');
fireEvent.click(toggle!);

const startConversationButton = container.querySelector(
'[data-test-subj="aiAgentStartConversationButton"]'
);
fireEvent.click(startConversationButton!);

expect(buildAttachments).toHaveBeenCalledWith('Hello world', 'context');
expect(mockOpenConversationFlyout).toHaveBeenCalledWith({
newConversation: true,
attachments: [{ type: 'test', data: {} }],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check for the agentId for OBSERVABILITY_AGENT_ID in here too?

agentId: OBSERVABILITY_AGENT_ID,
});

unmount();
});
});

it('shows regenerate button after stream is stopped', () => {
const regenerate = jest.fn();
mockUseStreamingAiInsight.mockReturnValue(
createStreamingState({ summary: 'Partial response', wasStopped: true, regenerate })
);

const { container, unmount } = renderComponent();

const toggle = container.querySelector('[data-test-subj="agentBuilderAiInsight"]');
fireEvent.click(toggle!);

const regenerateButton = container.querySelector(
'[data-test-subj="observabilityAgentBuilderRegenerateButton"]'
);
expect(regenerateButton).toBeTruthy();

fireEvent.click(regenerateButton!);
expect(regenerate).toHaveBeenCalledTimes(1);

unmount();
});
});
Loading
Loading