Skip to content

Commit b7793c3

Browse files
TattdCodeMonkeyclaudekibanamachine
authored andcommitted
[Search][Getting Started] Chat first view (elastic#270500)
## Summary Implementing a chat first approach for the search solution getting started page. Note: this page is only visible when the feature flag is enabled, which it is currently off for everyone. ### Screenshots <img width="1465" height="810" alt="image" src="https://github.com/user-attachments/assets/5d4d872d-6f9d-4bdf-85e9-b3661b5a4b67" /> <img width="1465" height="810" alt="image" src="https://github.com/user-attachments/assets/41af62c0-f95a-4bbd-bbe5-0dd7fe76023f" /> <img width="1465" height="810" alt="image" src="https://github.com/user-attachments/assets/b7722ab3-6b1f-478c-beb7-4e2966f3a2bb" /> ### Testing 1. Run Kibana with serverless elasticsearch & eis enabled 2. Enabled the feature flag in kibana.dev.yaml: ```yaml feature_flags.overrides: searchSolution.gettingStartedChatEnabled: true ``` ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] ~If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~ - [ ] ~This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations.~ - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] ~The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)~ - [ ] ~Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels.~ --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 0834567 commit b7793c3

29 files changed

Lines changed: 1222 additions & 58 deletions

packages/kbn-optimizer/limits.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ pageLoadAssetSize:
154154
screenshotMode: 2351
155155
screenshotting: 3252
156156
searchAssistant: 7079
157-
searchGettingStarted: 6678
157+
searchGettingStarted: 7548
158158
searchHomepage: 9005
159159
searchInferenceEndpoints: 9765
160160
searchNavigation: 8900

x-pack/platform/plugins/shared/agent_builder/public/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import type {
1616
import { AgentBuilderPlugin } from './plugin';
1717
import { AGENTBUILDER_FEATURE_ID, AGENTBUILDER_APP_ID, uiPrivileges } from '../common/features';
1818
import { type CreateSkillResponse, SKILLS_API_PATH } from '../common/http_api/skills';
19+
import { MCP_SERVER_PATH } from '../common/mcp';
1920

2021
export type {
2122
AgentBuilderPluginSetup,
2223
AgentBuilderPluginStart,
2324
PublicEmbeddableConversationProps,
2425
} from './types';
2526
export type { EmbeddableConversationProps } from './embeddable/types';
26-
export { AGENTBUILDER_FEATURE_ID, AGENTBUILDER_APP_ID, uiPrivileges };
27+
export { AGENTBUILDER_FEATURE_ID, AGENTBUILDER_APP_ID, uiPrivileges, MCP_SERVER_PATH };
2728
export { type CreateSkillResponse, SKILLS_API_PATH };
2829
export { ConversationInputShell } from '@kbn/agent-builder-browser';
2930
export type { ConversationInputShellProps } from '@kbn/agent-builder-browser';

x-pack/solutions/search/plugins/search_getting_started/kibana.jsonc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
"cloud",
2121
"console",
2222
"searchNavigation",
23+
"spaces",
2324
"usageCollection"
2425
],
2526
"requiredBundles": [
26-
"kibanaReact"
27+
"agentBuilder",
28+
"kibanaReact",
29+
"spaces",
2730
]
2831
}
2932
}

x-pack/solutions/search/plugins/search_getting_started/moon.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ dependsOn:
4545
- '@kbn/scout-search'
4646
- '@kbn/search-agent'
4747
- '@kbn/shared-ux-ai-components'
48+
- '@kbn/agent-builder-plugin'
49+
- '@kbn/spaces-plugin'
50+
- '@kbn/deeplinks-agent-builder'
51+
- '@kbn/agent-builder-common'
4852
tags:
4953
- plugin
5054
- prod
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render, screen, fireEvent } from '@testing-library/react';
10+
import { I18nProvider } from '@kbn/i18n-react';
11+
import { EuiThemeProvider } from '@elastic/eui';
12+
import { GettingStartedAgentPrompt } from './agent_prompt';
13+
import { useUsageTracker } from '../../contexts/usage_tracker_context';
14+
15+
jest.mock('../../contexts/usage_tracker_context', () => ({
16+
useUsageTracker: jest.fn(),
17+
}));
18+
19+
const mockUseUsageTracker = useUsageTracker as jest.Mock;
20+
21+
const renderComponent = () =>
22+
render(
23+
<I18nProvider>
24+
<EuiThemeProvider>
25+
<GettingStartedAgentPrompt />
26+
</EuiThemeProvider>
27+
</I18nProvider>
28+
);
29+
30+
describe('GettingStartedAgentPrompt', () => {
31+
beforeEach(() => {
32+
mockUseUsageTracker.mockReturnValue({ click: jest.fn(), count: jest.fn(), load: jest.fn() });
33+
});
34+
35+
it('does not render the modal on initial mount', () => {
36+
renderComponent();
37+
38+
expect(screen.queryByTestId('promptModalCode')).not.toBeInTheDocument();
39+
});
40+
41+
it('opens the modal when the Copy prompt button is clicked', () => {
42+
renderComponent();
43+
44+
fireEvent.click(screen.getByTestId('chatFirstAgentInstallBtn'));
45+
46+
expect(screen.getByTestId('promptModalCode')).toBeInTheDocument();
47+
});
48+
49+
it('renders the modal with prompt content when opened', () => {
50+
renderComponent();
51+
52+
fireEvent.click(screen.getByTestId('chatFirstAgentInstallBtn'));
53+
54+
expect(screen.getByTestId('promptModalCode')).not.toBeEmptyDOMElement();
55+
});
56+
57+
it('closes the modal when the Close button is clicked', () => {
58+
renderComponent();
59+
60+
fireEvent.click(screen.getByTestId('chatFirstAgentInstallBtn'));
61+
expect(screen.getByTestId('promptModalCode')).toBeInTheDocument();
62+
63+
fireEvent.click(screen.getByTestId('promptModalCloseBtn'));
64+
expect(screen.queryByTestId('promptModalCode')).not.toBeInTheDocument();
65+
});
66+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React, { useState } from 'react';
9+
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
10+
import { i18n } from '@kbn/i18n';
11+
12+
import { buildPrompt } from '../agent_install/util';
13+
import { PromptModal } from '../agent_install/prompt_modal';
14+
15+
export const GettingStartedAgentPrompt = () => {
16+
const [isPromptModalOpen, setIsPromptModalOpen] = useState(false);
17+
18+
return (
19+
<>
20+
<EuiFlexGroup direction="column" gutterSize="xs">
21+
<EuiFlexItem>
22+
<EuiTitle size="xxs">
23+
<h5>
24+
{i18n.translate('xpack.search.gettingStarted.chat.agentPrompt.title', {
25+
defaultMessage: 'Prompt your agent',
26+
})}
27+
</h5>
28+
</EuiTitle>
29+
</EuiFlexItem>
30+
<EuiFlexItem>
31+
<EuiText color="subdued" size="xs">
32+
<p>
33+
{i18n.translate('xpack.search.gettingStarted.chat.agentPrompt.description', {
34+
defaultMessage:
35+
'Set up our official optimized Elasticsearch skills in your preferred agentic code workflow.',
36+
})}
37+
</p>
38+
</EuiText>
39+
</EuiFlexItem>
40+
<EuiSpacer size="s" />
41+
<EuiFlexItem>
42+
<span>
43+
<EuiButton
44+
color="text"
45+
onClick={() => setIsPromptModalOpen(true)}
46+
iconType="copy"
47+
size="s"
48+
data-test-subj="chatFirstAgentInstallBtn"
49+
>
50+
{i18n.translate('xpack.search.gettingStarted.chat.agentPrompt.cta', {
51+
defaultMessage: 'Copy prompt',
52+
})}
53+
</EuiButton>
54+
</span>
55+
</EuiFlexItem>
56+
</EuiFlexGroup>
57+
{isPromptModalOpen && (
58+
<PromptModal prompt={buildPrompt('cli')} onClose={() => setIsPromptModalOpen(false)} />
59+
)}
60+
</>
61+
);
62+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
10+
11+
import { ChatElasticsearchConnectionDetails } from './connection_details';
12+
import { ConversationPrompt } from './conversation_prompt';
13+
import { ChatColumnsGrid, ChatContentSeparator } from './styles';
14+
import { GettingStartedAgentPrompt } from './agent_prompt';
15+
16+
export const GettingStartedChatContent = () => {
17+
return (
18+
<EuiFlexGrid data-test-subj="gettingStartedChatContent" columns={2} css={ChatColumnsGrid}>
19+
<EuiFlexItem css={ChatContentSeparator}>
20+
<ConversationPrompt />
21+
</EuiFlexItem>
22+
<EuiFlexItem>
23+
<EuiFlexGroup direction="column" gutterSize="l">
24+
<EuiFlexItem>
25+
<ChatElasticsearchConnectionDetails />
26+
</EuiFlexItem>
27+
<EuiFlexItem>
28+
<GettingStartedAgentPrompt />
29+
</EuiFlexItem>
30+
</EuiFlexGroup>
31+
</EuiFlexItem>
32+
</EuiFlexGrid>
33+
);
34+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
10+
import { i18n } from '@kbn/i18n';
11+
12+
import { DeploymentStatusBadges } from '../header/deployment_status_badges';
13+
import { ChatColumnsGrid, ChatStretchedFlexItem } from './styles';
14+
15+
export const ChatHeader = () => {
16+
return (
17+
<EuiFlexGroup gutterSize="l" alignItems="flexStart" direction="column">
18+
<EuiFlexItem css={ChatStretchedFlexItem}>
19+
<DeploymentStatusBadges />
20+
</EuiFlexItem>
21+
<EuiFlexItem>
22+
<EuiFlexGrid columns={2} css={ChatColumnsGrid}>
23+
<EuiFlexItem>
24+
<EuiTitle size="l">
25+
<h1>
26+
{i18n.translate('xpack.search.gettingStarted.chatPage.title', {
27+
defaultMessage: 'Bring your data and start building your next search experience.',
28+
})}
29+
</h1>
30+
</EuiTitle>
31+
</EuiFlexItem>
32+
</EuiFlexGrid>
33+
</EuiFlexItem>
34+
</EuiFlexGroup>
35+
);
36+
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render, screen, fireEvent } from '@testing-library/react';
10+
import { I18nProvider } from '@kbn/i18n-react';
11+
import { EuiThemeProvider } from '@elastic/eui';
12+
import { ChatElasticsearchConnectionDetails } from './connection_details';
13+
import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';
14+
import { useAgentBuilderMcpUrl } from '../../hooks/use_mcp_url';
15+
16+
jest.mock('../../hooks/use_elasticsearch_url');
17+
jest.mock('../../hooks/use_mcp_url');
18+
jest.mock('@kbn/search-api-keys-components', () => ({
19+
ApiKeyForm: () => <div data-test-subj="apiKeyForm" />,
20+
}));
21+
22+
const mockUseElasticsearchUrl = useElasticsearchUrl as jest.Mock;
23+
const mockUseAgentBuilderMcpUrl = useAgentBuilderMcpUrl as jest.Mock;
24+
25+
const MOCK_ES_URL = 'https://my-deployment.es.us-east-1.aws.elastic.cloud';
26+
const MOCK_MCP_URL = 'https://my-kibana.kb.us-east-1.aws.elastic.cloud/api/agent_builder/mcp';
27+
28+
const renderComponent = () =>
29+
render(
30+
<I18nProvider>
31+
<EuiThemeProvider>
32+
<ChatElasticsearchConnectionDetails />
33+
</EuiThemeProvider>
34+
</I18nProvider>
35+
);
36+
37+
describe('ChatElasticsearchConnectionDetails', () => {
38+
beforeEach(() => {
39+
mockUseElasticsearchUrl.mockReturnValue(MOCK_ES_URL);
40+
mockUseAgentBuilderMcpUrl.mockReturnValue(MOCK_MCP_URL);
41+
});
42+
43+
it('shows the Elasticsearch URL by default', () => {
44+
renderComponent();
45+
46+
expect(screen.getByTestId('endpointValueField')).toHaveTextContent(MOCK_ES_URL);
47+
expect(screen.queryByTestId('mcpEndpointValueField')).not.toBeInTheDocument();
48+
});
49+
50+
it('switches to the MCP URL when the MCP badge is clicked', () => {
51+
renderComponent();
52+
53+
fireEvent.click(screen.getByTestId('viewMCPUrlBtn'));
54+
55+
expect(screen.getByTestId('mcpEndpointValueField')).toHaveTextContent(MOCK_MCP_URL);
56+
expect(screen.queryByTestId('endpointValueField')).not.toBeInTheDocument();
57+
});
58+
59+
it('switches back to the Elasticsearch URL when the Elasticsearch badge is clicked', () => {
60+
renderComponent();
61+
62+
fireEvent.click(screen.getByTestId('viewMCPUrlBtn'));
63+
fireEvent.click(screen.getByTestId('viewElasticsearchUrlBtn'));
64+
65+
expect(screen.getByTestId('endpointValueField')).toHaveTextContent(MOCK_ES_URL);
66+
expect(screen.queryByTestId('mcpEndpointValueField')).not.toBeInTheDocument();
67+
});
68+
69+
describe('badge colors', () => {
70+
it('Elasticsearch badge is default color and MCP badge is hollow on initial render', () => {
71+
renderComponent();
72+
73+
const esBadge = screen.getByTestId('viewElasticsearchUrlBtn');
74+
const mcpBadge = screen.getByTestId('viewMCPUrlBtn');
75+
76+
// EuiBadge uses CSS-in-JS; check the className string for the variant name
77+
expect(esBadge.closest('.euiBadge')?.className).not.toContain('hollow');
78+
expect(mcpBadge.closest('.euiBadge')?.className).toContain('hollow');
79+
});
80+
81+
it('MCP badge becomes default color and Elasticsearch badge becomes hollow after switching', () => {
82+
renderComponent();
83+
84+
fireEvent.click(screen.getByTestId('viewMCPUrlBtn'));
85+
86+
const esBadge = screen.getByTestId('viewElasticsearchUrlBtn');
87+
const mcpBadge = screen.getByTestId('viewMCPUrlBtn');
88+
89+
expect(mcpBadge.closest('.euiBadge')?.className).not.toContain('hollow');
90+
expect(esBadge.closest('.euiBadge')?.className).toContain('hollow');
91+
});
92+
});
93+
94+
it('always renders the ApiKeyForm regardless of URL view', () => {
95+
renderComponent();
96+
97+
expect(screen.getByTestId('apiKeyForm')).toBeInTheDocument();
98+
99+
fireEvent.click(screen.getByTestId('viewMCPUrlBtn'));
100+
101+
expect(screen.getByTestId('apiKeyForm')).toBeInTheDocument();
102+
});
103+
});

0 commit comments

Comments
 (0)