[8.x] [Search] Search Playground - shared rendering (#201302) (#203243)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Search] Search Playground - shared rendering
(#201302)](https://github.com/elastic/kibana/pull/201302)

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

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

<!--BACKPORT [{"author":{"name":"Rodney
Norris","email":"rodney.norris@elastic.co"},"sourceCommit":{"committedDate":"2024-12-05T21:09:51Z","message":"[Search]
Search Playground - shared rendering
(#201302)","sha":"434eaa78ad7c045f52b2126cdae0f1d8fa7a00f6","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","backport:prev-minor","v8.18.0"],"number":201302,"url":"https://github.com/elastic/kibana/pull/201302","mergeCommit":{"message":"[Search]
Search Playground - shared rendering
(#201302)","sha":"434eaa78ad7c045f52b2126cdae0f1d8fa7a00f6"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201302","number":201302,"mergeCommit":{"message":"[Search]
Search Playground - shared rendering
(#201302)","sha":"434eaa78ad7c045f52b2126cdae0f1d8fa7a00f6"}},{"branch":"8.x","label":"v8.18.0","labelRegex":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Rodney Norris 2024-12-06 11:22:01 -06:00 committed by GitHub
parent aa59d7acd4
commit 4dd3c9e47b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 485 additions and 420 deletions

View file

@ -84,9 +84,6 @@ xpack.ml.compatibleModuleType: 'search'
data_visualizer.resultLinks.fileBeat.enabled: false
# Search Playground
xpack.searchPlayground.ui.enabled: true
# Search InferenceEndpoints
xpack.searchInferenceEndpoints.ui.enabled: true

View file

@ -7,6 +7,7 @@ xpack.infra.enabled: true
xpack.uptime.enabled: true
xpack.securitySolution.enabled: false
xpack.search.notebooks.enabled: false
xpack.searchPlayground.enabled: false
## Fine-tune the observability solution feature privileges. Also, refer to `serverless.yml` for the project-agnostic overrides.
xpack.features.overrides:

View file

@ -8,6 +8,7 @@ xpack.observabilityLogsExplorer.enabled: false
xpack.observability.enabled: false
xpack.observabilityAIAssistant.enabled: false
xpack.search.notebooks.enabled: false
xpack.searchPlayground.enabled: false
## Fine-tune the security solution feature privileges. Also, refer to `serverless.yml` for the project-agnostic overrides.
xpack.features.overrides:

View file

@ -16,7 +16,7 @@ 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';
export const ES_SEARCH_PLAYGROUND_ID = 'searchPlayground';
export const SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID = 'searchInferenceEndpoints';
export const SEARCH_HOMEPAGE = 'searchHomepage';
export const SEARCH_INDICES_START = 'elasticsearchStart';

View file

@ -17,7 +17,7 @@ import {
ENTERPRISE_SEARCH_ANALYTICS_APP_ID,
ENTERPRISE_SEARCH_APPSEARCH_APP_ID,
ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID,
SERVERLESS_ES_SEARCH_PLAYGROUND_ID,
ES_SEARCH_PLAYGROUND_ID,
SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID,
SEARCH_HOMEPAGE,
SEARCH_INDICES_START,
@ -38,7 +38,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 SearchPlaygroundId = typeof ES_SEARCH_PLAYGROUND_ID;
export type SearchInferenceEndpointsId = typeof SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID;
export type SearchHomepage = typeof SEARCH_HOMEPAGE;
export type SearchStart = typeof SEARCH_INDICES_START;
@ -50,7 +50,7 @@ export type SearchAISearch = typeof SEARCH_AI_SEARCH;
export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers';
export type ApplicationsLinkId = 'searchApplications' | 'playground';
export type ApplicationsLinkId = 'searchApplications';
export type AppsearchLinkId = 'engines';

View file

@ -21,6 +21,7 @@ export {
SEARCH_VECTOR_SEARCH,
SEARCH_SEMANTIC_SEARCH,
SEARCH_AI_SEARCH,
ES_SEARCH_PLAYGROUND_ID,
} from './constants';
export type {

View file

@ -137,6 +137,7 @@ export const applicationUsageSchema = {
enterpriseSearch: commonSchema,
enterpriseSearchContent: commonSchema,
searchInferenceEndpoints: commonSchema,
searchPlayground: commonSchema,
enterpriseSearchAnalytics: commonSchema,
enterpriseSearchApplications: commonSchema,
enterpriseSearchAISearch: commonSchema,

View file

@ -2229,6 +2229,137 @@
}
}
},
"searchPlayground": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "Always `main`"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 90 days"
}
},
"views": {
"type": "array",
"items": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "The application view being tracked"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application sub view since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application sub view is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
}
}
}
}
}
}
},
"enterpriseSearchAnalytics": {
"properties": {
"appId": {

View file

@ -13,10 +13,8 @@ import {
type CreateIndexLocatorParams,
} from './create_index_locator';
import { SearchInferenceEndpointLocatorDefinition } from './inference_locator';
import { PlaygroundLocatorDefinition, type PlaygroundLocatorParams } from './playground_locator';
export function registerLocators(share: SharePluginSetup) {
share.url.locators.create<CreateIndexLocatorParams>(new CreateIndexLocatorDefinition());
share.url.locators.create<PlaygroundLocatorParams>(new PlaygroundLocatorDefinition());
share.url.locators.create<SerializableRecord>(new SearchInferenceEndpointLocatorDefinition());
}

View file

@ -1,28 +0,0 @@
/*
* 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 type { LocatorDefinition } from '@kbn/share-plugin/common';
import type { SerializableRecord } from '@kbn/utility-types';
import { APPLICATIONS_PLUGIN, PLAYGROUND_URL } from '../constants';
export type PlaygroundLocatorParams = { 'default-index': string } & SerializableRecord;
export class PlaygroundLocatorDefinition implements LocatorDefinition<PlaygroundLocatorParams> {
public readonly getLocation = async (params: PlaygroundLocatorParams) => {
const defaultIndex = params['default-index'];
const path = `${PLAYGROUND_URL}${defaultIndex ? `?default-index=${defaultIndex}` : ''}`;
return {
app: APPLICATIONS_PLUGIN.ID,
path,
state: {},
};
};
public readonly id = 'PLAYGROUND_LOCATOR_ID';
}

View file

@ -16,7 +16,6 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { mlPluginMock } from '@kbn/ml-plugin/public/mocks';
import { searchPlaygroundMock } from '@kbn/search-playground/__mocks__/search_playground_mock';
import { securityMock } from '@kbn/security-plugin/public/mocks';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
@ -67,7 +66,6 @@ export const mockKibanaValues = {
},
renderHeaderActions: jest.fn(),
searchInferenceEndpoints: null,
searchPlayground: searchPlaygroundMock.createStart(),
security: securityMock.createStart(),
setBreadcrumbs: jest.fn(),
setChromeIsVisible: jest.fn(),

View file

@ -19,14 +19,12 @@ import { SetEnterpriseSearchApplicationsChrome } from '../../../shared/kibana_ch
import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout';
import { useEnterpriseSearchApplicationNav } from '../../../shared/layout';
import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
import { PlaygroundHeaderDocsAction } from '../playground/header_docs_action';
import { SearchApplicationHeaderDocsAction } from '../search_application/header_docs_action';
export type EnterpriseSearchApplicationsPageTemplateProps = Omit<
PageTemplateProps,
'useEndpointHeaderActions'
> & {
docLink?: 'search_application' | 'playground';
hasSchemaConflicts?: boolean;
restrictWidth?: boolean;
searchApplicationName?: string;
@ -41,7 +39,6 @@ export const EnterpriseSearchApplicationsPageTemplate: React.FC<
searchApplicationName,
hasSchemaConflicts,
restrictWidth = true,
docLink = 'search_application',
...pageTemplateProps
}) => {
const alwaysReturnNavItems = true;
@ -72,11 +69,7 @@ export const EnterpriseSearchApplicationsPageTemplate: React.FC<
);
useLayoutEffect(() => {
const docAction = {
playground: PlaygroundHeaderDocsAction,
search_application: SearchApplicationHeaderDocsAction,
}[docLink];
renderHeaderActions(docAction);
renderHeaderActions(SearchApplicationHeaderDocsAction);
return () => {
renderHeaderActions();

View file

@ -1,27 +0,0 @@
/*
* 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 { useValues } from 'kea';
import { KibanaLogic } from '../../../shared/kibana';
import { EndpointsHeaderAction } from '../../../shared/layout/endpoints_header_action';
export const PlaygroundHeaderDocsAction: React.FC = () => {
const { searchPlayground } = useValues(KibanaLogic);
if (!searchPlayground) {
return null;
}
return (
<EndpointsHeaderAction>
<searchPlayground.PlaygroundHeaderDocs />
</EndpointsHeaderAction>
);
};

View file

@ -1,71 +0,0 @@
/*
* 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, { useLayoutEffect } from 'react';
import { useValues } from 'kea';
import useObservable from 'react-use/lib/useObservable';
import { SEARCH_PRODUCT_NAME } from '../../../../../common/constants';
import { KibanaLogic } from '../../../shared/kibana';
import { SetSearchPlaygroundChrome } from '../../../shared/kibana_chrome/set_chrome';
import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout';
import { useEnterpriseSearchNav } from '../../../shared/layout';
import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
import { PlaygroundHeaderDocsAction } from './header_docs_action';
export type SearchPlaygroundPageTemplateProps = Omit<
PageTemplateProps,
'useEndpointHeaderActions'
> & {
hasSchemaConflicts?: boolean;
restrictWidth?: boolean;
searchApplicationName?: string;
};
export const SearchPlaygroundPageTemplate: React.FC<SearchPlaygroundPageTemplateProps> = ({
children,
pageChrome,
pageViewTelemetry,
searchApplicationName,
hasSchemaConflicts,
restrictWidth = true,
...pageTemplateProps
}) => {
const navItems = useEnterpriseSearchNav();
const { renderHeaderActions, getChromeStyle$ } = useValues(KibanaLogic);
const chromeStyle = useObservable(getChromeStyle$(), 'classic');
useLayoutEffect(() => {
renderHeaderActions(PlaygroundHeaderDocsAction);
return () => {
renderHeaderActions();
};
}, []);
return (
<EnterpriseSearchPageTemplateWrapper
{...pageTemplateProps}
solutionNav={{
items: chromeStyle === 'classic' ? navItems : undefined,
name: SEARCH_PRODUCT_NAME,
}}
restrictWidth={restrictWidth}
setPageChrome={pageChrome && <SetSearchPlaygroundChrome trail={pageChrome} />}
useEndpointHeaderActions={false}
>
{pageViewTelemetry && (
<SendEnterpriseSearchTelemetry action="viewed" metric={pageViewTelemetry} />
)}
{children}
</EnterpriseSearchPageTemplateWrapper>
);
};

View file

@ -1,46 +0,0 @@
/*
* 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 { useValues } from 'kea';
import { i18n } from '@kbn/i18n';
import { KibanaLogic } from '../../../shared/kibana';
import { SearchPlaygroundPageTemplate } from './page_template';
interface PlaygroundProps {
pageMode?: 'chat' | 'search';
}
export const Playground: React.FC<PlaygroundProps> = ({ pageMode = 'chat' }) => {
const { searchPlayground } = useValues(KibanaLogic);
if (!searchPlayground) {
return null;
}
return (
<searchPlayground.PlaygroundProvider>
<SearchPlaygroundPageTemplate
pageChrome={[
i18n.translate('xpack.enterpriseSearch.content.playground.breadcrumb', {
defaultMessage: 'Playground',
}),
]}
pageViewTelemetry="Playground"
restrictWidth={false}
panelled={false}
customPageSections
bottomBorder="extended"
>
<searchPlayground.Playground pageMode={pageMode} />
</SearchPlaygroundPageTemplate>
</searchPlayground.PlaygroundProvider>
);
};

View file

@ -1,19 +0,0 @@
/*
* 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 enum MessageRole {
'user' = 'user',
'assistant' = 'assistant',
'system' = 'system',
}
export interface Message {
id: string;
content: string | React.ReactNode;
createdAt?: Date;
role: MessageRole;
}

View file

@ -0,0 +1,22 @@
/*
* 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, { useEffect } from 'react';
import { ES_SEARCH_PLAYGROUND_ID } from '@kbn/deeplinks-search';
import { useKibana } from '@kbn/kibana-react-plugin/public';
export const PlaygroundRedirect: React.FC = () => {
const { application } = useKibana().services;
useEffect(() => {
application?.navigateToApp(ES_SEARCH_PLAYGROUND_ID);
}, [application]);
return null;
};

View file

@ -6,35 +6,19 @@
*/
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Routes, Route } from '@kbn/shared-ux-router';
import { NotFound } from './components/not_found';
import { Playground } from './components/playground/playground';
import { PlaygroundRedirect } from './components/playground_redirect';
import { SearchApplicationsRouter } from './components/search_applications/search_applications_router';
import {
PLAYGROUND_CHAT_PATH,
PLAYGROUND_PATH,
PLAYGROUND_SEARCH_PATH,
ROOT_PATH,
SEARCH_APPLICATIONS_PATH,
} from './routes';
import { ROOT_PATH, SEARCH_APPLICATIONS_PATH } from './routes';
export const Applications = () => {
return (
<Routes>
<Redirect exact from={ROOT_PATH} to={PLAYGROUND_PATH} />
<Route path={SEARCH_APPLICATIONS_PATH}>
<SearchApplicationsRouter />
</Route>
<Redirect exact from={PLAYGROUND_PATH} to={PLAYGROUND_CHAT_PATH} />
<Route path={PLAYGROUND_CHAT_PATH}>
<Playground pageMode={'chat'} />
</Route>
<Route path={PLAYGROUND_SEARCH_PATH}>
<Playground pageMode={'search'} />
</Route>
<Route exact path={ROOT_PATH} component={PlaygroundRedirect} />
<Route path={SEARCH_APPLICATIONS_PATH} component={SearchApplicationsRouter} />
<Route>
<NotFound />
</Route>

View file

@ -17,9 +17,6 @@ export enum SearchApplicationViewTabs {
export const SEARCH_APPLICATION_CREATION_PATH = `${SEARCH_APPLICATIONS_PATH}/new`;
export const SEARCH_APPLICATION_PATH = `${SEARCH_APPLICATIONS_PATH}/:searchApplicationName`;
export const SEARCH_APPLICATION_TAB_PATH = `${SEARCH_APPLICATION_PATH}/:tabId`;
export const PLAYGROUND_PATH = `${ROOT_PATH}playground/`;
export const PLAYGROUND_CHAT_PATH = `${PLAYGROUND_PATH}chat`;
export const PLAYGROUND_SEARCH_PATH = `${PLAYGROUND_PATH}search`;
export const SEARCH_APPLICATION_CONNECT_PATH = `${SEARCH_APPLICATION_PATH}/${SearchApplicationViewTabs.CONNECT}/:connectTabId`;
export enum SearchApplicationConnectTabs {

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useCallback } from 'react';
import { useValues } from 'kea';
@ -25,9 +25,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { ConnectorStatus } from '@kbn/search-connectors';
import { APPLICATIONS_PLUGIN } from '../../../../../../common/constants';
import { PLAYGROUND_PATH } from '../../../../applications/routes';
import { generateEncodedPath } from '../../../../shared/encode_path_params';
import { KibanaLogic } from '../../../../shared/kibana';
import { EuiButtonTo } from '../../../../shared/react_router_helpers';
@ -53,7 +50,14 @@ export const WhatsNextBox: React.FC<WhatsNextBoxProps> = ({
isSyncing = false,
isWaitingForConnector = false,
}) => {
const { navigateToUrl } = useValues(KibanaLogic);
const { share } = useValues(KibanaLogic);
const onStartPlaygroundClick = useCallback(() => {
if (!share) return;
const playgroundLocator = share.url.locators.get('PLAYGROUND_LOCATOR_ID');
if (playgroundLocator) {
playgroundLocator.navigate({ 'default-index': connectorIndex });
}
}, [connectorIndex, share]);
const isConfigured = !(
connectorStatus === ConnectorStatus.NEEDS_CONFIGURATION ||
connectorStatus === ConnectorStatus.CREATED
@ -84,14 +88,7 @@ export const WhatsNextBox: React.FC<WhatsNextBoxProps> = ({
data-test-subj="enterpriseSearchWhatsNextBoxSearchPlaygroundButton"
iconType="sparkles"
disabled={!connectorIndex || disabled}
onClick={() => {
navigateToUrl(
`${APPLICATIONS_PLUGIN.URL}${PLAYGROUND_PATH}?default-index=${connectorIndex}`,
{
shouldNotCreateHref: true,
}
);
}}
onClick={onStartPlaygroundClick}
>
<FormattedMessage
id="xpack.enterpriseSearch.whatsNextBox.searchPlaygroundButtonLabel"

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { css } from '@emotion/react';
@ -30,11 +30,10 @@ import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { APPLICATIONS_PLUGIN, ELASTICSEARCH_PLUGIN } from '../../../../../../common/constants';
import { ELASTICSEARCH_PLUGIN } from '../../../../../../common/constants';
import { KibanaDeps } from '../../../../../../common/types';
import { PLAYGROUND_PATH } from '../../../../applications/routes';
import { generateEncodedPath } from '../../../../shared/encode_path_params';
import { HttpLogic } from '../../../../shared/http';
import { KibanaLogic } from '../../../../shared/kibana';
@ -66,7 +65,14 @@ export const FinishUpStep: React.FC<FinishUpStepProps> = ({ title }) => {
const isSyncing = isWaitingForSync || isSyncingProp;
const { http } = useValues(HttpLogic);
const { application } = useValues(KibanaLogic);
const { application, share } = useValues(KibanaLogic);
const onStartPlaygroundClick = useCallback(() => {
if (!share) return;
const playgroundLocator = share.url.locators.get('PLAYGROUND_LOCATOR_ID');
if (playgroundLocator) {
playgroundLocator.navigate({ 'default-index': connector?.index_name });
}
}, [connector, share]);
useEffect(() => {
setTimeout(() => {
window.scrollTo({
@ -134,14 +140,7 @@ export const FinishUpStep: React.FC<FinishUpStepProps> = ({ title }) => {
'xpack.enterpriseSearch.createConnector.finishUpStep.euiButton.startSearchPlaygroundLabel',
{ defaultMessage: 'Start Search Playground' }
)}
onClick={() => {
if (connector) {
KibanaLogic.values.navigateToUrl(
`${APPLICATIONS_PLUGIN.URL}${PLAYGROUND_PATH}?default-index=${connector.index_name}`,
{ shouldNotCreateHref: true }
);
}
}}
onClick={onStartPlaygroundClick}
>
{i18n.translate(
'xpack.enterpriseSearch.createConnector.finishUpStep.startSearchPlaygroundButtonLabel',

View file

@ -5,14 +5,14 @@
* 2.0.
*/
import React from 'react';
import React, { useCallback } from 'react';
import { useValues } from 'kea';
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { APPLICATIONS_PLUGIN } from '../../../../../../../common/constants';
import { PLAYGROUND_PATH } from '../../../../../applications/routes';
import { KibanaLogic } from '../../../../../shared/kibana';
export interface SearchPlaygroundPopoverProps {
@ -24,18 +24,21 @@ export const SearchPlaygroundPopover: React.FC<SearchPlaygroundPopoverProps> = (
indexName,
ingestionMethod,
}) => {
const playgroundUrl = `${APPLICATIONS_PLUGIN.URL}${PLAYGROUND_PATH}?default-index=${indexName}`;
const { share } = useValues(KibanaLogic);
const onStartPlaygroundClick = useCallback(() => {
if (!share) return;
const playgroundLocator = share.url.locators.get('PLAYGROUND_LOCATOR_ID');
if (playgroundLocator) {
playgroundLocator.navigate({ 'default-index': indexName });
}
}, [indexName, share]);
return (
<EuiButton
data-test-subj="enterpriseSearchSearchPlaygroundPopoverViewInPlaygroundButton"
data-telemetry-id={`entSearchContent-${ingestionMethod}-header-viewPlayground`}
iconType="eye"
onClick={() => {
KibanaLogic.values.navigateToUrl(playgroundUrl, {
shouldNotCreateHref: true,
});
}}
onClick={onStartPlaygroundClick}
>
{i18n.translate('xpack.enterpriseSearch.content.index.viewPlayground', {
defaultMessage: 'View in Playground',

View file

@ -129,7 +129,6 @@ export const renderApp = (
params.setHeaderActionMenu(
HeaderActions ? renderHeaderActions.bind(null, HeaderActions, store, params) : undefined
),
searchPlayground: plugins.searchPlayground,
searchInferenceEndpoints: plugins.searchInferenceEndpoints,
security,
setBreadcrumbs: chrome.setBreadcrumbs,

View file

@ -30,7 +30,6 @@ import { MlPluginStart } from '@kbn/ml-plugin/public';
import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants';
import { ConnectorDefinition } from '@kbn/search-connectors';
import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
@ -68,7 +67,6 @@ export interface KibanaLogicProps {
productFeatures: ProductFeatures;
renderHeaderActions(HeaderActions?: FC): void;
searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart;
searchPlayground?: SearchPlaygroundPluginStart;
security?: SecurityPluginStart;
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
setChromeIsVisible(isVisible: boolean): void;
@ -103,7 +101,6 @@ export interface KibanaValues {
productFeatures: ProductFeatures;
renderHeaderActions(HeaderActions?: FC): void;
searchInferenceEndpoints: SearchInferenceEndpointsPluginStart | null;
searchPlayground: SearchPlaygroundPluginStart | null;
security: SecurityPluginStart | null;
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
setChromeIsVisible(isVisible: boolean): void;
@ -150,7 +147,6 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
productFeatures: [props.productFeatures, {}],
renderHeaderActions: [props.renderHeaderActions, {}],
searchInferenceEndpoints: [props.searchInferenceEndpoints || null, {}],
searchPlayground: [props.searchPlayground || null, {}],
security: [props.security || null, {}],
setBreadcrumbs: [props.setBreadcrumbs, {}],
setChromeIsVisible: [props.setChromeIsVisible, {}],

View file

@ -88,7 +88,7 @@ export const buildBaseClassicNavItems = ({
{
'data-test-subj': 'searchSideNav-Playground',
deepLink: {
link: 'enterpriseSearchApplications:playground',
link: 'searchPlayground',
shouldShowActiveForSubroutes: true,
},
id: 'playground',

View file

@ -133,7 +133,7 @@ describe('generateSideNavItems', () => {
},
{
deepLink: {
link: 'enterpriseSearchApplications:playground',
link: 'searchPlayground',
},
id: 'unit-test-missing',
},

View file

@ -73,7 +73,7 @@ const baseNavItems = [
items: [
{
'data-test-subj': 'searchSideNav-Playground',
href: '/app/enterprise_search/applications/playground',
href: '/app/search_playground',
id: 'playground',
items: undefined,
name: 'Playground',
@ -188,9 +188,9 @@ const mockNavLinks = [
url: '/app/enterprise_search/content/crawlers',
},
{
id: 'enterpriseSearchApplications:playground',
id: 'searchPlayground',
title: 'Playground',
url: '/app/enterprise_search/applications/playground',
url: '/app/search_playground',
},
{
id: 'enterpriseSearchApplications:searchApplications',

View file

@ -24,7 +24,6 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { I18nProvider } from '@kbn/i18n-react';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { mlPluginMock } from '@kbn/ml-plugin/public/mocks';
import { searchPlaygroundMock } from '@kbn/search-playground/__mocks__/search_playground_mock';
import { securityMock } from '@kbn/security-plugin/public/mocks';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
@ -86,7 +85,6 @@ export const mockKibanaProps: KibanaLogicProps = {
hasWebCrawler: true,
},
renderHeaderActions: jest.fn(),
searchPlayground: searchPlaygroundMock.createStart(),
security: securityMock.createStart(),
setBreadcrumbs: jest.fn(),
setChromeIsVisible: jest.fn(),

View file

@ -151,7 +151,7 @@ export const getNavigationTreeDefinition = ({
{
children: [
{
link: 'enterpriseSearchApplications:playground',
link: 'searchPlayground',
},
{
getIsActive: ({ pathNameSerialized, prepend }) => {

View file

@ -60,7 +60,7 @@ import { ClientConfigType, InitialAppData, ProductAccess } from '../common/types
import { hasEnterpriseLicense } from '../common/utils/licensing';
import { ENGINES_PATH } from './applications/app_search/routes';
import { SEARCH_APPLICATIONS_PATH, PLAYGROUND_PATH } from './applications/applications/routes';
import { SEARCH_APPLICATIONS_PATH } from './applications/applications/routes';
import {
CONNECTORS_PATH,
SEARCH_INDICES_PATH,
@ -151,14 +151,6 @@ const relevanceLinks: AppDeepLink[] = [
];
const applicationsLinks: AppDeepLink[] = [
{
id: 'playground',
path: `/${PLAYGROUND_PATH}`,
title: i18n.translate('xpack.enterpriseSearch.navigation.contentPlaygroundLinkLabel', {
defaultMessage: 'Playground',
}),
visibleIn: ['sideNav', 'globalSearch'],
},
{
id: 'searchApplications',
path: `/${SEARCH_APPLICATIONS_PATH}`,
@ -281,6 +273,7 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(EnterpriseSearchOverview, kibanaDeps, pluginData);
},
order: 0,
title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE,
visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'],
});
@ -306,6 +299,7 @@ export class EnterpriseSearchPlugin implements Plugin {
return renderApp(EnterpriseSearchContent, kibanaDeps, pluginData);
},
order: 1,
title: ENTERPRISE_SEARCH_CONTENT_PLUGIN.NAV_TITLE,
});

View file

@ -1,24 +0,0 @@
/*
* 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 { SearchPlaygroundPluginStart } from '../public';
export type Start = jest.Mocked<SearchPlaygroundPluginStart>;
const createStartMock = (): Start => {
const startContract: Start = {
PlaygroundProvider: jest.fn(),
Playground: jest.fn(),
PlaygroundHeaderDocs: jest.fn(),
};
return startContract;
};
export const searchPlaygroundMock = {
createStart: createStartMock,
};

View file

@ -5,10 +5,13 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { Pagination } from './types';
export const PLUGIN_ID = 'searchPlayground';
export const PLUGIN_NAME = 'Playground';
export const PLUGIN_NAME = i18n.translate('xpack.searchPlayground.plugin.name', {
defaultMessage: 'Playground',
});
export const PLUGIN_PATH = '/app/search_playground';
export const SEARCH_MODE_FEATURE_FLAG_ID = 'searchPlayground:searchModeEnabled';

View file

@ -18,7 +18,9 @@
"actions",
"data",
"encryptedSavedObjects",
"licensing",
"ml",
"features",
"navigation",
"share",
"security",
@ -29,6 +31,7 @@
"cloud",
"console",
"usageCollection",
"searchNavigation",
],
"requiredBundles": [
"kibanaReact",

View file

@ -1,34 +0,0 @@
/*
* 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 { dynamic } from '@kbn/shared-ux-utility';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { CoreStart } from '@kbn/core-lifecycle-browser';
import { AppPluginStartDependencies } from './types';
import { AppProps } from './components/app';
export const Playground = dynamic<React.FC<AppProps>>(async () => ({
default: (await import('./components/app')).App,
}));
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>) =>
(
<KibanaContextProvider services={{ ...core, ...services }}>
<PlaygroundProvider {...props} />
</KibanaContextProvider>
);

View file

@ -0,0 +1,25 @@
/*
* 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 { useEffect } from 'react';
import { useKibana } from './use_kibana';
export const usePlaygroundBreadcrumbs = () => {
const { searchNavigation } = useKibana().services;
useEffect(() => {
searchNavigation?.breadcrumbs.setSearchBreadCrumbs(
[{ text: 'Build' }, { text: 'Playground' }],
{ forClassicChromeStyle: true }
);
return () => {
// Clear breadcrumbs on unmount;
searchNavigation?.breadcrumbs.clearBreadcrumbs();
};
}, [searchNavigation]);
};

View file

@ -6,12 +6,13 @@
*/
import React, { useMemo } from 'react';
import { EuiPageTemplate } from '@elastic/eui';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { PlaygroundProvider } from './providers/playground_provider';
import { useKibana } from './hooks/use_kibana';
import { PlaygroundPageMode } from './types';
import { App } from './components/app';
import { usePlaygroundBreadcrumbs } from './hooks/use_playground_breadcrumbs';
interface PlaygroundOverviewProps {
pageMode?: PlaygroundPageMode;
@ -20,8 +21,9 @@ export const PlaygroundOverview: React.FC<PlaygroundOverviewProps> = ({
pageMode = PlaygroundPageMode.chat,
}) => {
const {
services: { console: consolePlugin },
services: { history, console: consolePlugin, searchNavigation },
} = useKibana();
usePlaygroundBreadcrumbs();
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
@ -30,16 +32,17 @@ export const PlaygroundOverview: React.FC<PlaygroundOverviewProps> = ({
return (
<PlaygroundProvider>
<EuiPageTemplate
<KibanaPageTemplate
offset={0}
restrictWidth={false}
data-test-subj="svlPlaygroundPage"
grow={false}
panelled={false}
solutionNav={searchNavigation?.useClassicNavigation(history)}
>
<App showDocs pageMode={pageMode} />
{embeddableConsole}
</EuiPageTemplate>
</KibanaPageTemplate>
</PlaygroundProvider>
);
};

View file

@ -5,17 +5,20 @@
* 2.0.
*/
import type {
CoreSetup,
import { BehaviorSubject, type Subscription } from 'rxjs';
import {
type CoreSetup,
Plugin,
CoreStart,
AppMountParameters,
PluginInitializerContext,
type CoreStart,
type AppMountParameters,
type PluginInitializerContext,
DEFAULT_APP_CATEGORIES,
AppUpdater,
AppStatus,
} from '@kbn/core/public';
import { PLUGIN_ID, PLUGIN_NAME, PLUGIN_PATH } from '../common';
import { docLinks } from '../common/doc_links';
import { PlaygroundHeaderDocs } from './components/playground_header_docs';
import { Playground, getPlaygroundProvider } from './embeddable';
import type {
AppPluginSetupDependencies,
AppPluginStartDependencies,
@ -29,6 +32,8 @@ export class SearchPlaygroundPlugin
implements Plugin<SearchPlaygroundPluginSetup, SearchPlaygroundPluginStart>
{
private config: SearchPlaygroundConfigType;
private appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({}));
private licenseSubscription: Subscription | undefined;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get<SearchPlaygroundConfigType>();
@ -43,12 +48,17 @@ export class SearchPlaygroundPlugin
core.application.register({
id: PLUGIN_ID,
appRoute: PLUGIN_PATH,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
euiIconType: 'logoEnterpriseSearch',
status: AppStatus.inaccessible,
title: PLUGIN_NAME,
updater$: this.appUpdater$,
async mount({ element, history }: AppMountParameters) {
const { renderApp } = await import('./application');
const [coreStart, depsStart] = await core.getStartServices();
coreStart.chrome.docTitle.change(PLUGIN_NAME);
depsStart.searchNavigation?.handleOnAppMount();
const startDeps: AppPluginStartDependencies = {
...depsStart,
@ -57,6 +67,8 @@ export class SearchPlaygroundPlugin
return renderApp(coreStart, startDeps, element);
},
visibleIn: ['sideNav', 'globalSearch'],
order: 2,
});
registerLocators(deps.share);
@ -64,14 +76,29 @@ export class SearchPlaygroundPlugin
return {};
}
public start(core: CoreStart, deps: AppPluginStartDependencies): SearchPlaygroundPluginStart {
public start(
core: CoreStart,
{ licensing }: AppPluginStartDependencies
): SearchPlaygroundPluginStart {
docLinks.setDocLinks(core.docLinks.links);
return {
PlaygroundProvider: getPlaygroundProvider(core, deps),
Playground,
PlaygroundHeaderDocs,
};
this.licenseSubscription = licensing.license$.subscribe((license) => {
const status: AppStatus =
license && license.isAvailable && license.isActive && license.hasAtLeast('enterprise')
? AppStatus.accessible
: AppStatus.inaccessible;
this.appUpdater$.next(() => ({
status,
}));
});
return {};
}
public stop() {}
public stop() {
if (this.licenseSubscription) {
this.licenseSubscription.unsubscribe();
this.licenseSubscription = undefined;
}
}
}

View file

@ -11,21 +11,19 @@ import {
IndicesStatsIndexMetadataState,
Uuid,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { HttpStart } from '@kbn/core-http-browser';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import React, { ComponentType } from 'react';
import { SharePluginSetup, 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 { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { ChatRequestData, MessageRole } from '../common/types';
import type { App } from './components/app';
import type { PlaygroundProvider as PlaygroundProviderComponent } from './providers/playground_provider';
import { PlaygroundHeaderDocs } from './components/playground_header_docs';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { SearchNavigationPluginStart } from '@kbn/search-navigation/public';
import type { SecurityPluginStart } from '@kbn/security-plugin/public';
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { ChatRequestData, MessageRole } from '../common/types';
export * from '../common/types';
@ -36,13 +34,11 @@ export enum PlaygroundPageMode {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchPlaygroundPluginSetup {}
export interface SearchPlaygroundPluginStart {
PlaygroundProvider: React.FC<React.ComponentProps<typeof PlaygroundProviderComponent>>;
Playground: React.FC<React.ComponentProps<typeof App>>;
PlaygroundHeaderDocs: React.FC<React.ComponentProps<typeof PlaygroundHeaderDocs>>;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchPlaygroundPluginStart {}
export interface AppPluginSetupDependencies {
cloud?: CloudSetup;
share: SharePluginSetup;
}
@ -52,20 +48,15 @@ export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
share: SharePluginStart;
cloud?: CloudStart;
console?: ConsolePluginStart;
data: DataPublicPluginStart;
searchNavigation?: SearchNavigationPluginStart;
security: SecurityPluginStart;
licensing: LicensingPluginStart;
}
export interface AppServicesContext {
http: HttpStart;
security: SecurityPluginStart;
share: SharePluginStart;
cloud?: CloudSetup;
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
usageCollection?: UsageCollectionStart;
console?: ConsolePluginStart;
data: DataPublicPluginStart;
}
export type AppServicesContext = CoreStart & AppPluginStartDependencies;
export enum ChatFormFields {
question = 'question',

View file

@ -13,7 +13,7 @@ export * from './types';
const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
ui: schema.object({
enabled: schema.boolean({ defaultValue: false }),
enabled: schema.boolean({ defaultValue: true }),
}),
});

View file

@ -5,15 +5,25 @@
* 2.0.
*/
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server';
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
DEFAULT_APP_CATEGORIES,
} from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { sendMessageEvent } from './analytics/events';
import {
SearchPlaygroundPluginSetup,
SearchPlaygroundPluginSetupDependencies,
SearchPlaygroundPluginStart,
SearchPlaygroundPluginStartDependencies,
} from './types';
import { defineRoutes } from './routes';
import { PLUGIN_ID, PLUGIN_NAME } from '../common';
export class SearchPlaygroundPlugin
implements
@ -31,7 +41,8 @@ export class SearchPlaygroundPlugin
}
public setup(
core: CoreSetup<SearchPlaygroundPluginStartDependencies, SearchPlaygroundPluginStart>
core: CoreSetup<SearchPlaygroundPluginStartDependencies, SearchPlaygroundPluginStart>,
{ features }: SearchPlaygroundPluginSetupDependencies
) {
this.logger.debug('searchPlayground: Setup');
const router = core.http.createRouter();
@ -40,6 +51,37 @@ export class SearchPlaygroundPlugin
this.registerAnalyticsEvents(core);
features.registerKibanaFeature({
id: PLUGIN_ID,
minimumLicense: 'enterprise',
name: PLUGIN_NAME,
order: 1,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana', PLUGIN_ID],
catalogue: [PLUGIN_ID],
privileges: {
all: {
app: ['kibana', PLUGIN_ID],
api: [PLUGIN_ID],
catalogue: [PLUGIN_ID],
savedObject: {
all: [],
read: [],
},
ui: [],
},
read: {
disabled: true,
savedObject: {
all: [],
read: [],
},
ui: [],
},
},
});
return {};
}

View file

@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema';
import type { Logger } from '@kbn/logging';
import { IRouter, StartServicesAccessor } from '@kbn/core/server';
import { i18n } from '@kbn/i18n';
import { PLUGIN_ID } from '../common';
import { sendMessageEvent, SendMessageEventData } from './analytics/events';
import { fetchFields } from './lib/fetch_query_source_fields';
import { AssistClientOptionsWithClient, createAssist as Assist } from './utils/assist';
@ -53,6 +54,14 @@ export function defineRoutes({
router.post(
{
path: APIRoutes.POST_QUERY_SOURCE_FIELDS,
options: {
access: 'internal',
},
security: {
authz: {
requiredPrivileges: [PLUGIN_ID],
},
},
validate: {
body: schema.object({
indices: schema.arrayOf(schema.string()),
@ -74,6 +83,14 @@ export function defineRoutes({
router.post(
{
path: APIRoutes.POST_CHAT_MESSAGE,
options: {
access: 'internal',
},
security: {
authz: {
requiredPrivileges: [PLUGIN_ID],
},
},
validate: {
body: schema.object({
data: schema.object({
@ -194,6 +211,14 @@ export function defineRoutes({
router.get(
{
path: APIRoutes.GET_INDICES,
options: {
access: 'internal',
},
security: {
authz: {
requiredPrivileges: [PLUGIN_ID],
},
},
validate: {
query: schema.object({
search_query: schema.maybe(schema.string()),
@ -223,6 +248,14 @@ export function defineRoutes({
router.post(
{
path: APIRoutes.POST_SEARCH_QUERY,
options: {
access: 'internal',
},
security: {
authz: {
requiredPrivileges: [PLUGIN_ID],
},
},
validate: {
body: schema.object({
search_query: schema.string(),
@ -287,6 +320,14 @@ export function defineRoutes({
router.post(
{
path: APIRoutes.GET_INDEX_MAPPINGS,
options: {
access: 'internal',
},
security: {
authz: {
requiredPrivileges: [PLUGIN_ID],
},
},
validate: {
body: schema.object({
indices: schema.arrayOf(schema.string()),

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server';
import { CloudStart } from '@kbn/cloud-plugin/server';
import type { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server';
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server';
import type { FeaturesPluginSetup } from '@kbn/features-plugin/server';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchPlaygroundPluginSetup {}
@ -14,6 +15,11 @@ export interface SearchPlaygroundPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchPlaygroundPluginStart {}
export interface SearchPlaygroundPluginSetupDependencies {
cloud?: CloudSetup;
features: FeaturesPluginSetup;
}
export interface SearchPlaygroundPluginStartDependencies {
actions: ActionsPluginStartContract;
cloud?: CloudStart;

View file

@ -17,7 +17,6 @@
"@kbn/i18n",
"@kbn/i18n-react",
"@kbn/kibana-react-plugin",
"@kbn/security-plugin",
"@kbn/user-profile-components",
"@kbn/shared-ux-router",
"@kbn/shared-ux-page-kibana-template",
@ -26,8 +25,6 @@
"@kbn/share-plugin",
"@kbn/cloud-plugin",
"@kbn/actions-plugin",
"@kbn/shared-ux-utility",
"@kbn/core-lifecycle-browser",
"@kbn/stack-connectors-plugin",
"@kbn/cases-plugin",
"@kbn/triggers-actions-ui-plugin",
@ -46,6 +43,10 @@
"@kbn/discover-utils",
"@kbn/data-plugin",
"@kbn/search-index-documents",
"@kbn/search-navigation",
"@kbn/features-plugin",
"@kbn/security-plugin",
"@kbn/licensing-plugin",
],
"exclude": [
"target/**/*",

View file

@ -12,13 +12,17 @@ import type {
PluginInitializerContext,
ScopedHistory,
} from '@kbn/core/public';
import type { ChromeStyle } from '@kbn/core-chrome-browser';
import type { Subscription } from 'rxjs';
import type { ChromeBreadcrumb, ChromeStyle } from '@kbn/core-chrome-browser';
import { i18n } from '@kbn/i18n';
import type { Logger } from '@kbn/logging';
import type {
SearchNavigationPluginSetup,
SearchNavigationPluginStart,
ClassicNavItem,
ClassicNavigationFactoryFn,
SearchNavigationSetBreadcrumbsOptions,
AppPluginStartDependencies,
} from './types';
export class SearchNavigationPlugin
@ -28,8 +32,10 @@ export class SearchNavigationPlugin
private currentChromeStyle: ChromeStyle | undefined = undefined;
private baseClassicNavItemsFn: (() => ClassicNavItem[]) | undefined = undefined;
private coreStart: CoreStart | undefined = undefined;
private pluginsStart: AppPluginStartDependencies | undefined = undefined;
private classicNavFactory: ClassicNavigationFactoryFn | undefined = undefined;
private onAppMountHandlers: Array<() => Promise<void>> = [];
private chromeSub: Subscription | undefined;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.logger = this.initializerContext.logger.get();
@ -39,9 +45,10 @@ export class SearchNavigationPlugin
return {};
}
public start(core: CoreStart): SearchNavigationPluginStart {
public start(core: CoreStart, plugins: AppPluginStartDependencies): SearchNavigationPluginStart {
this.coreStart = core;
core.chrome.getChromeStyle$().subscribe((value) => {
this.pluginsStart = plugins;
this.chromeSub = core.chrome.getChromeStyle$().subscribe((value) => {
this.currentChromeStyle = value;
});
@ -54,10 +61,19 @@ export class SearchNavigationPlugin
registerOnAppMountHandler: this.registerOnAppMountHandler.bind(this),
setGetBaseClassicNavItems: this.setGetBaseClassicNavItems.bind(this),
useClassicNavigation: this.useClassicNavigation.bind(this),
breadcrumbs: {
setSearchBreadCrumbs: this.setBreadcrumbs.bind(this),
clearBreadcrumbs: this.clearBreadcrumbs.bind(this),
},
};
}
public stop() {}
public stop() {
if (this.chromeSub) {
this.chromeSub.unsubscribe();
this.chromeSub = undefined;
}
}
private async handleOnAppMount() {
if (this.onAppMountHandlers.length === 0) return;
@ -89,4 +105,36 @@ export class SearchNavigationPlugin
return this.classicNavFactory(this.baseClassicNavItemsFn(), this.coreStart, history);
}
private setBreadcrumbs(
breadcrumbs: ChromeBreadcrumb[],
{ forClassicChromeStyle = false }: SearchNavigationSetBreadcrumbsOptions = {}
) {
if (forClassicChromeStyle === true && this.currentChromeStyle !== 'classic') return;
const searchBreadcrumbs = [this.getSearchHomeBreadcrumb(), ...breadcrumbs];
if (this.pluginsStart?.serverless) {
this.pluginsStart.serverless.setBreadcrumbs(searchBreadcrumbs);
} else {
this.coreStart?.chrome.setBreadcrumbs(searchBreadcrumbs);
}
}
private clearBreadcrumbs() {
if (this.pluginsStart?.serverless) {
this.pluginsStart.serverless.setBreadcrumbs([]);
} else {
this.coreStart?.chrome.setBreadcrumbs([]);
}
}
private getSearchHomeBreadcrumb(): ChromeBreadcrumb {
// TODO: When search_navigation handles solution nav, use the default
// home deep link for this breadcrumb's path.
return {
text: i18n.translate('xpack.searchNavigation.breadcrumbs.home.title', {
defaultMessage: 'Elasticsearch',
}),
};
}
}

View file

@ -6,7 +6,7 @@
*/
import type { ReactNode } from 'react';
import type { AppDeepLinkId } from '@kbn/core-chrome-browser';
import type { AppDeepLinkId, ChromeBreadcrumb } from '@kbn/core-chrome-browser';
import type { CoreStart, ScopedHistory } from '@kbn/core/public';
import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
import type { SolutionNavProps } from '@kbn/shared-ux-page-solution-nav';
@ -20,6 +20,13 @@ export interface SearchNavigationPluginStart {
// This is temporary until we can migrate building the class nav item list out of `enterprise_search` plugin
setGetBaseClassicNavItems: (classicNavItemsFn: () => ClassicNavItem[]) => void;
useClassicNavigation: (history: ScopedHistory<unknown>) => SolutionNavProps | undefined;
breadcrumbs: {
setSearchBreadCrumbs: (
breadcrumbs: ChromeBreadcrumb[],
options?: SearchNavigationSetBreadcrumbsOptions
) => void;
clearBreadcrumbs: () => void;
};
}
export interface AppPluginSetupDependencies {
@ -49,3 +56,10 @@ export type ClassicNavigationFactoryFn = (
core: CoreStart,
history: ScopedHistory<unknown>
) => SolutionNavProps | undefined;
export interface SearchNavigationSetBreadcrumbsOptions {
// When set to `true` breadcrumbs are only set when chrome style is set to classic.
// This option is for pages who rely on Solution Navigation for breadcrumbs, but still
// need to explicitly set the page breadcrumbs for classic solution view.
forClassicChromeStyle?: boolean;
}

View file

@ -4,11 +4,8 @@
"outDir": "target/types"
},
"include": [
"__mocks__/**/*",
"common/**/*",
"public/**/*",
"server/**/*",
"../../../../typings/**/*"
],
"kbn_references": [
"@kbn/core",

View file

@ -35,6 +35,7 @@
"indexManagement",
"searchConnectors",
"searchInferenceEndpoints",
"searchPlayground",
"usageCollection"
],
"requiredBundles": ["kibanaReact"]

View file

@ -17868,7 +17868,6 @@
"xpack.enterpriseSearch.content.overview.gettingStarted.generateApiKeyPanel.panelTitle": "Générer une clé dAPI",
"xpack.enterpriseSearch.content.overview.gettingStarted.pageDescription": "Configurez votre client de langage de programmation, ingérez des données, et vous serez prêt à commencer vos recherches en quelques minutes.",
"xpack.enterpriseSearch.content.overview.gettingStarted.pageTitle": "Clients linguistiques Elasticsearch",
"xpack.enterpriseSearch.content.playground.breadcrumb": "Playground",
"xpack.enterpriseSearch.content.searchIndex.cancelSync.successMessage": "Annulation réussie de la synchronisation",
"xpack.enterpriseSearch.content.searchIndex.cancelSyncs.successMessage": "Annulation réussie des synchronisations",
"xpack.enterpriseSearch.content.searchIndex.cannotConnect.body": "Le robot d'indexation Elastic requiert Enterprise Search. {link}",
@ -18444,7 +18443,6 @@
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "Moteurs",
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "Connecteurs",
"xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "Index",
"xpack.enterpriseSearch.navigation.contentPlaygroundLinkLabel": "Playground",
"xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "Robots d'indexation",
"xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel": "Points de terminaison d'inférence",
"xpack.enterpriseSearch.noEntSearch.noCrawler": "Le robot d'indexation d'Elastic n'est pas disponible sans Entreprise Search.",

View file

@ -17727,7 +17727,6 @@
"xpack.enterpriseSearch.content.overview.gettingStarted.generateApiKeyPanel.panelTitle": "APIキーを生成",
"xpack.enterpriseSearch.content.overview.gettingStarted.pageDescription": "プログラミング言語のクライアントを設定し、データを取り込めば、数分で検索を開始できます。",
"xpack.enterpriseSearch.content.overview.gettingStarted.pageTitle": "Elasticsearch言語クライアント",
"xpack.enterpriseSearch.content.playground.breadcrumb": "Playground",
"xpack.enterpriseSearch.content.searchIndex.cancelSync.successMessage": "同期が正常にキャンセルされました",
"xpack.enterpriseSearch.content.searchIndex.cancelSyncs.successMessage": "同期が正常にキャンセルされました",
"xpack.enterpriseSearch.content.searchIndex.cannotConnect.body": "Elastic Webクローラーにはエンタープライズ サーチが必要です。{link}",
@ -18301,7 +18300,6 @@
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "エンジン",
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "コネクター",
"xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "インデックス",
"xpack.enterpriseSearch.navigation.contentPlaygroundLinkLabel": "Playground",
"xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "Webクローラー",
"xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel": "推論エンドポイント",
"xpack.enterpriseSearch.noEntSearch.noCrawler": "Elastic Webクローラーはエンタープライズ サーチなしでは利用できません。",

View file

@ -17798,7 +17798,6 @@
"xpack.enterpriseSearch.content.overview.gettingStarted.generateApiKeyPanel.panelTitle": "生成 API 密钥",
"xpack.enterpriseSearch.content.overview.gettingStarted.pageDescription": "设置您的编程语言客户端,采集一些数据,如此即可在数分钟内开始搜索。",
"xpack.enterpriseSearch.content.overview.gettingStarted.pageTitle": "Elasticsearch 语言客户端",
"xpack.enterpriseSearch.content.playground.breadcrumb": "Playground",
"xpack.enterpriseSearch.content.searchIndex.cancelSync.successMessage": "已成功取消同步",
"xpack.enterpriseSearch.content.searchIndex.cancelSyncs.successMessage": "已成功取消同步",
"xpack.enterpriseSearch.content.searchIndex.cannotConnect.body": "Elastic 网络爬虫需要 Enterprise Search。{link}",
@ -18374,7 +18373,6 @@
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "引擎",
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "连接器",
"xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "索引",
"xpack.enterpriseSearch.navigation.contentPlaygroundLinkLabel": "Playground",
"xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "网络爬虫",
"xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel": "推理终端",
"xpack.enterpriseSearch.noEntSearch.noCrawler": "如果没有 Enterprise SearchElastic 网络爬虫将不可用。",

View file

@ -129,6 +129,7 @@ export default function ({ getService }: FtrProviderContext) {
'rulesSettings',
'uptime',
'searchInferenceEndpoints',
'searchPlayground',
'siem',
'slo',
'securitySolutionAssistant',
@ -180,6 +181,7 @@ export default function ({ getService }: FtrProviderContext) {
'rulesSettings',
'uptime',
'searchInferenceEndpoints',
'searchPlayground',
'siem',
'slo',
'securitySolutionAssistant',

View file

@ -60,6 +60,7 @@ export default function ({ getService }: FtrProviderContext) {
],
observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'],
slo: ['all', 'read', 'minimal_all', 'minimal_read'],
searchPlayground: ['all', 'read', 'minimal_all', 'minimal_read'],
searchInferenceEndpoints: ['all', 'read', 'minimal_all', 'minimal_read'],
fleetv2: ['all', 'read', 'minimal_all', 'minimal_read'],
fleet: ['all', 'read', 'minimal_all', 'minimal_read'],

View file

@ -50,6 +50,7 @@ export default function ({ getService }: FtrProviderContext) {
securitySolutionAttackDiscovery: ['all', 'read', 'minimal_all', 'minimal_read'],
securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read'],
securitySolutionCasesV2: ['all', 'read', 'minimal_all', 'minimal_read'],
searchPlayground: ['all', 'read', 'minimal_all', 'minimal_read'],
searchInferenceEndpoints: ['all', 'read', 'minimal_all', 'minimal_read'],
fleetv2: ['all', 'read', 'minimal_all', 'minimal_read'],
fleet: ['all', 'read', 'minimal_all', 'minimal_read'],
@ -146,6 +147,7 @@ export default function ({ getService }: FtrProviderContext) {
],
observabilityAIAssistant: ['all', 'read', 'minimal_all', 'minimal_read'],
slo: ['all', 'read', 'minimal_all', 'minimal_read'],
searchPlayground: ['all', 'read', 'minimal_all', 'minimal_read'],
searchInferenceEndpoints: ['all', 'read', 'minimal_all', 'minimal_read'],
fleetv2: ['all', 'read', 'minimal_all', 'minimal_read'],
fleet: ['all', 'read', 'minimal_all', 'minimal_read'],

View file

@ -144,15 +144,15 @@ export default function searchSolutionNavigation({
// check Build
// > Playground
await solutionNavigation.sidenav.clickLink({
deepLinkId: 'enterpriseSearchApplications:playground',
deepLinkId: 'searchPlayground',
});
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'enterpriseSearchApplications:playground',
deepLinkId: 'searchPlayground',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Build' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Playground' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'enterpriseSearchApplications:playground',
deepLinkId: 'searchPlayground',
});
// > Search applications
await solutionNavigation.sidenav.clickLink({
@ -293,7 +293,7 @@ export default function searchSolutionNavigation({
'enterpriseSearchContent:connectors',
'enterpriseSearchContent:webCrawlers',
'build',
'enterpriseSearchApplications:playground',
'searchPlayground',
'enterpriseSearchApplications:searchApplications',
'enterpriseSearchAnalytics',
'relevance',

View file

@ -84,6 +84,7 @@ export default function ({ getService }: FtrProviderContext) {
apm: 0,
enterpriseSearch: 0,
searchInferenceEndpoints: 0,
searchPlayground: 0,
siem: 0,
securitySolutionCases: 0,
securitySolutionCasesV2: 0,

View file

@ -93,6 +93,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
'enterpriseSearchVectorSearch',
'enterpriseSearchSemanticSearch',
'enterpriseSearchElasticsearch',
'searchPlayground',
'searchInferenceEndpoints',
'appSearch',
'observabilityAIAssistant',

View file

@ -64,6 +64,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
'monitoring',
'observabilityAIAssistant',
'enterpriseSearch',
'searchPlayground',
'searchInferenceEndpoints',
'guidedOnboardingFeature',
'securitySolutionAssistant',