[Search] Homepage Plugin setup (#186224)

## Summary

Introducing the `search_homepage` plugin along with integration into
`enterprise_search` and `serverless_search` behind a feature flag. This
will allow implementing the feature gated behind the feature flag.

To test these changes you can enable the feature flag with the Kibana
Dev Console using the following command:
```
POST kbn:/internal/kibana/settings/searchHomepage:homepageEnabled
{"value": true}
```

You can then disable the feature flag with the following command:
```
DELETE kbn:/internal/kibana/settings/searchHomepage:homepageEnabled
```

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Rodney Norris 2024-06-19 05:47:18 -05:00 committed by GitHub
parent e2b772aeae
commit 74c4d3a85e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 1025 additions and 56 deletions

1
.github/CODEOWNERS vendored
View file

@ -716,6 +716,7 @@ packages/kbn-search-connectors @elastic/search-kibana
x-pack/plugins/search_connectors @elastic/search-kibana
packages/kbn-search-errors @elastic/kibana-data-discovery
examples/search_examples @elastic/kibana-data-discovery
x-pack/plugins/search_homepage @elastic/search-kibana
packages/kbn-search-index-documents @elastic/search-kibana
x-pack/plugins/search_inference_endpoints @elastic/search-kibana
x-pack/plugins/search_notebooks @elastic/search-kibana

View file

@ -69,3 +69,6 @@ xpack.searchInferenceEndpoints.ui.enabled: false
# Search Notebooks
xpack.search.notebooks.catalog.url: https://elastic-enterprise-search.s3.us-east-2.amazonaws.com/serverless/catalog.json
# Search Homepage
xpack.search.homepage.ui.enabled: true

View file

@ -789,6 +789,10 @@ It uses Chromium and Puppeteer underneath to run the browser in headless mode.
|This plugin contains common assets and endpoints for the use of connectors in Kibana. Primarily used by the enterprise_search and serverless_search plugins.
|{kib-repo}blob/{branch}/x-pack/plugins/search_homepage/README.mdx[searchHomepage]
|The Search Homepage is a shared homepage for elasticsearch users.
|{kib-repo}blob/{branch}/x-pack/plugins/search_inference_endpoints/README.md[searchInferenceEndpoints]
|The Inference Endpoints is a tool used to manage inference endpoints

View file

@ -727,6 +727,7 @@
"@kbn/search-connectors-plugin": "link:x-pack/plugins/search_connectors",
"@kbn/search-errors": "link:packages/kbn-search-errors",
"@kbn/search-examples-plugin": "link:examples/search_examples",
"@kbn/search-homepage": "link:x-pack/plugins/search_homepage",
"@kbn/search-index-documents": "link:packages/kbn-search-index-documents",
"@kbn/search-inference-endpoints": "link:x-pack/plugins/search_inference_endpoints",
"@kbn/search-notebooks": "link:x-pack/plugins/search_notebooks",

View file

@ -17,3 +17,4 @@ export const SERVERLESS_ES_APP_ID = 'serverlessElasticsearch';
export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors';
export const SERVERLESS_ES_SEARCH_PLAYGROUND_ID = 'searchPlayground';
export const SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID = 'searchInferenceEndpoints';
export const SEARCH_HOMEPAGE = 'searchHomepage';

View file

@ -17,6 +17,7 @@ import {
ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID,
SERVERLESS_ES_SEARCH_PLAYGROUND_ID,
SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID,
SEARCH_HOMEPAGE,
} from './constants';
export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID;
@ -29,6 +30,7 @@ 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 SearchInferenceEndpointsId = typeof SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID;
export type SearchHomepage = typeof SEARCH_HOMEPAGE;
export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers';
@ -47,6 +49,7 @@ export type DeepLinkId =
| ConnectorsId
| SearchPlaygroundId
| SearchInferenceEndpointsId
| SearchHomepage
| `${EnterpriseSearchContentApp}:${ContentLinkId}`
| `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}`
| `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}`;

View file

@ -132,6 +132,7 @@ pageLoadAssetSize:
screenshotMode: 17856
screenshotting: 22870
searchConnectors: 30000
searchHomepage: 19831
searchInferenceEndpoints: 20470
searchNotebooks: 18942
searchPlayground: 19325

View file

@ -313,8 +313,9 @@ 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.search.homepage.ui.enabled (boolean)',
'xpack.searchInferenceEndpoints.ui.enabled (boolean)',
'xpack.searchPlayground.ui.enabled (boolean)',
'xpack.security.loginAssistanceMessage (string)',
'xpack.security.sameSiteCookies (alternatives)',
'xpack.security.showInsecureClusterWarning (boolean)',

View file

@ -1426,6 +1426,8 @@
"@kbn/search-errors/*": ["packages/kbn-search-errors/*"],
"@kbn/search-examples-plugin": ["examples/search_examples"],
"@kbn/search-examples-plugin/*": ["examples/search_examples/*"],
"@kbn/search-homepage": ["x-pack/plugins/search_homepage"],
"@kbn/search-homepage/*": ["x-pack/plugins/search_homepage/*"],
"@kbn/search-index-documents": ["packages/kbn-search-index-documents"],
"@kbn/search-index-documents/*": ["packages/kbn-search-index-documents/*"],
"@kbn/search-inference-endpoints": ["x-pack/plugins/search_inference_endpoints"],

View file

@ -92,6 +92,7 @@
"xpack.rollupJobs": ["plugins/rollup"],
"xpack.runtimeFields": "plugins/runtime_fields",
"xpack.screenshotting": "plugins/screenshotting",
"xpack.searchHomepage": "plugins/search_homepage",
"xpack.searchNotebooks": "plugins/search_notebooks",
"xpack.searchPlayground": "plugins/search_playground",
"xpack.searchInferenceEndpoints": "plugins/search_inference_endpoints",

View file

@ -30,6 +30,7 @@
"guidedOnboarding",
"console",
"searchConnectors",
"searchHomepage",
"searchPlayground",
"searchInferenceEndpoints",
"embeddable",

View file

@ -45,6 +45,7 @@ export const mockKibanaValues = {
history: mockHistory,
indexMappingComponent: null,
isCloud: false,
isSearchHomepageEnabled: false,
isSidebarEnabled: true,
lens: {
EmbeddableComponent: jest.fn(),
@ -64,6 +65,7 @@ export const mockKibanaValues = {
hasWebCrawler: true,
},
renderHeaderActions: jest.fn(),
searchHomepage: null,
searchInferenceEndpoints: null,
searchPlayground: searchPlaygroundMock.createStart(),
security: securityMock.createStart(),

View file

@ -117,6 +117,7 @@ export const renderApp = (
guidedOnboarding,
history,
indexMappingComponent,
isSearchHomepageEnabled: plugins.searchHomepage?.isHomepageFeatureEnabled() ?? false,
isSidebarEnabled,
lens,
ml,
@ -127,6 +128,7 @@ export const renderApp = (
params.setHeaderActionMenu(
HeaderActions ? renderHeaderActions.bind(null, HeaderActions, store, params) : undefined
),
searchHomepage: plugins.searchHomepage,
searchPlayground: plugins.searchPlayground,
searchInferenceEndpoints: plugins.searchInferenceEndpoints,
security,

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 { TestHelper } from '../../../test_helpers/test_utils.test_helper';
import { SearchHomepagePageTemplate } from './page_template';
describe('SearchHomepagePageTemplate', () => {
beforeAll(() => {
TestHelper.prepare();
});
it('renders as expected', async () => {
const { container } = TestHelper.render(
<SearchHomepagePageTemplate>
<div>Test</div>
</SearchHomepagePageTemplate>
);
expect(container.querySelector('.kbnSolutionNav__title')).toHaveTextContent('Search');
});
});

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 React from 'react';
import { SEARCH_PRODUCT_NAME } from '../../../../../common/constants';
import { SetSearchChrome } from '../../../shared/kibana_chrome';
import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout';
import { useEnterpriseSearchNav } from '../../../shared/layout';
import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
export const SearchHomepagePageTemplate: React.FC<PageTemplateProps> = ({
children,
pageChrome,
pageViewTelemetry,
...pageTemplateProps
}) => {
return (
<EnterpriseSearchPageTemplateWrapper
{...pageTemplateProps}
solutionNav={{
name: SEARCH_PRODUCT_NAME,
items: useEnterpriseSearchNav(),
}}
setPageChrome={pageChrome && <SetSearchChrome trail={pageChrome} />}
>
{pageViewTelemetry && (
<SendEnterpriseSearchTelemetry action="viewed" metric={pageViewTelemetry} />
)}
{children}
</EnterpriseSearchPageTemplateWrapper>
);
};

View file

@ -0,0 +1,35 @@
/*
* 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 { SetSearchChrome } from '../../shared/kibana_chrome';
import { SearchHomepagePageTemplate } from './layout/page_template';
export const SearchHomepagePage = () => {
const { isSearchHomepageEnabled, searchHomepage } = useValues(KibanaLogic);
if (!isSearchHomepageEnabled || !searchHomepage) {
return null;
}
return (
<SearchHomepagePageTemplate
restrictWidth={false}
grow={false}
offset={0}
pageViewTelemetry="searchHomepage"
>
<SetSearchChrome />
<searchHomepage.SearchHomepage />
</SearchHomepagePageTemplate>
);
};

View file

@ -0,0 +1,42 @@
/*
* 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 { Routes, Route } from '@kbn/shared-ux-router';
import { isVersionMismatch } from '../../../common/is_version_mismatch';
import type { InitialAppData } from '../../../common/types';
import { VersionMismatchPage } from '../shared/version_mismatch';
import { SearchHomepagePage } from './components/search_homepage';
export const SearchHomepage: React.FC<InitialAppData> = (props) => {
const { enterpriseSearchVersion, kibanaVersion } = props;
const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion);
const showView = () => {
if (incompatibleVersions) {
return (
<VersionMismatchPage
enterpriseSearchVersion={enterpriseSearchVersion}
kibanaVersion={kibanaVersion}
/>
);
}
return <SearchHomepagePage />;
};
return (
<Routes>
<Route exact path="/">
{showView()}
</Route>
</Routes>
);
};

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../../..',
roots: ['<rootDir>/x-pack/plugins/enterprise_search/public/applications/search_homepage'],
collectCoverage: true,
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}',
'!<rootDir>/x-pack/plugins/enterprise_search/public/*.ts',
'!<rootDir>/x-pack/plugins/enterprise_search/server/*.ts',
'!<rootDir>/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}',
],
coverageDirectory:
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/search_homepage',
modulePathIgnorePatterns: [
'<rootDir>/x-pack/plugins/enterprise_search/public/applications/app_search/cypress',
'<rootDir>/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress',
],
};

View file

@ -29,6 +29,7 @@ import { LensPublicStart } from '@kbn/lens-plugin/public';
import { MlPluginStart } from '@kbn/ml-plugin/public';
import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants';
import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public';
import type { SearchHomepagePluginStart } from '@kbn/search-homepage/public';
import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public';
@ -58,6 +59,7 @@ export interface KibanaLogicProps {
guidedOnboarding?: GuidedOnboardingPluginStart;
history: ScopedHistory;
indexMappingComponent?: React.FC<IndexMappingProps>;
isSearchHomepageEnabled: boolean;
isSidebarEnabled: boolean;
lens?: LensPublicStart;
ml?: MlPluginStart;
@ -65,6 +67,7 @@ export interface KibanaLogicProps {
productAccess: ProductAccess;
productFeatures: ProductFeatures;
renderHeaderActions(HeaderActions?: FC): void;
searchHomepage?: SearchHomepagePluginStart;
searchPlayground?: SearchPlaygroundPluginStart;
searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart;
security?: SecurityPluginStart;
@ -91,6 +94,7 @@ export interface KibanaValues {
history: ScopedHistory;
indexMappingComponent: React.FC<IndexMappingProps> | null;
isCloud: boolean;
isSearchHomepageEnabled: boolean;
isSidebarEnabled: boolean;
lens: LensPublicStart | null;
ml: MlPluginStart | null;
@ -98,6 +102,7 @@ export interface KibanaValues {
productAccess: ProductAccess;
productFeatures: ProductFeatures;
renderHeaderActions(HeaderActions?: FC): void;
searchHomepage: SearchHomepagePluginStart | null;
searchPlayground: SearchPlaygroundPluginStart | null;
searchInferenceEndpoints: SearchInferenceEndpointsPluginStart | null;
security: SecurityPluginStart | null;
@ -129,6 +134,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
guidedOnboarding: [props.guidedOnboarding || null, {}],
history: [props.history, {}],
indexMappingComponent: [props.indexMappingComponent || null, {}],
isSearchHomepageEnabled: [props.isSearchHomepageEnabled, {}],
isSidebarEnabled: [props.isSidebarEnabled, {}],
lens: [props.lens || null, {}],
ml: [props.ml || null, {}],
@ -143,6 +149,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
productAccess: [props.productAccess, {}],
productFeatures: [props.productFeatures, {}],
renderHeaderActions: [props.renderHeaderActions, {}],
searchHomepage: [props.searchHomepage || null, {}],
searchPlayground: [props.searchPlayground || null, {}],
searchInferenceEndpoints: [props.searchInferenceEndpoints || null, {}],
security: [props.security || null, {}],

View file

@ -0,0 +1,18 @@
/*
* 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 { ENTERPRISE_SEARCH_OVERVIEW_PLUGIN } from '../../../../common/constants';
/**
* HACK for base homepage URL, this can be removed and updated to a static
* URL when Search Homepage is no longer feature flagged.
*/
const breadCrumbHome = { url: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL };
export const getHomeURL = () => breadCrumbHome.url;
export const setBreadcrumbHomeUrl = (url: string) => {
breadCrumbHome.url = url;
};

View file

@ -15,7 +15,6 @@ import {
APP_SEARCH_PLUGIN,
ENTERPRISE_SEARCH_CONTENT_PLUGIN,
INFERENCE_ENDPOINTS_PLUGIN,
ENTERPRISE_SEARCH_OVERVIEW_PLUGIN,
ENTERPRISE_SEARCH_PRODUCT_NAME,
AI_SEARCH_PLUGIN,
SEARCH_EXPERIENCES_PLUGIN,
@ -29,6 +28,8 @@ import { HttpLogic } from '../http';
import { KibanaLogic } from '../kibana';
import { letBrowserHandleEvent, createHref } from '../react_router_helpers';
import { getHomeURL } from './breadcrumbs_home';
/**
* Types
*/
@ -107,7 +108,7 @@ export const useSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
useEuiBreadcrumbs([
{
text: SEARCH_PRODUCT_NAME,
path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
path: getHomeURL(),
shouldNotCreateHref: true,
},
...breadcrumbs,
@ -117,7 +118,7 @@ export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
useEuiBreadcrumbs([
{
text: ENTERPRISE_SEARCH_PRODUCT_NAME,
path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
path: getHomeURL(),
shouldNotCreateHref: true,
},
...breadcrumbs,

View file

@ -49,7 +49,8 @@ import { generateNavLink } from './nav_link_helpers';
* @returns The Enterprise Search navigation items
*/
export const useEnterpriseSearchNav = (alwaysReturn = false) => {
const { isSidebarEnabled, productAccess } = useValues(KibanaLogic);
const { isSearchHomepageEnabled, searchHomepage, isSidebarEnabled, productAccess } =
useValues(KibanaLogic);
const indicesNavItems = useIndicesNav();
if (!isSidebarEnabled && !alwaysReturn) return undefined;
@ -66,7 +67,10 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => {
...generateNavLink({
shouldNotCreateHref: true,
shouldShowActiveForSubroutes: true,
to: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
to:
isSearchHomepageEnabled && searchHomepage
? searchHomepage.app.appRoute
: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
}),
},
{

View file

@ -62,6 +62,7 @@ export const mockKibanaProps: KibanaLogicProps = {
indexMappingComponent: () => {
return <></>;
},
isSearchHomepageEnabled: false,
isSidebarEnabled: true,
lens: {
EmbeddableComponent: jest.fn(),
@ -84,6 +85,7 @@ export const mockKibanaProps: KibanaLogicProps = {
hasWebCrawler: true,
},
renderHeaderActions: jest.fn(),
searchHomepage: undefined,
searchPlayground: searchPlaygroundMock.createStart(),
security: securityMock.createStart(),
setBreadcrumbs: jest.fn(),
@ -114,7 +116,7 @@ interface TestHelper {
defaultMockValues: typeof DEFAULT_VALUES;
mountLogic: (logicFile: LogicFile, props?: object) => void;
prepare: (options?: PrepareOptions) => void;
render: (children: JSX.Element) => void;
render: (children: JSX.Element) => ReturnType<typeof testingLibraryRender>;
}
export const TestHelper: TestHelper = {
@ -147,7 +149,7 @@ export const TestHelper: TestHelper = {
TestHelper.actionsToRun.forEach((action) => {
action();
});
testingLibraryRender(
return testingLibraryRender(
<I18nProvider>
<Provider>{children}</Provider>
</I18nProvider>

View file

@ -67,12 +67,14 @@ const euiItemTypeToNodeDefinition = ({
export const getNavigationTreeDefinition = ({
dynamicItems$,
isSearchHomepageEnabled,
}: {
dynamicItems$: Observable<DynamicSideNavItems>;
isSearchHomepageEnabled: boolean;
}): AddSolutionNavigationArg => {
return {
dataTestSubj: 'searchSideNav',
homePage: 'enterpriseSearch',
homePage: isSearchHomepageEnabled ? 'searchHomepage' : 'enterpriseSearch',
icon,
id: 'es',
navigationTree$: dynamicItems$.pipe(
@ -84,7 +86,7 @@ export const getNavigationTreeDefinition = ({
breadcrumbStatus: 'hidden',
children: [
{
link: 'enterpriseSearch',
link: isSearchHomepageEnabled ? 'searchHomepage' : 'enterpriseSearch',
},
{
getIsActive: ({ pathNameSerialized, prepend }) => {

View file

@ -32,6 +32,10 @@ import { MlPluginStart } from '@kbn/ml-plugin/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants';
import { SearchConnectorsPluginStart } from '@kbn/search-connectors-plugin/public';
import type {
SearchHomepagePluginSetup,
SearchHomepagePluginStart,
} from '@kbn/search-homepage/public';
import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
@ -67,6 +71,7 @@ import {
import { INFERENCE_ENDPOINTS_PATH } from './applications/enterprise_search_relevance/routes';
import { docLinks } from './applications/shared/doc_links';
import { setBreadcrumbHomeUrl } from './applications/shared/kibana_chrome/breadcrumbs_home';
import type { DynamicSideNavItems } from './navigation_tree';
export interface ClientData extends InitialAppData {
@ -80,6 +85,7 @@ export type EnterpriseSearchPublicStart = ReturnType<EnterpriseSearchPlugin['sta
interface PluginsSetup {
cloud?: CloudSetup;
home?: HomePublicPluginSetup;
searchHomepage?: SearchHomepagePluginSetup;
security?: SecurityPluginSetup;
share?: SharePluginSetup;
}
@ -96,6 +102,7 @@ export interface PluginsStart {
ml?: MlPluginStart;
navigation: NavigationPublicPluginStart;
searchConnectors?: SearchConnectorsPluginStart;
searchHomepage?: SearchHomepagePluginStart;
searchPlayground?: SearchPlaygroundPluginStart;
searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart;
security?: SecurityPluginStart;
@ -255,30 +262,56 @@ export class EnterpriseSearchPlugin implements Plugin {
return;
}
const { cloud, share } = plugins;
const useSearchHomepage =
plugins.searchHomepage && plugins.searchHomepage.isHomepageFeatureEnabled();
core.application.register({
appRoute: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO,
id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAME);
if (useSearchHomepage) {
const { app } = plugins.searchHomepage!;
core.application.register({
...app,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO,
visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'],
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(app.title);
await this.getInitialData(http);
const pluginData = this.getPluginData();
await this.getInitialData(http);
const pluginData = this.getPluginData();
const { renderApp } = await import('./applications');
const { EnterpriseSearchOverview } = await import(
'./applications/enterprise_search_overview'
);
const { renderApp } = await import('./applications');
const { SearchHomepage } = await import('./applications/search_homepage');
return renderApp(EnterpriseSearchOverview, kibanaDeps, pluginData);
},
title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE,
visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'],
});
return renderApp(SearchHomepage, kibanaDeps, pluginData);
},
});
setBreadcrumbHomeUrl(app.appRoute);
} else {
core.application.register({
appRoute: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO,
id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAME);
await this.getInitialData(http);
const pluginData = this.getPluginData();
const { renderApp } = await import('./applications');
const { EnterpriseSearchOverview } = await import(
'./applications/enterprise_search_overview'
);
return renderApp(EnterpriseSearchOverview, kibanaDeps, pluginData);
},
title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE,
visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'],
});
}
core.application.register({
appRoute: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL,
@ -512,14 +545,27 @@ export class EnterpriseSearchPlugin implements Plugin {
}
if (plugins.home) {
plugins.home.featureCatalogue.registerSolution({
description: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.DESCRIPTION,
icon: 'logoEnterpriseSearch',
id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID,
order: 100,
path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
title: SEARCH_PRODUCT_NAME,
});
if (useSearchHomepage) {
const { searchHomepage } = plugins;
plugins.home.featureCatalogue.registerSolution({
description: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.DESCRIPTION,
icon: 'logoEnterpriseSearch',
id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID,
order: 100,
path: searchHomepage!.app.appRoute,
title: SEARCH_PRODUCT_NAME,
});
} else {
plugins.home.featureCatalogue.registerSolution({
description: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.DESCRIPTION,
icon: 'logoEnterpriseSearch',
id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID,
order: 100,
path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
title: SEARCH_PRODUCT_NAME,
});
}
plugins.home.featureCatalogue.register({
category: 'data',
@ -587,7 +633,10 @@ export class EnterpriseSearchPlugin implements Plugin {
import('./navigation_tree').then(({ getNavigationTreeDefinition }) => {
return plugins.navigation.addSolutionNavigation(
getNavigationTreeDefinition({ dynamicItems$: this.sideNavDynamicItems$ })
getNavigationTreeDefinition({
dynamicItems$: this.sideNavDynamicItems$,
isSearchHomepageEnabled: plugins.searchHomepage?.isHomepageFeatureEnabled() ?? false,
})
);
});

View file

@ -79,6 +79,7 @@
"@kbn/cloud",
"@kbn/try-in-console",
"@kbn/core-chrome-browser",
"@kbn/navigation-plugin"
"@kbn/navigation-plugin",
"@kbn/search-homepage"
]
}

View file

@ -0,0 +1,3 @@
# Search Homepage
The Search Homepage is a shared homepage for elasticsearch users.

View file

@ -0,0 +1,14 @@
/*
* 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 PLUGIN_ID = 'searchHomepage';
export const PLUGIN_NAME = 'searchHomepage';
/**
* UI Setting id for the Search Homepage feature flag
*/
export const HOMEPAGE_FEATURE_FLAG_ID = 'searchHomepage:homepageEnabled';

View file

@ -0,0 +1,15 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/x-pack/plugins/search_homepage'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/search_homepage',
coverageReporters: ['text', 'html'],
collectCoverageFrom: ['<rootDir>/x-pack/plugins/search_homepage/{public,server}/**/*.{ts,tsx}'],
};

View file

@ -0,0 +1,26 @@
{
"type": "plugin",
"id": "@kbn/search-homepage",
"owner": "@elastic/search-kibana",
"plugin": {
"id": "searchHomepage",
"server": true,
"browser": true,
"configPath": [
"xpack",
"search",
"homepage"
],
"requiredPlugins": [
"share",
],
"optionalPlugins": [
"cloud",
"console",
"usageCollection",
],
"requiredBundles": [
"kibanaReact"
]
}
}

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 React from 'react';
import ReactDOM from 'react-dom';
import { CoreStart } from '@kbn/core/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { I18nProvider } from '@kbn/i18n-react';
import { Router } from '@kbn/shared-ux-router';
import { SearchHomepageAppPluginStartDependencies } from './types';
import { HomepageRouter } from './router';
export const renderApp = async (
core: CoreStart,
services: SearchHomepageAppPluginStartDependencies,
element: HTMLElement
) => {
ReactDOM.render(
<KibanaRenderContextProvider {...core}>
<KibanaContextProvider services={{ ...core, ...services }}>
<I18nProvider>
<Router history={services.history}>
<HomepageRouter />
</Router>
</I18nProvider>
</KibanaContextProvider>
</KibanaRenderContextProvider>,
element
);
return () => ReactDOM.unmountComponentAtNode(element);
};

View file

@ -0,0 +1,32 @@
/*
* 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, { useMemo } from 'react';
import { EuiPageTemplate } from '@elastic/eui';
import { useKibana } from '../hooks/use_kibana';
import { SearchHomepageBody } from './search_homepage_body';
import { SearchHomepageHeader } from './search_homepage_header';
export const SearchHomepagePage = () => {
const {
services: { console: consolePlugin },
} = useKibana();
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
[consolePlugin]
);
return (
<EuiPageTemplate offset={0} restrictWidth={false} data-test-subj="search-homepage" grow={false}>
<SearchHomepageHeader />
<SearchHomepageBody />
{embeddableConsole}
</EuiPageTemplate>
);
};

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 React from 'react';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
export const SearchHomepageBody = () => (
<KibanaPageTemplate.Section
alignment="top"
restrictWidth={false}
grow
css={{
position: 'relative',
}}
contentProps={{ css: { display: 'flex', flexGrow: 1, position: 'absolute', inset: 0 } }}
paddingSize="none"
className="eui-fullHeight"
>
<div />
</KibanaPageTemplate.Section>
);

View file

@ -0,0 +1,26 @@
/*
* 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 { EuiPageTemplate, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
export const SearchHomepageHeader = () => (
<EuiPageTemplate.Header
pageTitle={
<EuiTitle data-test-subj="search-homepage-header-title">
<h2>
<FormattedMessage
id="xpack.searchHomepage.pageTitle"
defaultMessage="Welcome to Search"
/>
</h2>
</EuiTitle>
}
data-test-subj="search-homepage-header"
rightSideItems={[]}
/>
);

View file

@ -0,0 +1,19 @@
/*
* 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 { SearchHomepageBody } from './search_homepage_body';
import { SearchHomepageHeader } from './search_homepage_header';
export const App: React.FC = () => {
return (
<>
<SearchHomepageHeader />
<SearchHomepageBody />
</>
);
};

View file

@ -0,0 +1,12 @@
/*
* 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 { dynamic } from '@kbn/shared-ux-utility';
export const SearchHomepage = dynamic(async () => ({
default: (await import('./components/stack_app')).App,
}));

View file

@ -0,0 +1,13 @@
/*
* 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 { IUiSettingsClient } from '@kbn/core/public';
import { HOMEPAGE_FEATURE_FLAG_ID } from '../common';
export function isHomepageEnabled(uiSettings: IUiSettingsClient): boolean {
return uiSettings.get<boolean>(HOMEPAGE_FEATURE_FLAG_ID, false);
}

View file

@ -0,0 +1,11 @@
/*
* 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 { useKibana as _useKibana } from '@kbn/kibana-react-plugin/public';
import { SearchHomepageServicesContext } from '../types';
export const useKibana = () => _useKibana<SearchHomepageServicesContext>();

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 { PluginInitializerContext } from '@kbn/core/public';
import { SearchHomepagePlugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new SearchHomepagePlugin(initializerContext);
}
export type {
SearchHomepagePluginSetup,
SearchHomepagePluginStart,
SearchHomepageAppInfo,
} from './types';

View file

@ -0,0 +1,80 @@
/*
* 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 {
CoreSetup,
Plugin,
CoreStart,
AppMountParameters,
PluginInitializerContext,
} from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { PLUGIN_ID } from '../common';
import { SearchHomepage } from './embeddable';
import { isHomepageEnabled } from './feature_flags';
import {
SearchHomepageConfigType,
SearchHomepagePluginSetup,
SearchHomepagePluginStart,
SearchHomepageAppPluginStartDependencies,
SearchHomepageAppInfo,
} from './types';
const appInfo: SearchHomepageAppInfo = {
id: PLUGIN_ID,
appRoute: '/app/elasticsearch/home',
title: i18n.translate('xpack.searchHomepage.appTitle', { defaultMessage: 'Home' }),
};
export class SearchHomepagePlugin
implements Plugin<SearchHomepagePluginSetup, SearchHomepagePluginStart, {}, {}>
{
private readonly config: SearchHomepageConfigType;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get<SearchHomepageConfigType>();
}
public setup(
core: CoreSetup<SearchHomepageAppPluginStartDependencies, SearchHomepagePluginStart>
) {
const result: SearchHomepagePluginSetup = {
app: appInfo,
isHomepageFeatureEnabled() {
return isHomepageEnabled(core.uiSettings);
},
};
if (!this.config.ui?.enabled) return result;
if (!isHomepageEnabled(core.uiSettings)) return result;
core.application.register({
...result.app,
async mount({ element, history }: AppMountParameters) {
const { renderApp } = await import('./application');
const [coreStart, depsStart] = await core.getStartServices();
const startDeps: SearchHomepageAppPluginStartDependencies = {
...depsStart,
history,
};
return renderApp(coreStart, startDeps, element);
},
});
return result;
}
public start(core: CoreStart) {
return {
app: appInfo,
isHomepageFeatureEnabled() {
return isHomepageEnabled(core.uiSettings);
},
SearchHomepage,
};
}
}

View file

@ -0,0 +1,19 @@
/*
* 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 { Route, Routes } from '@kbn/shared-ux-router';
import { SearchHomepagePage } from './components/search_homepage';
export const HomepageRouter = () => (
<Routes>
<Route>
<SearchHomepagePage />
</Route>
</Routes>
);

View file

@ -0,0 +1,74 @@
/*
* 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 { ComponentProps, FC } from 'react';
import type { CloudSetup } from '@kbn/cloud-plugin/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
import type { AppMountParameters, HttpStart } from '@kbn/core/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { App } from './components/stack_app';
export interface SearchHomepageConfigType {
ui: {
enabled: boolean;
};
}
export interface SearchHomepageAppInfo {
appRoute: string;
id: string;
title: string;
}
export interface SearchHomepagePluginSetup {
/**
* Search Homepage shared information for the Kibana application.
* Used to ensure the stack and serverless apps have the same route
* and deep links.
*/
app: SearchHomepageAppInfo;
/**
* Checks if the Search Homepage feature flag is currently enabled.
* @returns true if Search Homepage feature is enabled
*/
isHomepageFeatureEnabled: () => boolean;
}
export interface SearchHomepagePluginStart {
/**
* Search Homepage shared information for the Kibana application.
* Used to ensure the stack and serverless apps have the same route
* and deep links.
*/
app: SearchHomepageAppInfo;
/**
* Checks if the Search Homepage feature flag is currently enabled.
* @returns true if Search Homepage feature is enabled
*/
isHomepageFeatureEnabled: () => boolean;
/**
* SearchHomepage shared component, used to render the search homepage in
* the Stack search plugin
*/
SearchHomepage: FC<ComponentProps<typeof App>>;
}
export interface SearchHomepageAppPluginStartDependencies {
history: AppMountParameters['history'];
usageCollection?: UsageCollectionStart;
share: SharePluginStart;
console?: ConsolePluginStart;
}
export interface SearchHomepageServicesContext {
http: HttpStart;
share: SharePluginStart;
cloud?: CloudSetup;
usageCollection?: UsageCollectionStart;
console?: ConsolePluginStart;
}

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 SearchHomepageConfig = TypeOf<typeof configSchema>;
export const config: PluginConfigDescriptor<SearchHomepageConfig> = {
exposeToBrowser: {
ui: true,
},
schema: configSchema,
};

View file

@ -0,0 +1,17 @@
/*
* 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 { PluginInitializerContext } from '@kbn/core/server';
export { config } from './config';
export async function plugin(initializerContext: PluginInitializerContext) {
const { SearchHomepagePlugin } = await import('./plugin');
return new SearchHomepagePlugin(initializerContext);
}
export type { SearchHomepagePluginSetup, SearchHomepagePluginStart } from './types';

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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server';
import { SearchHomepagePluginSetup, SearchHomepagePluginStart } from './types';
export class SearchHomepagePlugin
implements Plugin<SearchHomepagePluginSetup, SearchHomepagePluginStart, {}, {}>
{
private readonly logger: Logger;
constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
}
public setup(core: CoreSetup<{}, SearchHomepagePluginStart>) {
this.logger.debug('searchHomepage: Setup');
return {};
}
public start(core: CoreStart) {
return {};
}
}

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchHomepagePluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SearchHomepagePluginStart {}

View file

@ -0,0 +1,30 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
},
"include": [
"__mocks__/**/*",
"common/**/*",
"public/**/*",
"server/**/*",
],
"kbn_references": [
"@kbn/core",
"@kbn/react-kibana-context-render",
"@kbn/kibana-react-plugin",
"@kbn/i18n-react",
"@kbn/shared-ux-router",
"@kbn/shared-ux-page-kibana-template",
"@kbn/shared-ux-utility",
"@kbn/i18n",
"@kbn/cloud-plugin",
"@kbn/console-plugin",
"@kbn/share-plugin",
"@kbn/usage-collection-plugin",
"@kbn/config-schema",
],
"exclude": [
"target/**/*",
]
}

View file

@ -29,6 +29,7 @@
"optionalPlugins": [
"indexManagement",
"searchConnectors",
"searchHomepage",
"searchInferenceEndpoints",
"usageCollection"
],

View file

@ -9,7 +9,7 @@ import type { NavigationTreeDefinition } from '@kbn/core-chrome-browser';
import { i18n } from '@kbn/i18n';
import { CONNECTORS_LABEL } from '../common/i18n_string';
export const navigationTree: NavigationTreeDefinition = {
export const navigationTree = (useSearchHomepage: boolean = false): NavigationTreeDefinition => ({
body: [
{
type: 'navGroup',
@ -25,7 +25,7 @@ export const navigationTree: NavigationTreeDefinition = {
title: i18n.translate('xpack.serverlessSearch.nav.home', {
defaultMessage: 'Home',
}),
link: 'serverlessElasticsearch',
link: useSearchHomepage ? 'searchHomepage' : 'serverlessElasticsearch',
spaceBefore: 'm',
},
{
@ -149,4 +149,4 @@ export const navigationTree: NavigationTreeDefinition = {
],
},
],
};
});

View file

@ -43,6 +43,9 @@ export class ServerlessSearchPlugin
core: CoreSetup<ServerlessSearchPluginStartDependencies, ServerlessSearchPluginStart>,
setupDeps: ServerlessSearchPluginSetupDependencies
): ServerlessSearchPluginSetup {
const { searchHomepage } = setupDeps;
const useSearchHomepage = searchHomepage && searchHomepage.isHomepageFeatureEnabled();
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onError: (error) => {
@ -69,6 +72,24 @@ export class ServerlessSearchPlugin
},
}),
});
if (useSearchHomepage) {
core.application.register({
id: 'serverlessHomeRedirect',
title: i18n.translate('xpack.serverlessSearch.app.home.title', {
defaultMessage: 'Home',
}),
appRoute: '/app/elasticsearch',
euiIconType: 'logoElastic',
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
visibleIn: [],
async mount({}: AppMountParameters) {
const [coreStart] = await core.getStartServices();
coreStart.application.navigateToApp('searchHomepage');
return () => {};
},
});
}
core.application.register({
id: 'serverlessElasticsearch',
title: i18n.translate('xpack.serverlessSearch.app.elasticsearch.title', {
@ -76,7 +97,7 @@ export class ServerlessSearchPlugin
}),
euiIconType: 'logoElastic',
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
appRoute: '/app/elasticsearch',
appRoute: useSearchHomepage ? '/app/elasticsearch/getting_started' : '/app/elasticsearch',
async mount({ element, history }: AppMountParameters) {
const { renderApp } = await import('./application/elasticsearch');
const [coreStart, services] = await core.getStartServices();
@ -121,10 +142,12 @@ export class ServerlessSearchPlugin
core: CoreStart,
services: ServerlessSearchPluginStartDependencies
): ServerlessSearchPluginStart {
const { serverless, management, indexManagement, security } = services;
serverless.setProjectHome('/app/elasticsearch');
const { serverless, management, indexManagement, security, searchHomepage } = services;
const useSearchHomepage = searchHomepage && searchHomepage.isHomepageFeatureEnabled();
const navigationTree$ = of(navigationTree);
serverless.setProjectHome(useSearchHomepage ? '/app/elasticsearch/home' : '/app/elasticsearch');
const navigationTree$ = of(navigationTree(searchHomepage?.isHomepageFeatureEnabled() ?? false));
serverless.initNavigation('search', navigationTree$, { dataTestSubj: 'svlSearchSideNav' });
const extendCardNavDefinitions = serverless.getNavigationCards(

View file

@ -5,16 +5,20 @@
* 2.0.
*/
import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import { ConsolePluginStart } from '@kbn/console-plugin/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { IndexManagementPluginStart } from '@kbn/index-management-plugin/public';
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
import type { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import type { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
import type { SecurityPluginStart } from '@kbn/security-plugin/public';
import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { IndexManagementPluginStart } from '@kbn/index-management-plugin/public';
import type { DiscoverSetup } from '@kbn/discover-plugin/public';
import type {
SearchHomepagePluginSetup,
SearchHomepagePluginStart,
} from '@kbn/search-homepage/public';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ServerlessSearchPluginSetup {}
@ -27,6 +31,7 @@ export interface ServerlessSearchPluginSetupDependencies {
management: ManagementSetup;
serverless: ServerlessPluginSetup;
discover: DiscoverSetup;
searchHomepage?: SearchHomepagePluginSetup;
}
export interface ServerlessSearchPluginStartDependencies {
@ -39,4 +44,5 @@ export interface ServerlessSearchPluginStartDependencies {
serverless: ServerlessPluginStart;
share: SharePluginStart;
indexManagement?: IndexManagementPluginStart;
searchHomepage?: SearchHomepagePluginStart;
}

View file

@ -50,5 +50,6 @@
"@kbn/react-kibana-context-render",
"@kbn/search-playground",
"@kbn/search-inference-endpoints",
"@kbn/search-homepage",
]
}

View file

@ -21,6 +21,7 @@ import { SvlRuleDetailsPageProvider } from './svl_rule_details_ui_page';
import { SvlSearchConnectorsPageProvider } from './svl_search_connectors_page';
import { SvlManagementPageProvider } from './svl_management_page';
import { SvlIngestPipelines } from './svl_ingest_pipelines';
import { SvlSearchHomePageProvider } from './svl_search_homepage';
export const pageObjects = {
...xpackFunctionalPageObjects,
@ -38,4 +39,5 @@ export const pageObjects = {
svlRuleDetailsUI: SvlRuleDetailsPageProvider,
svlManagementPage: SvlManagementPageProvider,
svlIngestPipelines: SvlIngestPipelines,
svlSearchHomePage: SvlSearchHomePageProvider,
};

View file

@ -0,0 +1,26 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlSearchHomePageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const browser = getService('browser');
return {
async expectToBeOnHomepage() {
expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/home');
},
async expectToNotBeOnHomepage() {
expect(await browser.getCurrentUrl()).not.contain('/app/elasticsearch/home');
},
async expectHomepageHeader() {
await testSubjects.existOrFail('search-homepage-header', { timeout: 2000 });
},
};
}

View file

@ -16,6 +16,7 @@ import { SvlCommonScreenshotsProvider } from './svl_common_screenshots';
import { SvlCasesServiceProvider } from '../../api_integration/services/svl_cases';
import { MachineLearningProvider } from './ml';
import { LogsSynthtraceProvider } from './log';
import { UISettingsServiceProvider } from './ui_settings';
export const services = {
// deployment agnostic FTR services
@ -30,6 +31,7 @@ export const services = {
svlCommonScreenshots: SvlCommonScreenshotsProvider,
svlCases: SvlCasesServiceProvider,
svlMl: MachineLearningProvider,
uiSettings: UISettingsServiceProvider,
// log services
svlLogsSynthtraceClient: LogsSynthtraceProvider,
};

View file

@ -0,0 +1,32 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
import { RoleCredentials } from '../../shared/services';
export function UISettingsServiceProvider({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const supertestWithoutAuth = getService('supertestWithoutAuth');
return {
async setUiSetting(role: RoleCredentials, settingId: string, value: unknown) {
await supertestWithoutAuth
.post(`/internal/kibana/settings/${settingId}`)
.set(svlCommonApi.getInternalRequestHeader())
.set(role.apiKeyHeader)
.send({ value })
.expect(200);
},
async deleteUISetting(role: RoleCredentials, settingId: string) {
await supertestWithoutAuth
.delete(`/internal/kibana/settings/${settingId}`)
.set(svlCommonApi.getInternalRequestHeader())
.set(role.apiKeyHeader)
.expect(200);
},
};
}

View file

@ -23,5 +23,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./playground_overview'));
loadTestFile(require.resolve('./ml'));
loadTestFile(require.resolve('./search_homepage'));
});
}

View file

@ -0,0 +1,58 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
import { RoleCredentials } from '../../../shared/services';
import { testHasEmbeddedConsole } from './embedded_console';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation', 'svlSearchHomePage']);
const svlUserManager = getService('svlUserManager');
const uiSettings = getService('uiSettings');
let roleAuthc: RoleCredentials;
const HOMEPAGE_FF_UI_SETTING = 'searchHomepage:homepageEnabled';
describe('Search Homepage', function () {
this.tags('skipMKI');
before(async () => {
roleAuthc = await svlUserManager.createApiKeyForRole('admin');
// Enable Homepage Feature Flag
await uiSettings.setUiSetting(roleAuthc, HOMEPAGE_FF_UI_SETTING, true);
await pageObjects.svlCommonPage.login();
});
after(async () => {
if (!roleAuthc) return;
// Disable Homepage Feature Flag
await uiSettings.deleteUISetting(roleAuthc, HOMEPAGE_FF_UI_SETTING);
await pageObjects.svlCommonPage.forceLogout();
});
it('has search homepage with Home sidenav', async () => {
pageObjects.svlSearchHomePage.expectToBeOnHomepage();
pageObjects.svlSearchHomePage.expectHomepageHeader();
// Navigate to another page
await pageObjects.svlCommonNavigation.sidenav.clickLink({
deepLinkId: 'serverlessConnectors',
});
pageObjects.svlSearchHomePage.expectToNotBeOnHomepage();
// Click Home in Side nav
await pageObjects.svlCommonNavigation.sidenav.clickLink({
deepLinkId: 'searchHomepage',
});
pageObjects.svlSearchHomePage.expectToBeOnHomepage();
});
it('has embedded dev console', async () => {
testHasEmbeddedConsole(pageObjects);
});
});
}

View file

@ -6056,6 +6056,10 @@
version "0.0.0"
uid ""
"@kbn/search-homepage@link:x-pack/plugins/search_homepage":
version "0.0.0"
uid ""
"@kbn/search-index-documents@link:packages/kbn-search-index-documents":
version "0.0.0"
uid ""