[8.14] [Serverless] Playground in Serverless (#181474) (#181988)

# Backport

This will backport the following commits from `main` to `8.14`:
- [[Serverless] Playground in Serverless
(#181474)](https://github.com/elastic/kibana/pull/181474)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Samiul
Monir","email":"150824886+Samiul-TheSoccerFan@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-04-26T18:12:51Z","message":"[Serverless]
Playground in Serverless (#181474)\n\n## Summary\r\n\r\nThis
PR:\r\n\r\n- Integrate Playground into Serverless\r\n- Redesign of
Navigation Menu\r\n- Refactor Playground docs\r\n\r\n## UI
changes:\r\n### Playground in
Serverless\r\n\r\n![img-1](772d5812-e8ea-41ee-a875-4204fff3e948)\r\n\r\n###
Playground with docs and
indices\r\n\r\n![img-2](5545dc3e-bf7d-45c0-9f4a-250dd9c63f75)\r\n\r\n###
Playground in
action\r\n\r\n\r\n![img-3](a7088863-6dd9-4c4a-9760-e168d37f16c2)\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[ ] If a plugin configuration key changed, check if it needs to
be\r\nallowlisted in the cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[ ] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"41fd6432be054eff743bfbf2a3f4402a0ae42733","branchLabelMapping":{"^v8.15.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:EnterpriseSearch","v8.14.0","v8.15.0"],"number":181474,"url":"https://github.com/elastic/kibana/pull/181474","mergeCommit":{"message":"[Serverless]
Playground in Serverless (#181474)\n\n## Summary\r\n\r\nThis
PR:\r\n\r\n- Integrate Playground into Serverless\r\n- Redesign of
Navigation Menu\r\n- Refactor Playground docs\r\n\r\n## UI
changes:\r\n### Playground in
Serverless\r\n\r\n![img-1](772d5812-e8ea-41ee-a875-4204fff3e948)\r\n\r\n###
Playground with docs and
indices\r\n\r\n![img-2](5545dc3e-bf7d-45c0-9f4a-250dd9c63f75)\r\n\r\n###
Playground in
action\r\n\r\n\r\n![img-3](a7088863-6dd9-4c4a-9760-e168d37f16c2)\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[ ] If a plugin configuration key changed, check if it needs to
be\r\nallowlisted in the cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[ ] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"41fd6432be054eff743bfbf2a3f4402a0ae42733"}},"sourceBranch":"main","suggestedTargetBranches":["8.14"],"targetPullRequestStates":[{"branch":"8.14","label":"v8.14.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.15.0","labelRegex":"^v8.15.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/181474","number":181474,"mergeCommit":{"message":"[Serverless]
Playground in Serverless (#181474)\n\n## Summary\r\n\r\nThis
PR:\r\n\r\n- Integrate Playground into Serverless\r\n- Redesign of
Navigation Menu\r\n- Refactor Playground docs\r\n\r\n## UI
changes:\r\n### Playground in
Serverless\r\n\r\n![img-1](772d5812-e8ea-41ee-a875-4204fff3e948)\r\n\r\n###
Playground with docs and
indices\r\n\r\n![img-2](5545dc3e-bf7d-45c0-9f4a-250dd9c63f75)\r\n\r\n###
Playground in
action\r\n\r\n\r\n![img-3](a7088863-6dd9-4c4a-9760-e168d37f16c2)\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI
touched in this PR does not create any new axe failures\r\n(run axe in
browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n-
[ ] If a plugin configuration key changed, check if it needs to
be\r\nallowlisted in the cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This renders correctly on smaller devices using a
responsive\r\nlayout. (You can test this [in
your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n-
[ ] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"41fd6432be054eff743bfbf2a3f4402a0ae42733"}}]}]
BACKPORT-->

Co-authored-by: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com>
This commit is contained in:
Rodney Norris 2024-04-29 10:23:17 -05:00 committed by GitHub
parent a4cb49799d
commit 94e4ede666
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 499 additions and 145 deletions

View file

@ -28,8 +28,9 @@ xpack.serverless.plugin.developer.projectSwitcher.currentType: 'search'
# Specify in telemetry the project type
telemetry.labels.serverless: search
# Alerts config
xpack.actions.enabledActionTypes: ['.email', '.index', '.slack', '.jira', '.webhook', '.teams']
# Alerts and LLM config
xpack.actions.enabledActionTypes:
['.email', '.index', '.slack', '.jira', '.webhook', '.teams', '.gen-ai', '.bedrock']
# Customize empty page state for analytics apps
no_data_page.analyticsNoDataPageFlavor: 'serverless_search'
@ -43,3 +44,6 @@ xpack.ml.nlp.enabled: true
xpack.ml.compatibleModuleType: 'search'
data_visualizer.resultLinks.fileBeat.enabled: false
# Search Playground
xpack.searchPlayground.ui.enabled: false

View file

@ -14,3 +14,4 @@ export const ENTERPRISE_SEARCH_APPSEARCH_APP_ID = 'appSearch';
export const ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID = 'workplaceSearch';
export const SERVERLESS_ES_APP_ID = 'serverlessElasticsearch';
export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors';
export const SERVERLESS_ES_SEARCH_PLAYGROUND_ID = 'searchPlayground';

View file

@ -15,6 +15,7 @@ import {
ENTERPRISE_SEARCH_ANALYTICS_APP_ID,
ENTERPRISE_SEARCH_APPSEARCH_APP_ID,
ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID,
SERVERLESS_ES_SEARCH_PLAYGROUND_ID,
} from './constants';
export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID;
@ -25,6 +26,7 @@ export type EnterpriseSearchAppsearchApp = typeof ENTERPRISE_SEARCH_APPSEARCH_AP
export type EnterpriseSearchWorkplaceSearchApp = typeof ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID;
export type ServerlessSearchApp = typeof SERVERLESS_ES_APP_ID;
export type ConnectorsId = typeof SERVERLESS_ES_CONNECTORS_ID;
export type SearchPlaygroundId = typeof SERVERLESS_ES_SEARCH_PLAYGROUND_ID;
export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers';
@ -41,6 +43,7 @@ export type DeepLinkId =
| EnterpriseSearchWorkplaceSearchApp
| ServerlessSearchApp
| ConnectorsId
| SearchPlaygroundId
| `${EnterpriseSearchContentApp}:${ContentLinkId}`
| `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}`
| `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}`;

View file

@ -206,7 +206,6 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
licenseManagement: `${ENTERPRISE_SEARCH_DOCS}license-management.html`,
machineLearningStart: `${ELASTICSEARCH_DOCS}nlp-example.html`,
mailService: `${ENTERPRISE_SEARCH_DOCS}mailer-configuration.html`,
playground: `${KIBANA_DOCS}playground.html`,
mlDocumentEnrichment: `${ELASTICSEARCH_DOCS}ingest-pipeline-search-inference.html`,
searchApplicationsTemplates: `${ELASTICSEARCH_DOCS}search-application-api.html`,
searchApplicationsSearchApi: `${ELASTICSEARCH_DOCS}search-application-security.html`,
@ -939,5 +938,8 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
telemetry: {
settings: `${KIBANA_DOCS}telemetry-settings-kbn.html`,
},
playground: {
chatPlayground: `${KIBANA_DOCS}playground.html`,
},
});
};

View file

@ -170,7 +170,6 @@ export interface DocLinks {
readonly languageClients: string;
readonly licenseManagement: string;
readonly machineLearningStart: string;
readonly playground: string;
readonly mailService: string;
readonly mlDocumentEnrichment: string;
readonly searchApplicationsTemplates: string;
@ -648,6 +647,9 @@ export interface DocLinks {
readonly telemetry: {
readonly settings: string;
};
readonly playground: {
readonly chatPlayground: string;
};
}
export type BuildFlavor = 'serverless' | 'traditional';

View file

@ -311,6 +311,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
// 'xpack.reporting.poll.jobsRefresh.intervalErrorMultiplier (number)',
'xpack.rollup.ui.enabled (boolean)',
'xpack.saved_object_tagging.cache_refresh_interval (duration)',
'xpack.searchPlayground.ui.enabled (boolean)',
'xpack.security.loginAssistanceMessage (string)',
'xpack.security.sameSiteCookies (alternatives)',
'xpack.security.showInsecureClusterWarning (boolean)',

View file

@ -7,25 +7,21 @@
import React from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { useValues } from 'kea';
import { i18n } from '@kbn/i18n';
import { KibanaLogic } from '../../../shared/kibana';
import { docLinks } from '../../../shared/doc_links';
import { EndpointsHeaderAction } from '../../../shared/layout/endpoints_header_action';
export const PlaygroundHeaderDocsAction: React.FC = () => (
<EndpointsHeaderAction>
<EuiButtonEmpty
data-telemetry-id="entSearchApplications-playground-documentationLink"
data-test-subj="playground-documentation-link"
href={docLinks.playground}
target="_blank"
iconType="documents"
>
{i18n.translate('xpack.enterpriseSearch.content.playground.header.docLink', {
defaultMessage: 'Playground Docs',
})}
</EuiButtonEmpty>
</EndpointsHeaderAction>
);
export const PlaygroundHeaderDocsAction: React.FC = () => {
const { searchPlayground } = useValues(KibanaLogic);
if (!searchPlayground) {
return null;
}
return (
<EndpointsHeaderAction>
<searchPlayground.PlaygroundHeaderDocs />
</EndpointsHeaderAction>
);
};

View file

@ -124,7 +124,6 @@ class DocLinks {
public licenseManagement: string;
public machineLearningStart: string;
public mlDocumentEnrichment: string;
public playground: string;
public pluginsIngestAttachment: string;
public queryDsl: string;
public restApis: string;
@ -302,7 +301,6 @@ class DocLinks {
this.licenseManagement = '';
this.machineLearningStart = '';
this.mlDocumentEnrichment = '';
this.playground = '';
this.pluginsIngestAttachment = '';
this.queryDsl = '';
this.restApis = '';
@ -482,7 +480,6 @@ class DocLinks {
this.licenseManagement = docLinks.links.enterpriseSearch.licenseManagement;
this.machineLearningStart = docLinks.links.enterpriseSearch.machineLearningStart;
this.mlDocumentEnrichment = docLinks.links.enterpriseSearch.mlDocumentEnrichment;
this.playground = docLinks.links.enterpriseSearch.playground;
this.pluginsIngestAttachment = docLinks.links.plugins.ingestAttachment;
this.queryDsl = docLinks.links.query.queryDsl;
this.restApis = docLinks.links.apis.restApis;

View file

@ -14,6 +14,7 @@ const createStartMock = (): Start => {
PlaygroundProvider: jest.fn(),
PlaygroundToolbar: jest.fn(),
Playground: jest.fn(),
PlaygroundHeaderDocs: jest.fn(),
};
return startContract;

View file

@ -0,0 +1,20 @@
/*
* 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 { DocLinks } from '@kbn/doc-links';
class PlaygroundDocLinks {
public chatPlayground: string = '';
constructor() {}
setDocLinks(newDocLinks: DocLinks) {
this.chatPlayground = newDocLinks.playground.chatPlayground;
}
}
export const docLinks = new PlaygroundDocLinks();

View file

@ -0,0 +1,10 @@
/*
* 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 function isNotNullish<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}

View file

@ -25,6 +25,7 @@ export enum APIRoutes {
POST_API_KEY = '/internal/search_playground/api_key',
POST_CHAT_MESSAGE = '/internal/search_playground/chat',
POST_QUERY_SOURCE_FIELDS = '/internal/search_playground/query_source_fields',
GET_INDICES = '/internal/search_playground/indices',
}
export enum LLMs {
@ -43,3 +44,9 @@ export interface ChatRequestData {
source_fields: string;
doc_size: number;
}
export interface SearchPlaygroundConfigType {
ui: {
enabled: boolean;
};
}

View file

@ -14,6 +14,7 @@
"actions",
"encryptedSavedObjects",
"navigation",
"share",
"security",
"stackConnectors",
"triggersActionsUi",

View file

@ -7,53 +7,26 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { CoreStart, AppMountParameters } from '@kbn/core/public';
import { CoreStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { I18nProvider } from '@kbn/i18n-react';
import { BrowserRouter as Router } from '@kbn/shared-ux-router';
import { i18n } from '@kbn/i18n';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { PLUGIN_ID } from '../common';
import { Router } from '@kbn/shared-ux-router';
import { AppPluginStartDependencies } from './types';
import { App } from './components/app';
import { PlaygroundProvider } from './providers/playground_provider';
export const renderApp = (
export const renderApp = async (
core: CoreStart,
services: AppPluginStartDependencies,
{ appBasePath, element }: AppMountParameters
element: HTMLElement
) => {
const navigation = services.navigation;
const { PlaygroundRouter } = await import('./playground_router');
ReactDOM.render(
<KibanaThemeProvider theme={core.theme}>
<KibanaContextProvider services={{ ...core, ...services }}>
<I18nProvider>
<Router basename={appBasePath}>
<navigation.ui.TopNavMenu appName={PLUGIN_ID} />
<PlaygroundProvider
defaultValues={{
indices: [],
}}
>
<KibanaPageTemplate
pageChrome={[
i18n.translate('xpack.searchPlayground.breadcrumb', {
defaultMessage: 'Playground',
}),
]}
pageHeader={{
pageTitle: i18n.translate('xpack.searchPlayground.pageTitle', {
defaultMessage: 'Playground',
}),
}}
bottomBorder="extended"
restrictWidth={false}
>
<App />
</KibanaPageTemplate>
</PlaygroundProvider>
<Router history={services.history}>
<PlaygroundRouter />
</Router>
</I18nProvider>
</KibanaContextProvider>

View file

@ -0,0 +1,37 @@
/*
* 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';
import React from 'react';
import { EuiPageTemplate } from '@elastic/eui';
import { PlaygroundProvider } from './providers/playground_provider';
import { App } from './components/app';
import { PlaygroundToolbar } from './embeddable';
import { PlaygroundHeaderDocs } from './components/playground_header_docs';
export const ChatPlaygroundOverview: React.FC = () => {
return (
<PlaygroundProvider
defaultValues={{
indices: [],
}}
>
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlPlaygroundPage">
<EuiPageTemplate.Header
pageTitle={i18n.translate('xpack.searchPlayground.pageTitle', {
defaultMessage: 'Playground',
})}
data-test-subj="svlPlaygroundPageTitle"
restrictWidth
rightSideItems={[<PlaygroundHeaderDocs />, <PlaygroundToolbar />]}
/>
<App />
</EuiPageTemplate>
</PlaygroundProvider>
);
};

View file

@ -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 React from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { docLinks } from '../../common/doc_links';
export const PlaygroundHeaderDocs: React.FC = () => (
<EuiButtonEmpty
data-telemetry-id="playground-header-documentationLink"
data-test-subj="playground-documentation-link"
href={docLinks.chatPlayground}
target="_blank"
iconType="documents"
>
{i18n.translate('xpack.searchPlayground.pageTitle.header.docLink', {
defaultMessage: 'Playground Docs',
})}
</EuiButtonEmpty>
);

View file

@ -23,6 +23,10 @@ export const PlaygroundProvider = dynamic(async () => ({
default: (await import('./providers/playground_provider')).PlaygroundProvider,
}));
export const PlaygroundHeaderDocs = dynamic(async () => ({
default: (await import('./components/playground_header_docs')).PlaygroundHeaderDocs,
}));
export const getPlaygroundProvider =
(core: CoreStart, services: AppPluginStartDependencies) =>
(props: React.ComponentProps<typeof PlaygroundProvider>) =>

View file

@ -14,7 +14,7 @@ export const useManagementLink = (connectorId: string) => {
} = useKibana();
const managementLocator = useMemo(
() => share.url.locators.get('MANAGEMENT_APP_LOCATOR'),
[share]
[share.url.locators]
);
const [managementLink, setManagementLink] = useState('');
useEffect(() => {

View file

@ -0,0 +1,38 @@
/*
* 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 { useQueryIndices } from './use_query_indices';
jest.mock('./use_kibana', () => ({
useKibana: jest.fn().mockReturnValue({
services: {
services: {
http: {},
},
},
}),
}));
jest.mock('./use_query_indices', () => ({
useQueryIndices: jest.fn(),
}));
describe('useQueryIndices Hook', () => {
afterEach(jest.clearAllMocks);
it('successfully loads indices', async () => {
const mockUseQueryIndices = (data: string[]) => {
(useQueryIndices as jest.Mock).mockReturnValue({ indices: data, isLoading: false });
};
const mockIndices = ['index-1', 'index-2'];
mockUseQueryIndices(mockIndices);
const { result } = renderHook(() => useQueryIndices());
expect(result.current).toEqual({ indices: ['index-1', 'index-2'], isLoading: false });
});
});

View file

@ -8,7 +8,7 @@
import { useQuery } from '@tanstack/react-query';
import { IndexName } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { useKibana } from './use_kibana';
import { ElasticsearchIndex } from '../types';
import { APIRoutes } from '../types';
export const useQueryIndices = (
query: string = ''
@ -19,18 +19,15 @@ export const useQueryIndices = (
queryKey: ['indices', query],
queryFn: async () => {
const response = await services.http.get<{
indices: ElasticsearchIndex[];
}>('/internal/enterprise_search/indices', {
indices: string[];
}>(APIRoutes.GET_INDICES, {
query: {
from: 0,
only_show_search_optimized_indices: false,
return_hidden_indices: false,
search_query: query,
size: 20,
size: 10,
},
});
return response.indices.map((index) => index.name);
return response.indices;
},
});

View file

@ -5,10 +5,12 @@
* 2.0.
*/
import { PluginInitializerContext } from '@kbn/core/public';
import { SearchPlaygroundPlugin } from './plugin';
export function plugin() {
return new SearchPlaygroundPlugin();
export function plugin(initializerContext: PluginInitializerContext) {
return new SearchPlaygroundPlugin(initializerContext);
}
export type { SearchPlaygroundPluginSetup, SearchPlaygroundPluginStart } from './types';

View file

@ -0,0 +1,24 @@
/*
* 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 { Route, Routes } from '@kbn/shared-ux-router';
import React from 'react';
import { Redirect } from 'react-router-dom';
import { ChatPlaygroundOverview } from './chat_playground_overview';
import { ROOT_PATH, SEARCH_PLAYGROUND_CHAT_PATH } from './routes';
export const PlaygroundRouter: React.FC = () => {
return (
<Routes>
<Redirect exact from={ROOT_PATH} to={SEARCH_PLAYGROUND_CHAT_PATH} />
<Route path={SEARCH_PLAYGROUND_CHAT_PATH}>
<ChatPlaygroundOverview />
</Route>
</Routes>
);
};

View file

@ -5,11 +5,20 @@
* 2.0.
*/
import { CoreSetup, Plugin, CoreStart, AppMountParameters } from '@kbn/core/public';
import {
CoreSetup,
Plugin,
CoreStart,
AppMountParameters,
PluginInitializerContext,
} from '@kbn/core/public';
import { PLUGIN_ID, PLUGIN_NAME } from '../common';
import { docLinks } from '../common/doc_links';
import { PlaygroundHeaderDocs } from './components/playground_header_docs';
import { PlaygroundToolbar, Playground, getPlaygroundProvider } from './embeddable';
import {
AppPluginStartDependencies,
SearchPlaygroundConfigType,
SearchPlaygroundPluginSetup,
SearchPlaygroundPluginStart,
} from './types';
@ -17,27 +26,43 @@ import {
export class SearchPlaygroundPlugin
implements Plugin<SearchPlaygroundPluginSetup, SearchPlaygroundPluginStart>
{
public setup(core: CoreSetup): SearchPlaygroundPluginSetup {
return {};
private config: SearchPlaygroundConfigType;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get<SearchPlaygroundConfigType>();
}
public setup(
core: CoreSetup<AppPluginStartDependencies, SearchPlaygroundPluginStart>
): SearchPlaygroundPluginSetup {
if (!this.config.ui?.enabled) return {};
core.application.register({
id: PLUGIN_ID,
appRoute: '/app/search_playground',
title: PLUGIN_NAME,
async mount(params: AppMountParameters) {
async mount({ element, history }: AppMountParameters) {
const { renderApp } = await import('./application');
const [coreStart, depsStart] = await core.getStartServices();
const startDeps: AppPluginStartDependencies = {
...depsStart,
history,
};
return renderApp(coreStart, depsStart as AppPluginStartDependencies, params);
return renderApp(coreStart, startDeps, element);
},
});
return {};
}
public start(core: CoreStart, deps: AppPluginStartDependencies): SearchPlaygroundPluginStart {
docLinks.setDocLinks(core.docLinks.links);
return {
PlaygroundProvider: getPlaygroundProvider(core, deps),
PlaygroundToolbar,
Playground,
PlaygroundHeaderDocs,
};
}

View file

@ -0,0 +1,9 @@
/*
* 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 ROOT_PATH = '/';
export const SEARCH_PLAYGROUND_CHAT_PATH = `${ROOT_PATH}chat`;

View file

@ -19,10 +19,12 @@ import React, { ComponentType } from 'react';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { CloudSetup } from '@kbn/cloud-plugin/public';
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
import { AppMountParameters } from '@kbn/core/public';
import { ChatRequestData } from '../common/types';
import type { App } from './components/app';
import type { PlaygroundProvider as PlaygroundProviderComponent } from './providers/playground_provider';
import type { Toolbar } from './components/toolbar';
import { PlaygroundHeaderDocs } from './components/playground_header_docs';
export * from '../common/types';
@ -32,11 +34,14 @@ export interface SearchPlaygroundPluginStart {
PlaygroundProvider: React.FC<React.ComponentProps<typeof PlaygroundProviderComponent>>;
PlaygroundToolbar: React.FC<React.ComponentProps<typeof Toolbar>>;
Playground: React.FC<React.ComponentProps<typeof App>>;
PlaygroundHeaderDocs: React.FC<React.ComponentProps<typeof PlaygroundHeaderDocs>>;
}
export interface AppPluginStartDependencies {
history: AppMountParameters['history'];
navigation: NavigationPublicPluginStart;
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
share: SharePluginStart;
}
export interface AppServicesContext {

View file

@ -0,0 +1,27 @@
/*
* 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 { schema, TypeOf } from '@kbn/config-schema';
import { PluginConfigDescriptor } from '@kbn/core/server';
export * from './types';
const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
ui: schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),
});
export type SearchPlaygroundConfig = TypeOf<typeof configSchema>;
export const config: PluginConfigDescriptor<SearchPlaygroundConfig> = {
exposeToBrowser: {
ui: true,
},
schema: configSchema,
};

View file

@ -7,6 +7,8 @@
import { PluginInitializerContext } from '@kbn/core/server';
export { config } from './config';
export async function plugin(initializerContext: PluginInitializerContext) {
const { SearchPlaygroundPlugin } = await import('./plugin');
return new SearchPlaygroundPlugin(initializerContext);

View file

@ -6,7 +6,7 @@
*/
import type { Client } from '@elastic/elasticsearch';
import { createAssist as Assist } from './assist';
import { createAssist as Assist } from '../utils/assist';
import { ConversationalChain } from './conversational_chain';
import { FakeListLLM } from 'langchain/llms/fake';
import { BaseChatModel } from '@langchain/core/language_models/chat_models';

View file

@ -17,10 +17,10 @@ import {
} from 'ai';
import { BaseLanguageModel } from '@langchain/core/language_models/base';
import { ElasticsearchRetriever } from './elasticsearch_retriever';
import { renderTemplate } from './render_template';
import { renderTemplate } from '../utils/render_template';
import { AssistClient } from './assist';
import { getCitations } from './get_citations';
import { AssistClient } from '../utils/assist';
import { getCitations } from '../utils/get_citations';
interface RAGOptions {
index: string;

View file

@ -0,0 +1,54 @@
/*
* 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 { ElasticsearchClient } from '@kbn/core/server';
import { fetchIndices } from './fetch_indices';
describe('fetch indices', () => {
const mockIndexResponse = {
'index-1': {
aliases: {
'search-alias-1': {},
'search-alias-2': {},
},
},
'index-2': {
aliases: {
'search-alias-1': {},
'search-alias-2': {},
},
},
'index-3': {
aliases: {
'search-alias-1': {},
'search-alias-2': {},
},
},
};
beforeEach(() => {
jest.clearAllMocks();
});
const mockClient = {
asCurrentUser: { indices: { get: jest.fn() } },
};
it('returns index data with for non-hidden indices', async () => {
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => {
return mockIndexResponse;
});
const indexData = await fetchIndices(
mockClient.asCurrentUser as unknown as ElasticsearchClient,
undefined
);
expect(indexData).toEqual({
indexNames: ['index-1', 'index-2', 'index-3'],
});
});
});

View file

@ -0,0 +1,52 @@
/*
* 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 { IndicesIndexState } from '@elastic/elasticsearch/lib/api/types';
import { ElasticsearchClient } from '@kbn/core/server';
function isHidden(index: IndicesIndexState): boolean {
return index.settings?.index?.hidden === true || index.settings?.index?.hidden === 'true';
}
function isClosed(index: IndicesIndexState): boolean {
return (
index.settings?.index?.verified_before_close === true ||
index.settings?.index?.verified_before_close === 'true'
);
}
export const fetchIndices = async (
client: ElasticsearchClient,
searchQuery: string | undefined
): Promise<{
indexNames: string[];
}> => {
const indexPattern = searchQuery ? `*${searchQuery}*` : '*';
const allIndexMatches = await client.indices.get({
expand_wildcards: ['open'],
// for better performance only compute aliases and settings of indices but not mappings
features: ['aliases', 'settings'],
// only get specified index properties from ES to keep the response under 536MB
// node.js string length limit: https://github.com/nodejs/node/issues/33960
filter_path: ['*.aliases', '*.settings.index.hidden', '*.settings.index.verified_before_close'],
index: indexPattern,
});
const allIndexNames = Object.keys(allIndexMatches).filter(
(indexName) =>
allIndexMatches[indexName] &&
!isHidden(allIndexMatches[indexName]) &&
!isClosed(allIndexMatches[indexName])
);
const indexNames = searchQuery
? allIndexNames.filter((indexName) => indexName.includes(searchQuery.toLowerCase()))
: allIndexNames;
return {
indexNames,
};
};

View file

@ -9,16 +9,18 @@ import { schema } from '@kbn/config-schema';
import { streamFactory } from '@kbn/ml-response-stream/server';
import type { Logger } from '@kbn/logging';
import { IRouter, StartServicesAccessor } from '@kbn/core/server';
import { fetchFields } from './utils/fetch_query_source_fields';
import { fetchFields } from './lib/fetch_query_source_fields';
import { AssistClientOptionsWithClient, createAssist as Assist } from './utils/assist';
import { ConversationalChain } from './utils/conversational_chain';
import { ConversationalChain } from './lib/conversational_chain';
import { errorHandler } from './utils/error_handler';
import {
APIRoutes,
SearchPlaygroundPluginStart,
SearchPlaygroundPluginStartDependencies,
} from './types';
import { getChatParams } from './utils/get_chat_params';
import { getChatParams } from './lib/get_chat_params';
import { fetchIndices } from './lib/fetch_indices';
import { isNotNullish } from '../common/is_not_nullish';
export function createRetriever(esQuery: string) {
return (question: string) => {
@ -199,4 +201,35 @@ export function defineRoutes({
});
})
);
// SECURITY: We don't apply any authorization tags to this route because all actions performed
// on behalf of the user making the request and governed by the user's own cluster privileges.
router.get(
{
path: APIRoutes.GET_INDICES,
validate: {
query: schema.object({
search_query: schema.maybe(schema.string()),
size: schema.number({ defaultValue: 10, min: 0 }),
}),
},
},
errorHandler(async (context, request, response) => {
const { search_query: searchQuery, size } = request.query;
const {
client: { asCurrentUser },
} = (await context.core).elasticsearch;
const { indexNames } = await fetchIndices(asCurrentUser, searchQuery);
const indexNameSlice = indexNames.slice(0, size).filter(isNotNullish);
return response.ok({
body: {
indices: indexNameSlice,
},
headers: { 'content-type': 'application/json' },
});
})
);
}

View file

@ -34,7 +34,8 @@
"@kbn/cases-plugin",
"@kbn/triggers-actions-ui-plugin",
"@kbn/elastic-assistant-common",
"@kbn/logging"
"@kbn/logging",
"@kbn/doc-links"
],
"exclude": [
"target/**/*",

View file

@ -11,7 +11,6 @@ import { CONNECTORS_LABEL } from '../common/i18n_string';
export const navigationTree: NavigationTreeDefinition = {
body: [
{ type: 'recentlyAccessed' },
{
type: 'navGroup',
id: 'search_project_nav',
@ -38,61 +37,66 @@ export const navigationTree: NavigationTreeDefinition = {
getIsActive: ({ pathNameSerialized, prepend }) => {
return pathNameSerialized.startsWith(prepend('/app/dev_tools'));
},
spaceBefore: 'm',
},
{
link: 'discover',
spaceBefore: 'm',
},
{
link: 'dashboards',
getIsActive: ({ pathNameSerialized, prepend }) => {
return pathNameSerialized.startsWith(prepend('/app/dashboards'));
},
},
{
link: 'management:triggersActions',
title: i18n.translate('xpack.serverlessSearch.nav.alerts', {
defaultMessage: 'Alerts',
id: 'kibana',
title: i18n.translate('xpack.serverlessSearch.nav.kibana', {
defaultMessage: 'Kibana',
}),
},
{
title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
defaultMessage: 'Index Management',
}),
link: 'management:index_management',
breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */,
spaceBefore: 'm',
children: [
{
link: 'discover',
},
{
link: 'dashboards',
getIsActive: ({ pathNameSerialized, prepend }) => {
return pathNameSerialized.startsWith(prepend('/app/dashboards'));
},
},
],
},
{
title: i18n.translate('xpack.serverlessSearch.nav.content.pipelines', {
defaultMessage: 'Pipelines',
id: 'content',
title: i18n.translate('xpack.serverlessSearch.nav.content', {
defaultMessage: 'Content',
}),
link: 'management:ingest_pipelines',
breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */,
},
{
title: CONNECTORS_LABEL,
link: 'serverlessConnectors',
},
{
link: 'management:api_keys',
breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */,
spaceBefore: 'm',
children: [
{
title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
defaultMessage: 'Index Management',
}),
link: 'management:index_management',
breadcrumbStatus:
'hidden' /* management sub-pages set their breadcrumbs themselves */,
},
{
title: CONNECTORS_LABEL,
link: 'serverlessConnectors',
},
],
},
{
id: 'build',
title: i18n.translate('xpack.serverlessSearch.nav.build', {
defaultMessage: 'Build',
}),
spaceBefore: 'm',
children: [
{
id: 'searchPlayground',
title: i18n.translate('xpack.serverlessSearch.nav.build.searchPlayground', {
defaultMessage: 'Playground',
}),
link: 'searchPlayground',
},
],
},
],
},
],
footer: [
{
type: 'navItem',
id: 'search_getting_started',
title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
defaultMessage: 'Get started',
}),
icon: 'launch',
link: 'serverlessElasticsearch',
},
{
type: 'navGroup',
id: 'project_settings_project_nav',

View file

@ -7,6 +7,7 @@
import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import { ConsolePluginStart } from '@kbn/console-plugin/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
@ -30,6 +31,7 @@ export interface ServerlessSearchPluginSetupDependencies {
export interface ServerlessSearchPluginStartDependencies {
cloud: CloudStart;
console: ConsolePluginStart;
searchPlayground: SearchPlaygroundPluginStart;
management: ManagementStart;
security: SecurityPluginStart;
serverless: ServerlessPluginStart;

View file

@ -47,5 +47,6 @@
"@kbn/discover-plugin",
"@kbn/search-connectors-plugin",
"@kbn/index-management",
"@kbn/search-playground",
]
}

View file

@ -38430,11 +38430,8 @@
"xpack.serverlessSearch.languages.ruby": "Ruby",
"xpack.serverlessSearch.languages.ruby.githubLabel": "elasticsearch-serverless-ruby",
"xpack.serverlessSearch.learnMore": "En savoir plus",
"xpack.serverlessSearch.nav.alerts": "Alertes",
"xpack.serverlessSearch.nav.content.indices": "Gestion des index",
"xpack.serverlessSearch.nav.content.pipelines": "Pipelines",
"xpack.serverlessSearch.nav.devTools": "Outils de développement",
"xpack.serverlessSearch.nav.gettingStarted": "Démarrer",
"xpack.serverlessSearch.nav.home": "Accueil",
"xpack.serverlessSearch.nav.mngt": "Gestion",
"xpack.serverlessSearch.nav.performance": "Performances",

View file

@ -38398,11 +38398,8 @@
"xpack.serverlessSearch.languages.ruby": "Ruby",
"xpack.serverlessSearch.languages.ruby.githubLabel": "elasticsearch-serverless-ruby",
"xpack.serverlessSearch.learnMore": "詳細",
"xpack.serverlessSearch.nav.alerts": "アラート",
"xpack.serverlessSearch.nav.content.indices": "インデックス管理",
"xpack.serverlessSearch.nav.content.pipelines": "パイプライン",
"xpack.serverlessSearch.nav.devTools": "開発ツール",
"xpack.serverlessSearch.nav.gettingStarted": "使ってみる",
"xpack.serverlessSearch.nav.home": "ホーム",
"xpack.serverlessSearch.nav.mngt": "管理",
"xpack.serverlessSearch.nav.performance": "パフォーマンス",

View file

@ -38442,11 +38442,8 @@
"xpack.serverlessSearch.languages.ruby": "Ruby",
"xpack.serverlessSearch.languages.ruby.githubLabel": "elasticsearch-serverless-ruby",
"xpack.serverlessSearch.learnMore": "了解详情",
"xpack.serverlessSearch.nav.alerts": "告警",
"xpack.serverlessSearch.nav.content.indices": "索引管理",
"xpack.serverlessSearch.nav.content.pipelines": "管道",
"xpack.serverlessSearch.nav.devTools": "开发工具",
"xpack.serverlessSearch.nav.gettingStarted": "开始使用",
"xpack.serverlessSearch.nav.home": "主页",
"xpack.serverlessSearch.nav.mngt": "管理",
"xpack.serverlessSearch.nav.performance": "性能",

View file

@ -43,5 +43,10 @@ export function SvlManagementPageProvider({ getService }: FtrProviderContext) {
async clickOrgMembersManagementCard() {
await testSubjects.click('app-card-organization_members');
},
// Ingest Pipelines
async clickIngestPipelinesManagementCard() {
await testSubjects.click('app-card-ingest_pipelines');
},
};
}

View file

@ -71,17 +71,12 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
});
it("management apps from the sidenav hide the 'stack management' root from the breadcrumbs", async () => {
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:triggersActions' });
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Alerts', 'Rules']);
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:index_management' });
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Index Management', 'Indices']);
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:ingest_pipelines' });
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Ingest Pipelines']);
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:api_keys' });
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['API keys']);
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts([
'Content',
'Index Management',
'Indices',
]);
});
it('navigate management', async () => {