From 74c4d3a85ef1ef1edf22d15e6925ae1c88e2da6e Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 19 Jun 2024 05:47:18 -0500 Subject: [PATCH] [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> --- .github/CODEOWNERS | 1 + config/serverless.es.yml | 3 + docs/developer/plugin-list.asciidoc | 4 + package.json | 1 + packages/deeplinks/search/constants.ts | 1 + packages/deeplinks/search/deep_links.ts | 3 + packages/kbn-optimizer/limits.yml | 1 + .../test_suites/core_plugins/rendering.ts | 3 +- tsconfig.base.json | 2 + x-pack/.i18nrc.json | 1 + x-pack/plugins/enterprise_search/kibana.jsonc | 1 + .../__mocks__/kea_logic/kibana_logic.mock.ts | 2 + .../public/applications/index.tsx | 2 + .../components/layout/page_template.test.tsx | 28 +++++ .../components/layout/page_template.tsx | 37 ++++++ .../components/search_homepage.tsx | 35 ++++++ .../applications/search_homepage/index.tsx | 42 +++++++ .../search_homepage/jest.config.js | 26 +++++ .../shared/kibana/kibana_logic.ts | 7 ++ .../shared/kibana_chrome/breadcrumbs_home.ts | 18 +++ .../kibana_chrome/generate_breadcrumbs.ts | 7 +- .../public/applications/shared/layout/nav.tsx | 8 +- .../test_helpers/test_utils.test_helper.tsx | 6 +- .../public/navigation_tree.ts | 6 +- .../enterprise_search/public/plugin.ts | 107 +++++++++++++----- .../plugins/enterprise_search/tsconfig.json | 3 +- x-pack/plugins/search_homepage/README.mdx | 3 + .../plugins/search_homepage/common/index.ts | 14 +++ x-pack/plugins/search_homepage/jest.config.js | 15 +++ x-pack/plugins/search_homepage/kibana.jsonc | 26 +++++ .../search_homepage/public/application.tsx | 37 ++++++ .../public/components/search_homepage.tsx | 32 ++++++ .../components/search_homepage_body.tsx | 24 ++++ .../components/search_homepage_header.tsx | 26 +++++ .../public/components/stack_app.tsx | 19 ++++ .../search_homepage/public/embeddable.tsx | 12 ++ .../search_homepage/public/feature_flags.ts | 13 +++ .../public/hooks/use_kibana.ts | 11 ++ .../plugins/search_homepage/public/index.ts | 20 ++++ .../plugins/search_homepage/public/plugin.ts | 80 +++++++++++++ .../plugins/search_homepage/public/router.tsx | 19 ++++ .../plugins/search_homepage/public/types.ts | 74 ++++++++++++ .../plugins/search_homepage/server/config.ts | 27 +++++ .../plugins/search_homepage/server/index.ts | 17 +++ .../plugins/search_homepage/server/plugin.ts | 28 +++++ .../plugins/search_homepage/server/types.ts | 11 ++ x-pack/plugins/search_homepage/tsconfig.json | 30 +++++ x-pack/plugins/serverless_search/kibana.jsonc | 1 + .../public/navigation_tree.ts | 6 +- .../serverless_search/public/plugin.ts | 31 ++++- .../plugins/serverless_search/public/types.ts | 24 ++-- .../plugins/serverless_search/tsconfig.json | 1 + .../functional/page_objects/index.ts | 2 + .../page_objects/svl_search_homepage.ts | 26 +++++ .../functional/services/index.ts | 2 + .../functional/services/ui_settings.ts | 32 ++++++ .../functional/test_suites/search/index.ts | 1 + .../test_suites/search/search_homepage.ts | 58 ++++++++++ yarn.lock | 4 + 59 files changed, 1025 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_homepage/components/search_homepage.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_homepage/index.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_homepage/jest.config.js create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/breadcrumbs_home.ts create mode 100644 x-pack/plugins/search_homepage/README.mdx create mode 100644 x-pack/plugins/search_homepage/common/index.ts create mode 100644 x-pack/plugins/search_homepage/jest.config.js create mode 100644 x-pack/plugins/search_homepage/kibana.jsonc create mode 100644 x-pack/plugins/search_homepage/public/application.tsx create mode 100644 x-pack/plugins/search_homepage/public/components/search_homepage.tsx create mode 100644 x-pack/plugins/search_homepage/public/components/search_homepage_body.tsx create mode 100644 x-pack/plugins/search_homepage/public/components/search_homepage_header.tsx create mode 100644 x-pack/plugins/search_homepage/public/components/stack_app.tsx create mode 100644 x-pack/plugins/search_homepage/public/embeddable.tsx create mode 100644 x-pack/plugins/search_homepage/public/feature_flags.ts create mode 100644 x-pack/plugins/search_homepage/public/hooks/use_kibana.ts create mode 100644 x-pack/plugins/search_homepage/public/index.ts create mode 100644 x-pack/plugins/search_homepage/public/plugin.ts create mode 100644 x-pack/plugins/search_homepage/public/router.tsx create mode 100644 x-pack/plugins/search_homepage/public/types.ts create mode 100644 x-pack/plugins/search_homepage/server/config.ts create mode 100644 x-pack/plugins/search_homepage/server/index.ts create mode 100644 x-pack/plugins/search_homepage/server/plugin.ts create mode 100644 x-pack/plugins/search_homepage/server/types.ts create mode 100644 x-pack/plugins/search_homepage/tsconfig.json create mode 100644 x-pack/test_serverless/functional/page_objects/svl_search_homepage.ts create mode 100644 x-pack/test_serverless/functional/services/ui_settings.ts create mode 100644 x-pack/test_serverless/functional/test_suites/search/search_homepage.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 690698e02a142..e06b53da0ab57 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/config/serverless.es.yml b/config/serverless.es.yml index 531fa5520c0ae..a170cf569b54d 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -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 diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index f78070b9bfa49..fc15206bd284d 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -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 diff --git a/package.json b/package.json index 48abd4ddd30b7..efd020e91df66 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts index 36d31d22dfe21..3fdcc78bb68a1 100644 --- a/packages/deeplinks/search/constants.ts +++ b/packages/deeplinks/search/constants.ts @@ -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'; diff --git a/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts index 8eeceff8f8ca2..f004d1b2c9dd6 100644 --- a/packages/deeplinks/search/deep_links.ts +++ b/packages/deeplinks/search/deep_links.ts @@ -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}`; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 336cfd2c6b93d..c3ffc507387b8 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -132,6 +132,7 @@ pageLoadAssetSize: screenshotMode: 17856 screenshotting: 22870 searchConnectors: 30000 + searchHomepage: 19831 searchInferenceEndpoints: 20470 searchNotebooks: 18942 searchPlayground: 19325 diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 31e0a44b9e823..19eeef57ba62b 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -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)', diff --git a/tsconfig.base.json b/tsconfig.base.json index ea18fe456af85..0f8d9a11563e0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 2943af3cf46d5..1f224ca164e52 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -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", diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index 3855fd3d9e1f9..0dc2562ff2fe3 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -30,6 +30,7 @@ "guidedOnboarding", "console", "searchConnectors", + "searchHomepage", "searchPlayground", "searchInferenceEndpoints", "embeddable", diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index cca5523ded681..5f4774be15b96 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -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(), diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index ea45e121470e2..98d6677c35fc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -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, diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.test.tsx new file mode 100644 index 0000000000000..c44cc39c5eb1d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.test.tsx @@ -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( + +
Test
+
+ ); + + expect(container.querySelector('.kbnSolutionNav__title')).toHaveTextContent('Search'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.tsx new file mode 100644 index 0000000000000..76f2e6e526239 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.tsx @@ -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 = ({ + children, + pageChrome, + pageViewTelemetry, + ...pageTemplateProps +}) => { + return ( + } + > + {pageViewTelemetry && ( + + )} + {children} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/search_homepage.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/search_homepage.tsx new file mode 100644 index 0000000000000..a605010fcb00d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/search_homepage.tsx @@ -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 ( + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/index.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/index.tsx new file mode 100644 index 0000000000000..43963f21d3b5d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/index.tsx @@ -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 = (props) => { + const { enterpriseSearchVersion, kibanaVersion } = props; + const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion); + + const showView = () => { + if (incompatibleVersions) { + return ( + + ); + } + + return ; + }; + + return ( + + + {showView()} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/search_homepage/jest.config.js new file mode 100644 index 0000000000000..c18a3561afb65 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/jest.config.js @@ -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: ['/x-pack/plugins/enterprise_search/public/applications/search_homepage'], + collectCoverage: true, + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}', + '!/x-pack/plugins/enterprise_search/public/*.ts', + '!/x-pack/plugins/enterprise_search/server/*.ts', + '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}', + ], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/search_homepage', + modulePathIgnorePatterns: [ + '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', + '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', + ], +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index da18e9e8bb44f..4920b25cffd75 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -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; + 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 | 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>({ 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>({ 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, {}], diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/breadcrumbs_home.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/breadcrumbs_home.ts new file mode 100644 index 0000000000000..34e9e12c88b76 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/breadcrumbs_home.ts @@ -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; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index ac3e6d7a6437d..5798a48680d1f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -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, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index bd53ed235d4cb..77454581c61e7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -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, }), }, { diff --git a/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx b/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx index 5601c006a4433..0050165b8be50 100644 --- a/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx @@ -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; } export const TestHelper: TestHelper = { @@ -147,7 +149,7 @@ export const TestHelper: TestHelper = { TestHelper.actionsToRun.forEach((action) => { action(); }); - testingLibraryRender( + return testingLibraryRender( {children} diff --git a/x-pack/plugins/enterprise_search/public/navigation_tree.ts b/x-pack/plugins/enterprise_search/public/navigation_tree.ts index dc079aec90688..051cfaa6779af 100644 --- a/x-pack/plugins/enterprise_search/public/navigation_tree.ts +++ b/x-pack/plugins/enterprise_search/public/navigation_tree.ts @@ -67,12 +67,14 @@ const euiItemTypeToNodeDefinition = ({ export const getNavigationTreeDefinition = ({ dynamicItems$, + isSearchHomepageEnabled, }: { dynamicItems$: Observable; + 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 }) => { diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 9c03fb623fa3d..280de2f04356b 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -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 { - 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, + }) ); }); diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index 3a66b8350be4f..a6bb797de5eb1 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -79,6 +79,7 @@ "@kbn/cloud", "@kbn/try-in-console", "@kbn/core-chrome-browser", - "@kbn/navigation-plugin" + "@kbn/navigation-plugin", + "@kbn/search-homepage" ] } diff --git a/x-pack/plugins/search_homepage/README.mdx b/x-pack/plugins/search_homepage/README.mdx new file mode 100644 index 0000000000000..00ba4f491c607 --- /dev/null +++ b/x-pack/plugins/search_homepage/README.mdx @@ -0,0 +1,3 @@ +# Search Homepage + +The Search Homepage is a shared homepage for elasticsearch users. diff --git a/x-pack/plugins/search_homepage/common/index.ts b/x-pack/plugins/search_homepage/common/index.ts new file mode 100644 index 0000000000000..d93d5c49f8cc5 --- /dev/null +++ b/x-pack/plugins/search_homepage/common/index.ts @@ -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'; diff --git a/x-pack/plugins/search_homepage/jest.config.js b/x-pack/plugins/search_homepage/jest.config.js new file mode 100644 index 0000000000000..65cd8f1e34252 --- /dev/null +++ b/x-pack/plugins/search_homepage/jest.config.js @@ -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: ['/x-pack/plugins/search_homepage'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/search_homepage', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/x-pack/plugins/search_homepage/{public,server}/**/*.{ts,tsx}'], +}; diff --git a/x-pack/plugins/search_homepage/kibana.jsonc b/x-pack/plugins/search_homepage/kibana.jsonc new file mode 100644 index 0000000000000..0e345ab0d330a --- /dev/null +++ b/x-pack/plugins/search_homepage/kibana.jsonc @@ -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" + ] + } +} diff --git a/x-pack/plugins/search_homepage/public/application.tsx b/x-pack/plugins/search_homepage/public/application.tsx new file mode 100644 index 0000000000000..4af256de498ed --- /dev/null +++ b/x-pack/plugins/search_homepage/public/application.tsx @@ -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( + + + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/search_homepage/public/components/search_homepage.tsx b/x-pack/plugins/search_homepage/public/components/search_homepage.tsx new file mode 100644 index 0000000000000..7af02cbcf3275 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/search_homepage.tsx @@ -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 ? : null), + [consolePlugin] + ); + + return ( + + + + {embeddableConsole} + + ); +}; diff --git a/x-pack/plugins/search_homepage/public/components/search_homepage_body.tsx b/x-pack/plugins/search_homepage/public/components/search_homepage_body.tsx new file mode 100644 index 0000000000000..808393594e7d8 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/search_homepage_body.tsx @@ -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 = () => ( + +
+ +); diff --git a/x-pack/plugins/search_homepage/public/components/search_homepage_header.tsx b/x-pack/plugins/search_homepage/public/components/search_homepage_header.tsx new file mode 100644 index 0000000000000..941655d67cdab --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/search_homepage_header.tsx @@ -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 = () => ( + +

+ +

+ + } + data-test-subj="search-homepage-header" + rightSideItems={[]} + /> +); diff --git a/x-pack/plugins/search_homepage/public/components/stack_app.tsx b/x-pack/plugins/search_homepage/public/components/stack_app.tsx new file mode 100644 index 0000000000000..ca18ac7112c09 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/stack_app.tsx @@ -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 ( + <> + + + + ); +}; diff --git a/x-pack/plugins/search_homepage/public/embeddable.tsx b/x-pack/plugins/search_homepage/public/embeddable.tsx new file mode 100644 index 0000000000000..ee69062ea3fe5 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/embeddable.tsx @@ -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, +})); diff --git a/x-pack/plugins/search_homepage/public/feature_flags.ts b/x-pack/plugins/search_homepage/public/feature_flags.ts new file mode 100644 index 0000000000000..bea65a8e1548f --- /dev/null +++ b/x-pack/plugins/search_homepage/public/feature_flags.ts @@ -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(HOMEPAGE_FEATURE_FLAG_ID, false); +} diff --git a/x-pack/plugins/search_homepage/public/hooks/use_kibana.ts b/x-pack/plugins/search_homepage/public/hooks/use_kibana.ts new file mode 100644 index 0000000000000..b22c7b4ed9d7f --- /dev/null +++ b/x-pack/plugins/search_homepage/public/hooks/use_kibana.ts @@ -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(); diff --git a/x-pack/plugins/search_homepage/public/index.ts b/x-pack/plugins/search_homepage/public/index.ts new file mode 100644 index 0000000000000..b5133bb506406 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/index.ts @@ -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'; diff --git a/x-pack/plugins/search_homepage/public/plugin.ts b/x-pack/plugins/search_homepage/public/plugin.ts new file mode 100644 index 0000000000000..ebb3ef8ed822c --- /dev/null +++ b/x-pack/plugins/search_homepage/public/plugin.ts @@ -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 +{ + private readonly config: SearchHomepageConfigType; + constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.get(); + } + + public setup( + core: CoreSetup + ) { + 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, + }; + } +} diff --git a/x-pack/plugins/search_homepage/public/router.tsx b/x-pack/plugins/search_homepage/public/router.tsx new file mode 100644 index 0000000000000..e4db94ebde4ae --- /dev/null +++ b/x-pack/plugins/search_homepage/public/router.tsx @@ -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 = () => ( + + + + + +); diff --git a/x-pack/plugins/search_homepage/public/types.ts b/x-pack/plugins/search_homepage/public/types.ts new file mode 100644 index 0000000000000..de5283abfc61d --- /dev/null +++ b/x-pack/plugins/search_homepage/public/types.ts @@ -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>; +} + +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; +} diff --git a/x-pack/plugins/search_homepage/server/config.ts b/x-pack/plugins/search_homepage/server/config.ts new file mode 100644 index 0000000000000..3e068a719f046 --- /dev/null +++ b/x-pack/plugins/search_homepage/server/config.ts @@ -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; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + ui: true, + }, + schema: configSchema, +}; diff --git a/x-pack/plugins/search_homepage/server/index.ts b/x-pack/plugins/search_homepage/server/index.ts new file mode 100644 index 0000000000000..864af85c0a2fb --- /dev/null +++ b/x-pack/plugins/search_homepage/server/index.ts @@ -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'; diff --git a/x-pack/plugins/search_homepage/server/plugin.ts b/x-pack/plugins/search_homepage/server/plugin.ts new file mode 100644 index 0000000000000..f446ba4e41fd3 --- /dev/null +++ b/x-pack/plugins/search_homepage/server/plugin.ts @@ -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 +{ + 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 {}; + } +} diff --git a/x-pack/plugins/search_homepage/server/types.ts b/x-pack/plugins/search_homepage/server/types.ts new file mode 100644 index 0000000000000..c4e5d46959422 --- /dev/null +++ b/x-pack/plugins/search_homepage/server/types.ts @@ -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 {} diff --git a/x-pack/plugins/search_homepage/tsconfig.json b/x-pack/plugins/search_homepage/tsconfig.json new file mode 100644 index 0000000000000..9f084b32f7d3b --- /dev/null +++ b/x-pack/plugins/search_homepage/tsconfig.json @@ -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/**/*", + ] +} diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc index 3a98a87c032a4..04002ee897cf4 100644 --- a/x-pack/plugins/serverless_search/kibana.jsonc +++ b/x-pack/plugins/serverless_search/kibana.jsonc @@ -29,6 +29,7 @@ "optionalPlugins": [ "indexManagement", "searchConnectors", + "searchHomepage", "searchInferenceEndpoints", "usageCollection" ], diff --git a/x-pack/plugins/serverless_search/public/navigation_tree.ts b/x-pack/plugins/serverless_search/public/navigation_tree.ts index d21eee8de9c19..f5618e8b83e05 100644 --- a/x-pack/plugins/serverless_search/public/navigation_tree.ts +++ b/x-pack/plugins/serverless_search/public/navigation_tree.ts @@ -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 = { ], }, ], -}; +}); diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index d9019f911444a..e72e1a4575079 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -43,6 +43,9 @@ export class ServerlessSearchPlugin core: CoreSetup, 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(); + + serverless.setProjectHome(useSearchHomepage ? '/app/elasticsearch/home' : '/app/elasticsearch'); - const navigationTree$ = of(navigationTree); + const navigationTree$ = of(navigationTree(searchHomepage?.isHomepageFeatureEnabled() ?? false)); serverless.initNavigation('search', navigationTree$, { dataTestSubj: 'svlSearchSideNav' }); const extendCardNavDefinitions = serverless.getNavigationCards( diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts index e4eab5fbfd61d..d3011210c524f 100644 --- a/x-pack/plugins/serverless_search/public/types.ts +++ b/x-pack/plugins/serverless_search/public/types.ts @@ -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; } diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index 8cf2dec121d46..418dcb5fc6f5c 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -50,5 +50,6 @@ "@kbn/react-kibana-context-render", "@kbn/search-playground", "@kbn/search-inference-endpoints", + "@kbn/search-homepage", ] } diff --git a/x-pack/test_serverless/functional/page_objects/index.ts b/x-pack/test_serverless/functional/page_objects/index.ts index f1604d48508e2..94e02f9c5e455 100644 --- a/x-pack/test_serverless/functional/page_objects/index.ts +++ b/x-pack/test_serverless/functional/page_objects/index.ts @@ -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, }; diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_homepage.ts b/x-pack/test_serverless/functional/page_objects/svl_search_homepage.ts new file mode 100644 index 0000000000000..eeb1b6de731f9 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/svl_search_homepage.ts @@ -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 }); + }, + }; +} diff --git a/x-pack/test_serverless/functional/services/index.ts b/x-pack/test_serverless/functional/services/index.ts index a1112232377cd..c63a16b4402f1 100644 --- a/x-pack/test_serverless/functional/services/index.ts +++ b/x-pack/test_serverless/functional/services/index.ts @@ -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, }; diff --git a/x-pack/test_serverless/functional/services/ui_settings.ts b/x-pack/test_serverless/functional/services/ui_settings.ts new file mode 100644 index 0000000000000..337930790489d --- /dev/null +++ b/x-pack/test_serverless/functional/services/ui_settings.ts @@ -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); + }, + }; +} diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts index 455acc0404429..8c3cfd83e04e9 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.ts @@ -23,5 +23,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./playground_overview')); loadTestFile(require.resolve('./ml')); + loadTestFile(require.resolve('./search_homepage')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts b/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts new file mode 100644 index 0000000000000..3138e0e7f0242 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts @@ -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); + }); + }); +} diff --git a/yarn.lock b/yarn.lock index 64ba753c57695..d1dec3cae024a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 ""