diff --git a/config/serverless.es.yml b/config/serverless.es.yml
index 2d5ba84ece795..b8e12054ddf28 100644
--- a/config/serverless.es.yml
+++ b/config/serverless.es.yml
@@ -72,7 +72,6 @@ xpack.cloud.serverless.project_type: search
## Enable the Serverless Search plugin
xpack.serverless.search.enabled: true
-xpack.searchIndices.enabled: true
## Set the home route
uiSettings.overrides.defaultRoute: /app/elasticsearch
diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml
index 937954a1c5e84..a08f9a72e9e7b 100644
--- a/config/serverless.oblt.yml
+++ b/config/serverless.oblt.yml
@@ -9,6 +9,8 @@ xpack.securitySolution.enabled: false
xpack.search.notebooks.enabled: false
xpack.searchPlayground.enabled: false
xpack.searchInferenceEndpoints.enabled: false
+xpack.searchIndices.enabled: false
+
## Fine-tune the observability solution feature privileges. Also, refer to `serverless.yml` for the project-agnostic overrides.
xpack.features.overrides:
diff --git a/config/serverless.security.yml b/config/serverless.security.yml
index 0e3a0c92f8546..f748c3ea5b7b2 100644
--- a/config/serverless.security.yml
+++ b/config/serverless.security.yml
@@ -11,6 +11,7 @@ xpack.search.notebooks.enabled: false
xpack.searchPlayground.enabled: false
xpack.searchInferenceEndpoints.enabled: false
xpack.inventory.enabled: false
+xpack.searchIndices.enabled: false
## Fine-tune the security solution feature privileges. Also, refer to `serverless.yml` for the project-agnostic overrides.
xpack.features.overrides:
diff --git a/src/platform/packages/shared/deeplinks/search/constants.ts b/src/platform/packages/shared/deeplinks/search/constants.ts
index d1992d22da120..5b5ba7d833d57 100644
--- a/src/platform/packages/shared/deeplinks/search/constants.ts
+++ b/src/platform/packages/shared/deeplinks/search/constants.ts
@@ -13,6 +13,8 @@ export const ENTERPRISE_SEARCH_APPLICATIONS_APP_ID = 'enterpriseSearchApplicatio
export const ENTERPRISE_SEARCH_ANALYTICS_APP_ID = 'enterpriseSearchAnalytics';
export const ENTERPRISE_SEARCH_APPSEARCH_APP_ID = 'appSearch';
export const ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID = 'workplaceSearch';
+export const SEARCH_INDICES_START_APP_ID = 'elasticsearchStart';
+export const SEARCH_INDICES_INDICES_APP_ID = 'elasticsearchIndices';
export const SERVERLESS_ES_APP_ID = 'serverlessElasticsearch';
export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors';
export const SERVERLESS_ES_WEB_CRAWLERS_ID = 'serverlessWebCrawlers';
diff --git a/src/platform/packages/shared/deeplinks/search/index.ts b/src/platform/packages/shared/deeplinks/search/index.ts
index 2b3d392971a5f..608a2b5165e93 100644
--- a/src/platform/packages/shared/deeplinks/search/index.ts
+++ b/src/platform/packages/shared/deeplinks/search/index.ts
@@ -21,6 +21,8 @@ export {
SEARCH_SEMANTIC_SEARCH,
SEARCH_AI_SEARCH,
ES_SEARCH_PLAYGROUND_ID,
+ SEARCH_INDICES_START_APP_ID,
+ SEARCH_INDICES_INDICES_APP_ID,
} from './constants';
export type {
diff --git a/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts
index 2542b4f04f15a..443526aac9f33 100644
--- a/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts
+++ b/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts
@@ -138,6 +138,8 @@ export const applicationUsageSchema = {
enterpriseSearchContent: commonSchema,
searchInferenceEndpoints: commonSchema,
searchPlayground: commonSchema,
+ elasticsearchIndices: commonSchema,
+ elasticsearchStart: commonSchema,
enterpriseSearchAnalytics: commonSchema,
enterpriseSearchApplications: commonSchema,
enterpriseSearchAISearch: commonSchema,
diff --git a/src/platform/plugins/shared/telemetry/schema/oss_platform.json b/src/platform/plugins/shared/telemetry/schema/oss_platform.json
index 2bd3174ba35d3..8f249135ea6cd 100644
--- a/src/platform/plugins/shared/telemetry/schema/oss_platform.json
+++ b/src/platform/plugins/shared/telemetry/schema/oss_platform.json
@@ -2360,6 +2360,268 @@
}
}
},
+ "elasticsearchIndices": {
+ "properties": {
+ "appId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application being tracked"
+ }
+ },
+ "viewId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "Always `main`"
+ }
+ },
+ "clicks_total": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application since we started counting them"
+ }
+ },
+ "clicks_7_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 7 days"
+ }
+ },
+ "clicks_30_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 30 days"
+ }
+ },
+ "clicks_90_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 90 days"
+ }
+ },
+ "minutes_on_screen_total": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen since we started counting them."
+ }
+ },
+ "minutes_on_screen_7_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 7 days"
+ }
+ },
+ "minutes_on_screen_30_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 30 days"
+ }
+ },
+ "minutes_on_screen_90_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 90 days"
+ }
+ },
+ "views": {
+ "type": "array",
+ "items": {
+ "properties": {
+ "appId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application being tracked"
+ }
+ },
+ "viewId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application view being tracked"
+ }
+ },
+ "clicks_total": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application sub view since we started counting them"
+ }
+ },
+ "clicks_7_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 7 days"
+ }
+ },
+ "clicks_30_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 30 days"
+ }
+ },
+ "clicks_90_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 90 days"
+ }
+ },
+ "minutes_on_screen_total": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application sub view is active and on-screen since we started counting them."
+ }
+ },
+ "minutes_on_screen_7_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
+ }
+ },
+ "minutes_on_screen_30_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
+ }
+ },
+ "minutes_on_screen_90_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "elasticsearchStart": {
+ "properties": {
+ "appId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application being tracked"
+ }
+ },
+ "viewId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "Always `main`"
+ }
+ },
+ "clicks_total": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application since we started counting them"
+ }
+ },
+ "clicks_7_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 7 days"
+ }
+ },
+ "clicks_30_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 30 days"
+ }
+ },
+ "clicks_90_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 90 days"
+ }
+ },
+ "minutes_on_screen_total": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen since we started counting them."
+ }
+ },
+ "minutes_on_screen_7_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 7 days"
+ }
+ },
+ "minutes_on_screen_30_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 30 days"
+ }
+ },
+ "minutes_on_screen_90_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 90 days"
+ }
+ },
+ "views": {
+ "type": "array",
+ "items": {
+ "properties": {
+ "appId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application being tracked"
+ }
+ },
+ "viewId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application view being tracked"
+ }
+ },
+ "clicks_total": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application sub view since we started counting them"
+ }
+ },
+ "clicks_7_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 7 days"
+ }
+ },
+ "clicks_30_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 30 days"
+ }
+ },
+ "clicks_90_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 90 days"
+ }
+ },
+ "minutes_on_screen_total": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application sub view is active and on-screen since we started counting them."
+ }
+ },
+ "minutes_on_screen_7_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
+ }
+ },
+ "minutes_on_screen_30_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
+ }
+ },
+ "minutes_on_screen_90_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"enterpriseSearchAnalytics": {
"properties": {
"appId": {
diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
index 0274f9260b917..1b23832da3d82 100644
--- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json
+++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
@@ -18215,16 +18215,6 @@
"xpack.enterpriseSearch.nav.relevanceTitle": "Pertinence",
"xpack.enterpriseSearch.nav.searchApplication.contentTitle": "Contenu",
"xpack.enterpriseSearch.nav.searchApplication.docsExplorerTitle": "Explorateur de documents",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.connectorsConfigurationLabel": "Configuration",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerConfigurationLabel": "Configuration",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerSchedulingLabel": "Planification",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.documentsTitle": "Documents",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.domainManagementLabel": "Gérer les domaines",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.indexMappingsTitle": "Mappings d'index",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.overviewTitle": "Aperçu",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.pipelinesLabel": "Pipelines",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "Planification",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "Règles de synchronisation",
"xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "Applications de recherche",
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "Moteurs",
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "Connecteurs",
diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
index 19513093d5517..1aebdbae1bc3e 100644
--- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json
+++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
@@ -18074,16 +18074,6 @@
"xpack.enterpriseSearch.nav.homeTitle": "ホーム",
"xpack.enterpriseSearch.nav.searchApplication.contentTitle": "コンテンツ",
"xpack.enterpriseSearch.nav.searchApplication.docsExplorerTitle": "ドキュメントエクスプローラー",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.connectorsConfigurationLabel": "構成",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerConfigurationLabel": "構成",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerSchedulingLabel": "スケジュール",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.documentsTitle": "ドキュメント",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.domainManagementLabel": "ドメインを管理",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.indexMappingsTitle": "インデックスマッピング",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.overviewTitle": "概要",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.pipelinesLabel": "パイプライン",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "スケジュール",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "同期ルール",
"xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "検索アプリケーション",
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "エンジン",
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "コネクター",
diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
index 41bb4953fc45b..fd31512e4f805 100644
--- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json
+++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
@@ -17774,16 +17774,6 @@
"xpack.enterpriseSearch.nav.relevanceTitle": "相关性",
"xpack.enterpriseSearch.nav.searchApplication.contentTitle": "内容",
"xpack.enterpriseSearch.nav.searchApplication.docsExplorerTitle": "文档浏览器",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.connectorsConfigurationLabel": "配置",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerConfigurationLabel": "配置",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerSchedulingLabel": "正在计划",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.documentsTitle": "文档",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.domainManagementLabel": "管理域",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.indexMappingsTitle": "索引映射",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.overviewTitle": "概览",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.pipelinesLabel": "管道",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "正在计划",
- "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "同步规则",
"xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "搜索应用程序",
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "引擎",
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "连接器",
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/setup_environment.tsx
index 535e0219bf823..9694b6c19dc50 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/setup_environment.tsx
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/setup_environment.tsx
@@ -21,6 +21,7 @@ import {
applicationServiceMock,
fatalErrorsServiceMock,
httpServiceMock,
+ chromeServiceMock,
} from '@kbn/core/public/mocks';
import { GlobalFlyout } from '@kbn/es-ui-shared-plugin/public';
@@ -70,6 +71,7 @@ const appDependencies = {
executionContext: executionContextServiceMock.createStartContract(),
http: httpServiceMock.createSetupContract(),
application: applicationServiceMock.createStartContract(),
+ chrome: chromeServiceMock.createStartContract(),
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
},
plugins: {
@@ -105,6 +107,7 @@ const { Provider: KibanaReactContextProvider } = createKibanaReactContext({
uiSettings: uiSettingsServiceMock.createSetupContract(),
settings: settingsServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
+ chrome: chromeServiceMock.createStartContract(),
kibanaVersion: {
get: () => kibanaVersion,
},
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_project_level_retention.test.ts b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_project_level_retention.test.ts
index 19d6f169429b7..4f560b07b392e 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_project_level_retention.test.ts
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_project_level_retention.test.ts
@@ -37,6 +37,7 @@ const urlServiceMock = {
}),
},
};
+jest.mock('react-use/lib/useObservable', () => () => jest.fn());
describe('Data Streams - Project level max retention', () => {
const { httpSetup, httpRequestsMockHelpers } = setupEnvironment();
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
index d3368371de336..0ce078e6b992d 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -26,6 +26,8 @@ import {
createNonDataStreamIndex,
} from './data_streams_tab.helpers';
+jest.mock('react-use/lib/useObservable', () => () => jest.fn());
+
const nonBreakingSpace = ' ';
const urlServiceMock = {
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/home.test.ts
index ef3bfabf7b9c9..e296069b5276b 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/home.test.ts
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/home.test.ts
@@ -10,6 +10,8 @@ import { act } from 'react-dom/test-utils';
import { setupEnvironment } from '../helpers';
import { HomeTestBed, setup } from './home.helpers';
+jest.mock('react-use/lib/useObservable', () => () => jest.fn());
+
describe('', () => {
const { httpSetup, httpRequestsMockHelpers } = setupEnvironment();
let testBed: HomeTestBed;
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/indices_tab.test.tsx b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/indices_tab.test.tsx
index a8256a2e00b27..733ed9c62793d 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/indices_tab.test.tsx
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/indices_tab.test.tsx
@@ -8,22 +8,9 @@
/*
* Mocking EuiSearchBar because its onChange is not firing during tests
*/
+import React from 'react';
import { EuiSearchBoxProps } from '@elastic/eui/src/components/search_bar/search_box';
-
import { applicationServiceMock } from '@kbn/core/public/mocks';
-jest.mock('@elastic/eui/lib/components/search_bar/search_box', () => {
- return {
- EuiSearchBox: (props: EuiSearchBoxProps) => (
- ) => {
- props.onSearch(event.target.value);
- }}
- />
- ),
- };
-});
-import React from 'react';
import { act } from 'react-dom/test-utils';
import { API_BASE_PATH, Index, INTERNAL_API_BASE_PATH } from '../../../common';
@@ -41,6 +28,20 @@ import {
IndexManagementBreadcrumb,
} from '../../../public/application/services/breadcrumbs';
+jest.mock('@elastic/eui/lib/components/search_bar/search_box', () => {
+ return {
+ EuiSearchBox: (props: EuiSearchBoxProps) => (
+ ) => {
+ props.onSearch(event.target.value);
+ }}
+ />
+ ),
+ };
+});
+jest.mock('react-use/lib/useObservable', () => () => jest.fn());
+
describe('', () => {
let testBed: IndicesTestBed;
let httpSetup: ReturnType['httpSetup'];
diff --git a/x-pack/platform/plugins/shared/index_management/__jest__/components/index_table.test.js b/x-pack/platform/plugins/shared/index_management/__jest__/components/index_table.test.js
index 0a59fef7be6e6..43ba97a970571 100644
--- a/x-pack/platform/plugins/shared/index_management/__jest__/components/index_table.test.js
+++ b/x-pack/platform/plugins/shared/index_management/__jest__/components/index_table.test.js
@@ -26,7 +26,11 @@ import { setExtensionsService } from '../../public/application/store/selectors/e
import { ExtensionsService } from '../../public/services';
import { kibanaVersion } from '../client_integration/helpers';
-import { notificationServiceMock, executionContextServiceMock } from '@kbn/core/public/mocks';
+import {
+ notificationServiceMock,
+ executionContextServiceMock,
+ chromeServiceMock,
+} from '@kbn/core/public/mocks';
let store = null;
const indices = [];
@@ -44,6 +48,8 @@ const getBaseFakeIndex = (isOpen) => {
};
};
+jest.mock('react-use/lib/useObservable', () => () => jest.fn());
+
for (let i = 0; i < 105; i++) {
indices.push({
...getBaseFakeIndex(true),
@@ -159,6 +165,7 @@ describe('index table', () => {
core: {
getUrlForApp: () => {},
executionContext: executionContextServiceMock.createStartContract(),
+ chrome: chromeServiceMock.createStartContract(),
},
plugins: {},
url: urlServiceMock,
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/app_context.tsx b/x-pack/platform/plugins/shared/index_management/public/application/app_context.tsx
index 1019d580dec5d..5fc1363050026 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/app_context.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/app_context.tsx
@@ -19,6 +19,7 @@ import {
HttpSetup,
IUiSettingsClient,
OverlayStart,
+ ChromeStart,
} from '@kbn/core/public';
import type { MlPluginStart } from '@kbn/ml-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
@@ -43,6 +44,7 @@ export interface AppDependencies {
http: HttpSetup;
i18n: I18nStart;
theme: ThemeServiceStart;
+ chrome: ChromeStart;
};
plugins: {
usageCollection: UsageCollectionSetup;
diff --git a/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx b/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx
index e7201ce5d44b3..4200d9b0d2462 100644
--- a/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx
+++ b/x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx
@@ -10,7 +10,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { EuiButton } from '@elastic/eui';
+import useObservable from 'react-use/lib/useObservable';
import { CreateIndexModal } from './create_index_modal';
+import { useAppContext } from '../../../../app_context';
export interface CreateIndexButtonProps {
loadIndices: () => void;
@@ -20,9 +22,17 @@ export interface CreateIndexButtonProps {
export const CreateIndexButton = ({ loadIndices, share }: CreateIndexButtonProps) => {
const [createIndexModalOpen, setCreateIndexModalOpen] = useState(false);
const createIndexUrl = share?.url.locators.get('SEARCH_CREATE_INDEX')?.useUrl({});
- const actionProp = createIndexUrl
- ? { href: createIndexUrl }
- : { onClick: () => setCreateIndexModalOpen(true) };
+
+ const {
+ core: { chrome },
+ } = useAppContext();
+
+ const activeSolutionId = useObservable(chrome.getActiveSolutionNavId$());
+
+ const actionProp =
+ createIndexUrl && activeSolutionId === 'es'
+ ? { href: createIndexUrl }
+ : { onClick: () => setCreateIndexModalOpen(true) };
return (
<>
diff --git a/x-pack/solutions/search/plugins/enterprise_search/common/constants.ts b/x-pack/solutions/search/plugins/enterprise_search/common/constants.ts
index 8e69b80564802..7733d68a6fa94 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/common/constants.ts
+++ b/x-pack/solutions/search/plugins/enterprise_search/common/constants.ts
@@ -18,6 +18,8 @@ import {
SEARCH_VECTOR_SEARCH,
SEARCH_SEMANTIC_SEARCH,
SEARCH_AI_SEARCH,
+ SEARCH_INDICES_INDICES_APP_ID,
+ SEARCH_INDICES_START_APP_ID,
} from '@kbn/deeplinks-search';
import { i18n } from '@kbn/i18n';
@@ -32,6 +34,8 @@ export const ENTERPRISE_SEARCH_PRODUCT_NAME = i18n.translate('xpack.enterpriseSe
defaultMessage: 'Enterprise Search',
});
+export { SEARCH_INDICES_START_APP_ID, SEARCH_INDICES_INDICES_APP_ID };
+
export const ENTERPRISE_SEARCH_OVERVIEW_PLUGIN = {
ID: ENTERPRISE_SEARCH_APP_ID,
NAME: SEARCH_PRODUCT_NAME,
diff --git a/x-pack/solutions/search/plugins/enterprise_search/kibana.jsonc b/x-pack/solutions/search/plugins/enterprise_search/kibana.jsonc
index a484cf625c0ae..d2061dcc15de6 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/kibana.jsonc
+++ b/x-pack/solutions/search/plugins/enterprise_search/kibana.jsonc
@@ -42,7 +42,8 @@
"charts",
"cloud",
"lens",
- "share"
+ "share",
+ "search_indices"
],
"requiredBundles": ["kibanaReact"]
}
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_api/method_api.test.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_api/method_api.test.tsx
index 031d3e8da3ecd..0c2554d62a1ec 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_api/method_api.test.tsx
+++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_api/method_api.test.tsx
@@ -17,7 +17,7 @@ import { NewSearchIndexTemplate } from '../new_search_index_template';
import { MethodApi } from './method_api';
-describe('MethodApi', () => {
+describe.skip('MethodApi', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockValues({ status: Status.IDLE });
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/indices/indices_nav.test.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/indices/indices_nav.test.tsx
deleted file mode 100644
index d7ac65e07f284..0000000000000
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/indices/indices_nav.test.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { setMockValues } from '../../../../__mocks__/kea_logic';
-import { mockUseRouteMatch, mockUseParams } from '../../../../__mocks__/react_router';
-
-import { isConnectorIndex, isCrawlerIndex } from '../../../utils/indices';
-
-import { mockIndexNameValues } from '../_mocks_/index_name_logic.mock';
-
-jest.mock('../../../../shared/layout', () => ({
- generateNavLink: jest.fn(({ to }) => ({ href: to })),
-}));
-jest.mock('../../../utils/indices');
-
-import { useIndicesNav } from './indices_nav';
-
-describe('useIndicesNav', () => {
- beforeEach(() => {
- setMockValues(mockIndexNameValues);
- mockUseRouteMatch.mockReturnValue(true);
- mockUseParams.mockReturnValue({ indexName: 'index-name' });
- });
-
- describe('returns empty', () => {
- it('does not return index nav items if not on an index route', () => {
- mockUseRouteMatch.mockReturnValueOnce(false);
- expect(useIndicesNav()).toBeUndefined();
- });
-
- it('does not return index nav items if index name is missing', () => {
- mockUseParams.mockReturnValue({ indexName: '' });
- expect(useIndicesNav()).toBeUndefined();
- });
- });
-
- describe('returns an array of EUI side nav items', () => {
- const BASE_NAV = [
- {
- id: 'indexName',
- name: 'INDEX-NAME',
- 'data-test-subj': 'IndexLabel',
- href: '/search_indices/index-name',
- items: [
- {
- id: 'overview',
- name: 'Overview',
- href: '/search_indices/index-name/overview',
- 'data-test-subj': 'IndexOverviewLink',
- },
- {
- id: 'documents',
- name: 'Documents',
- href: '/search_indices/index-name/documents',
- 'data-test-subj': 'IndexDocumentsLink',
- },
- {
- id: 'index_mappings',
- name: 'Index mappings',
- href: '/search_indices/index-name/index_mappings',
- 'data-test-subj': 'IndexIndexMappingsLink',
- },
- ],
- },
- ];
-
- it('always returns an index label, overview, documents, index mapping link', () => {
- expect(useIndicesNav()).toEqual(BASE_NAV);
- });
-
- it('returns pipelines with default navs when hasDefaultIngestPipeline is true', () => {
- setMockValues({
- ...mockIndexNameValues,
- productFeatures: { hasDefaultIngestPipeline: true },
- });
-
- const pipelineItem = {
- id: 'pipelines',
- name: 'Pipelines',
- href: '/search_indices/index-name/pipelines',
- 'data-test-subj': 'IndexPipelineLink',
- };
- const baseNavWithPipelines = BASE_NAV.map((navItem) => ({
- ...navItem,
- items: [...navItem.items, pipelineItem],
- }));
-
- expect(useIndicesNav()).toEqual(baseNavWithPipelines);
- });
-
- describe('connectors nav', () => {
- it('returns connectors nav with default navs when isConnectorIndex is true', () => {
- jest.mocked(isConnectorIndex).mockReturnValueOnce(true);
-
- const configuration = {
- 'data-test-subj': 'IndexConnectorsConfigurationLink',
- id: 'connectors_configuration',
- name: 'Configuration',
- href: '/search_indices/index-name/configuration',
- };
- const scheduling = {
- 'data-test-subj': 'IndexSchedulingLink',
- id: 'scheduling',
- name: 'Scheduling',
- href: '/search_indices/index-name/scheduling',
- };
- const baseNavWithConnectors = BASE_NAV.map((navItem) => ({
- ...navItem,
- items: [...navItem.items, configuration, scheduling],
- }));
-
- expect(useIndicesNav()).toEqual(baseNavWithConnectors);
- });
-
- it('returns connectors nav with sync rules with default navs when isConnectorIndex is true and hasFilteringFeature is true', () => {
- jest.mocked(isConnectorIndex).mockReturnValueOnce(true);
- setMockValues({ ...mockIndexNameValues, hasFilteringFeature: true });
-
- const configuration = {
- 'data-test-subj': 'IndexConnectorsConfigurationLink',
- id: 'connectors_configuration',
- name: 'Configuration',
- href: '/search_indices/index-name/configuration',
- };
- const syncRules = {
- 'data-test-subj': 'IndexSyncRulesLink',
- id: 'syncRules',
- name: 'Sync rules',
- href: '/search_indices/index-name/sync_rules',
- };
- const scheduling = {
- 'data-test-subj': 'IndexSchedulingLink',
- id: 'scheduling',
- name: 'Scheduling',
- href: '/search_indices/index-name/scheduling',
- };
- const baseNavWithConnectors = BASE_NAV.map((navItem) => ({
- ...navItem,
- items: [...navItem.items, configuration, syncRules, scheduling],
- }));
-
- expect(useIndicesNav()).toEqual(baseNavWithConnectors);
- });
- });
-
- describe('crawlers nav', () => {
- it('returns crawlers nav with default navs when isCrawlerIndex is true', () => {
- jest.mocked(isCrawlerIndex).mockReturnValueOnce(true);
-
- const domainManagement = {
- 'data-test-subj': 'IndexDomainManagementLink',
- id: 'domain_management',
- name: 'Manage Domains',
- href: '/search_indices/index-name/domain_management',
- };
- const configuration = {
- 'data-test-subj': 'IndexCrawlerConfigurationLink',
- id: 'crawler_configuration',
- name: 'Configuration',
- href: '/search_indices/index-name/crawler_configuration',
- };
- const scheduling = {
- 'data-test-subj': 'IndexCrawlerSchedulingLink',
- id: 'crawler_scheduling',
- name: 'Scheduling',
- href: '/search_indices/index-name/scheduling',
- };
- const baseNavWithCrawlers = BASE_NAV.map((navItem) => ({
- ...navItem,
- items: [...navItem.items, domainManagement, configuration, scheduling],
- }));
-
- expect(useIndicesNav()).toEqual(baseNavWithCrawlers);
- });
- });
- });
-});
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/indices/indices_nav.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/indices/indices_nav.tsx
deleted file mode 100644
index c3e754bdd9b62..0000000000000
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/indices/indices_nav.tsx
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { useParams, useRouteMatch } from 'react-router-dom';
-
-import { useValues } from 'kea';
-
-import { EuiSideNavItemType } from '@elastic/eui';
-
-import { i18n } from '@kbn/i18n';
-
-import { generateEncodedPath } from '../../../../shared/encode_path_params';
-import { KibanaLogic } from '../../../../shared/kibana';
-import { generateNavLink } from '../../../../shared/layout';
-import { SEARCH_INDEX_PATH, SEARCH_INDEX_TAB_PATH } from '../../../routes';
-import { isConnectorIndex, isCrawlerIndex } from '../../../utils/indices';
-import { IndexViewLogic } from '../index_view_logic';
-
-import { SearchIndexTabId } from '../search_index';
-
-export const useIndicesNav = () => {
- const isIndexRoute = !!useRouteMatch(SEARCH_INDEX_PATH);
- const { indexName } = useParams<{ indexName: string }>();
-
- const { hasFilteringFeature, index } = useValues(IndexViewLogic);
- const {
- productFeatures: { hasDefaultIngestPipeline },
- } = useValues(KibanaLogic);
-
- if (!indexName || !isIndexRoute) return undefined;
-
- const navItems: Array> = [
- {
- 'data-test-subj': 'IndexLabel',
- id: 'indexName',
- name: indexName.toUpperCase(),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_PATH, {
- indexName,
- }),
- }),
- items: [
- {
- 'data-test-subj': 'IndexOverviewLink',
- id: 'overview',
- name: i18n.translate('xpack.enterpriseSearch.nav.searchIndicesTitle.nav.overviewTitle', {
- defaultMessage: 'Overview',
- }),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.OVERVIEW,
- }),
- }),
- },
- {
- 'data-test-subj': 'IndexDocumentsLink',
- id: 'documents',
- name: i18n.translate('xpack.enterpriseSearch.nav.searchIndicesTitle.nav.documentsTitle', {
- defaultMessage: 'Documents',
- }),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.DOCUMENTS,
- }),
- }),
- },
- {
- 'data-test-subj': 'IndexIndexMappingsLink',
- id: 'index_mappings',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.indexMappingsTitle',
- {
- defaultMessage: 'Index mappings',
- }
- ),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.INDEX_MAPPINGS,
- }),
- }),
- },
- ...(isConnectorIndex(index)
- ? [
- {
- 'data-test-subj': 'IndexConnectorsConfigurationLink',
- id: 'connectors_configuration',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.connectorsConfigurationLabel',
- {
- defaultMessage: 'Configuration',
- }
- ),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.CONFIGURATION,
- }),
- }),
- },
- ...(hasFilteringFeature
- ? [
- {
- 'data-test-subj': 'IndexSyncRulesLink',
- id: 'syncRules',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel',
- {
- defaultMessage: 'Sync rules',
- }
- ),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.SYNC_RULES,
- }),
- }),
- },
- ]
- : []),
- {
- 'data-test-subj': 'IndexSchedulingLink',
- id: 'scheduling',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle',
- {
- defaultMessage: 'Scheduling',
- }
- ),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.SCHEDULING,
- }),
- }),
- },
- ]
- : []),
- ...(isCrawlerIndex(index)
- ? [
- {
- 'data-test-subj': 'IndexDomainManagementLink',
- id: 'domain_management',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.domainManagementLabel',
- {
- defaultMessage: 'Manage Domains',
- }
- ),
- ...generateNavLink({
- shouldShowActiveForSubroutes: true,
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.DOMAIN_MANAGEMENT,
- }),
- }),
- },
- {
- 'data-test-subj': 'IndexCrawlerConfigurationLink',
- id: 'crawler_configuration',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerConfigurationLabel',
- {
- defaultMessage: 'Configuration',
- }
- ),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.CRAWLER_CONFIGURATION,
- }),
- }),
- },
- {
- 'data-test-subj': 'IndexCrawlerSchedulingLink',
- id: 'crawler_scheduling',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerSchedulingLabel',
- {
- defaultMessage: 'Scheduling',
- }
- ),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.SCHEDULING,
- }),
- }),
- },
- ]
- : []),
- ...(hasDefaultIngestPipeline
- ? [
- {
- 'data-test-subj': 'IndexPipelineLink',
- id: 'pipelines',
- name: i18n.translate(
- 'xpack.enterpriseSearch.nav.searchIndicesTitle.nav.pipelinesLabel',
- {
- defaultMessage: 'Pipelines',
- }
- ),
- ...generateNavLink({
- to: generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
- indexName,
- tabId: SearchIndexTabId.PIPELINES,
- }),
- }),
- },
- ]
- : []),
- ],
- },
- ];
-
- return navItems;
-};
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.test.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.test.tsx
deleted file mode 100644
index e5d64f960a22b..0000000000000
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.test.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import '../../../__mocks__/shallow_useeffect.mock';
-import { setMockValues } from '../../../__mocks__/kea_logic';
-
-import React from 'react';
-
-const mockUseIndicesNav = jest.fn().mockReturnValue([]);
-
-jest.mock('react-router-dom', () => ({
- useParams: () => ({}),
-}));
-
-jest.mock('./indices/indices_nav', () => ({
- useIndicesNav: (...args: any[]) => mockUseIndicesNav(...args),
-}));
-
-import { shallow } from 'enzyme';
-
-import { SearchIndex } from './search_index';
-
-const mockValues = {
- hasFilteringFeature: false,
- updateSideNavDefinition: jest.fn(),
-};
-
-describe('SearchIndex', () => {
- it('updates the side nav dynamic links', async () => {
- const updateSideNavDefinition = jest.fn();
-
- setMockValues({ ...mockValues, updateSideNavDefinition });
-
- const indicesItems = [{ foo: 'bar' }];
- mockUseIndicesNav.mockReturnValueOnce(indicesItems);
-
- shallow();
-
- expect(updateSideNavDefinition).toHaveBeenCalledWith({
- indices: indicesItems,
- });
- });
-});
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx
index e8876a7c56818..4068b35c89283 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx
+++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx
@@ -42,7 +42,6 @@ import { IndexError } from './index_error';
import { SearchIndexIndexMappings } from './index_mappings';
import { IndexNameLogic } from './index_name_logic';
import { IndexViewLogic } from './index_view_logic';
-import { useIndicesNav } from './indices/indices_nav';
import { SearchIndexOverview } from './overview';
import { SearchIndexPipelines } from './pipelines/pipelines';
@@ -82,8 +81,6 @@ export const SearchIndex: React.FC = () => {
updateSideNavDefinition,
} = useValues(KibanaLogic);
- const indicesItems = useIndicesNav();
-
useEffect(() => {
const subscription = guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$('appSearch', 'add_data')
@@ -117,11 +114,6 @@ export const SearchIndex: React.FC = () => {
return () => subscription?.unsubscribe();
}, [guidedOnboarding, index?.count]);
- useEffect(() => {
- // We update the new side nav definition with the selected indices items
- updateSideNavDefinition({ indices: indicesItems });
- }, [indicesItems, updateSideNavDefinition]);
-
useEffect(() => {
return () => {
updateSideNavDefinition({ indices: undefined });
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx
index 52cd6fe664adc..bcdeb9ed7d6c1 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx
+++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx
@@ -49,7 +49,7 @@ export const buildBaseClassicNavItems = (): ClassicNavItem[] => {
{
'data-test-subj': 'searchSideNav-Indices',
deepLink: {
- link: 'enterpriseSearchContent:searchIndices',
+ link: 'management:index_management',
shouldShowActiveForSubroutes: true,
},
id: 'search_indices',
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
index 19a0fea68969b..95e6870fa5859 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
+++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
@@ -9,10 +9,6 @@ jest.mock('./nav_link_helpers', () => ({
generateNavLink: jest.fn(({ to, items }) => ({ href: to, items })),
}));
-jest.mock('../../enterprise_search_content/components/search_index/indices/indices_nav', () => ({
- useIndicesNav: () => [],
-}));
-
import { setMockValues, mockKibanaValues } from '../../__mocks__/kea_logic';
import { renderHook } from '@testing-library/react';
@@ -45,9 +41,9 @@ const baseNavItems = [
items: [
{
'data-test-subj': 'searchSideNav-Indices',
- href: '/app/elasticsearch/content/search_indices',
+ href: '/app/management/data/index_management/',
id: 'search_indices',
- items: [],
+ items: undefined,
name: 'Indices',
},
{
@@ -152,9 +148,9 @@ const mockNavLinks = [
url: '/app/elasticsearch/overview',
},
{
- id: 'enterpriseSearchContent:searchIndices',
+ id: 'management:index_management',
title: 'Indices',
- url: '/app/elasticsearch/content/search_indices',
+ url: '/app/management/data/index_management/',
},
{
id: 'enterpriseSearchContent:connectors',
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
index d122cf788fd4f..eed071449be72 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
+++ b/x-pack/solutions/search/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
@@ -15,7 +15,6 @@ import { i18n } from '@kbn/i18n';
import { ANALYTICS_PLUGIN, APPLICATIONS_PLUGIN } from '../../../../common/constants';
import { SEARCH_APPLICATIONS_PATH, SearchApplicationViewTabs } from '../../applications/routes';
-import { useIndicesNav } from '../../enterprise_search_content/components/search_index/indices/indices_nav';
import { KibanaLogic } from '../kibana';
@@ -32,8 +31,6 @@ import { generateNavLink } from './nav_link_helpers';
export const useEnterpriseSearchNav = (alwaysReturn = false) => {
const { isSidebarEnabled, getNavLinks } = useValues(KibanaLogic);
- const indicesNavItems = useIndicesNav();
-
const navItems: Array> = useMemo(() => {
const baseNavItems = buildBaseClassicNavItems();
const deepLinks = getNavLinks().reduce((links, link) => {
@@ -41,8 +38,8 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => {
return links;
}, {} as Record);
- return generateSideNavItems(baseNavItems, deepLinks, { search_indices: indicesNavItems });
- }, [indicesNavItems]);
+ return generateSideNavItems(baseNavItems, deepLinks);
+ }, []);
if (!isSidebarEnabled && !alwaysReturn) return undefined;
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts b/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts
index 3525a546c0676..27d9fee279c2c 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts
+++ b/x-pack/solutions/search/plugins/enterprise_search/public/navigation_tree.ts
@@ -18,7 +18,6 @@ import { i18n } from '@kbn/i18n';
import type { AddSolutionNavigationArg } from '@kbn/navigation-plugin/public';
import { SEARCH_APPLICATIONS_PATH } from './applications/applications/routes';
-import { SEARCH_INDICES_PATH } from './applications/enterprise_search_content/routes';
export interface DynamicSideNavItems {
collections?: Array>;
@@ -77,7 +76,7 @@ export const getNavigationTreeDefinition = ({
id: 'es',
navigationTree$: dynamicItems$.pipe(
debounceTime(10),
- map(({ indices, searchApps, collections }) => {
+ map(({ searchApps, collections }) => {
const navTree: NavigationTreeDefinition = {
body: [
{
@@ -116,27 +115,21 @@ export const getNavigationTreeDefinition = ({
{
children: [
{
+ title: i18n.translate('xpack.enterpriseSearch.searchNav.kibana.indices', {
+ defaultMessage: 'Indices',
+ }),
+ link: 'management:index_management',
+ breadcrumbStatus:
+ 'hidden' /* management sub-pages set their breadcrumbs themselves */,
getIsActive: ({ pathNameSerialized, prepend }) => {
- const someSubItemSelected = indices?.some((index) =>
- index.items?.some((item) => item.isSelected)
- );
-
- if (someSubItemSelected) return false;
-
return (
- pathNameSerialized ===
- prepend(`/app/elasticsearch/content${SEARCH_INDICES_PATH}`)
+ pathNameSerialized.startsWith(
+ prepend('/app/management/data/index_management/')
+ ) ||
+ pathNameSerialized.startsWith(prepend('/app/elasticsearch/indices')) ||
+ pathNameSerialized.startsWith(prepend('/app/elasticsearch/start'))
);
},
- link: 'enterpriseSearchContent:searchIndices',
- renderAs: 'item',
- ...(indices
- ? {
- children: indices.map(euiItemTypeToNodeDefinition),
- isCollapsible: false,
- renderAs: 'accordion',
- }
- : {}),
},
{ link: 'enterpriseSearchContent:connectors' },
{ link: 'enterpriseSearchContent:webCrawlers' },
diff --git a/x-pack/solutions/search/plugins/enterprise_search/public/plugin.ts b/x-pack/solutions/search/plugins/enterprise_search/public/plugin.ts
index 6c33d08913c54..394b547bbf5ea 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/public/plugin.ts
+++ b/x-pack/solutions/search/plugins/enterprise_search/public/plugin.ts
@@ -59,11 +59,7 @@ import { hasEnterpriseLicense } from '../common/utils/licensing';
import { ENGINES_PATH } from './applications/app_search/routes';
import { SEARCH_APPLICATIONS_PATH } from './applications/applications/routes';
-import {
- CONNECTORS_PATH,
- SEARCH_INDICES_PATH,
- CRAWLERS_PATH,
-} from './applications/enterprise_search_content/routes';
+import { CONNECTORS_PATH, CRAWLERS_PATH } from './applications/enterprise_search_content/routes';
import { docLinks } from './applications/shared/doc_links';
import type { DynamicSideNavItems } from './navigation_tree';
@@ -120,7 +116,7 @@ const contentLinks: AppDeepLink[] = [
},
{
id: 'searchIndices',
- path: `/${SEARCH_INDICES_PATH}`,
+ path: `/app/ml/trained_models`,
title: i18n.translate('xpack.enterpriseSearch.navigation.contentIndicesLinkLabel', {
defaultMessage: 'Indices',
}),
diff --git a/x-pack/solutions/search/plugins/enterprise_search/server/plugin.ts b/x-pack/solutions/search/plugins/enterprise_search/server/plugin.ts
index 3cd0cb9d060b0..10a209b2a3ae1 100644
--- a/x-pack/solutions/search/plugins/enterprise_search/server/plugin.ts
+++ b/x-pack/solutions/search/plugins/enterprise_search/server/plugin.ts
@@ -47,6 +47,8 @@ import {
AI_SEARCH_PLUGIN,
APPLICATIONS_PLUGIN,
SEARCH_PRODUCT_NAME,
+ SEARCH_INDICES_INDICES_APP_ID,
+ SEARCH_INDICES_START_APP_ID,
} from '../common/constants';
import {
@@ -175,6 +177,8 @@ export class EnterpriseSearchPlugin implements Plugin {
VECTOR_SEARCH_PLUGIN.ID,
SEMANTIC_SEARCH_PLUGIN.ID,
AI_SEARCH_PLUGIN.ID,
+ SEARCH_INDICES_INDICES_APP_ID,
+ SEARCH_INDICES_START_APP_ID,
];
const isCloud = !!cloud?.cloudId;
diff --git a/x-pack/solutions/search/plugins/search_indices/common/index.ts b/x-pack/solutions/search/plugins/search_indices/common/index.ts
index e640397e3936d..63f9466f29f1e 100644
--- a/x-pack/solutions/search/plugins/search_indices/common/index.ts
+++ b/x-pack/solutions/search/plugins/search_indices/common/index.ts
@@ -6,10 +6,11 @@
*/
import type { SearchIndices, SearchStart } from '@kbn/deeplinks-search/deep_links';
+import { SEARCH_INDICES_INDICES_APP_ID, SEARCH_INDICES_START_APP_ID } from '@kbn/deeplinks-search';
export const PLUGIN_ID = 'searchIndices';
export const PLUGIN_NAME = 'searchIndices';
-export const START_APP_ID: SearchStart = 'elasticsearchStart';
-export const INDICES_APP_ID: SearchIndices = 'elasticsearchIndices';
+export const START_APP_ID: SearchStart = SEARCH_INDICES_START_APP_ID;
+export const INDICES_APP_ID: SearchIndices = SEARCH_INDICES_INDICES_APP_ID;
export type { IndicesStatusResponse, UserStartPrivilegesResponse } from './types';
diff --git a/x-pack/solutions/search/plugins/search_indices/common/routes.ts b/x-pack/solutions/search/plugins/search_indices/common/routes.ts
index f527fa676e2a0..7db8fb1a89033 100644
--- a/x-pack/solutions/search/plugins/search_indices/common/routes.ts
+++ b/x-pack/solutions/search/plugins/search_indices/common/routes.ts
@@ -11,3 +11,5 @@ export const GET_USER_PRIVILEGES_ROUTE = '/internal/search_indices/start_privile
export const POST_CREATE_INDEX_ROUTE = '/internal/search_indices/indices/create';
export const INDEX_DOCUMENT_ROUTE = '/internal/search_indices/{indexName}/documents/{id}';
+
+export const SEARCH_DOCUMENTS_ROUTE = '/internal/search_indices/{indexName}/documents/search';
diff --git a/x-pack/solutions/search/plugins/search_indices/kibana.jsonc b/x-pack/solutions/search/plugins/search_indices/kibana.jsonc
index 50778c3fbb4e4..68a86a01587a0 100644
--- a/x-pack/solutions/search/plugins/search_indices/kibana.jsonc
+++ b/x-pack/solutions/search/plugins/search_indices/kibana.jsonc
@@ -20,7 +20,8 @@
"cloud",
"console",
"usageCollection",
- "serverless"
+ "serverless",
+ "searchNavigation"
],
"requiredBundles": [
"kibanaReact",
diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index_page.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index_page.tsx
index 56ee5f49c5339..1466fbdcec6a6 100644
--- a/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index_page.tsx
+++ b/x-pack/solutions/search/plugins/search_indices/public/components/create_index/create_index_page.tsx
@@ -8,7 +8,7 @@
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiLoadingLogo, EuiPageTemplate } from '@elastic/eui';
+import { EuiLoadingLogo } from '@elastic/eui';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { useKibana } from '../../hooks/use_kibana';
@@ -24,7 +24,7 @@ const CreateIndexLabel = i18n.translate('xpack.searchIndices.createIndex.docTitl
});
export const CreateIndexPage = () => {
- const { console: consolePlugin } = useKibana().services;
+ const { console: consolePlugin, history, searchNavigation } = useKibana().services;
const {
data: indicesData,
isInitialLoading,
@@ -39,11 +39,12 @@ export const CreateIndexPage = () => {
usePageChrome(CreateIndexLabel, [...IndexManagementBreadcrumbs, { text: CreateIndexLabel }]);
return (
-
{isInitialLoading && }
@@ -53,6 +54,6 @@ export const CreateIndexPage = () => {
)}
{embeddableConsole}
-
+
);
};
diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx
index fb09943710dc6..e7d9eb96bb660 100644
--- a/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx
+++ b/x-pack/solutions/search/plugins/search_indices/public/components/indices/details_page.tsx
@@ -21,6 +21,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { SectionLoading } from '@kbn/es-ui-shared-plugin/public';
import { ApiKeyForm } from '@kbn/search-api-keys-components';
+import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { useNavigateToDiscover } from '../../hooks/use_navigate_to_discover';
import { useIndex } from '../../hooks/api/use_index';
import { useKibana } from '../../hooks/use_kibana';
@@ -45,7 +46,14 @@ export const SearchIndexDetailsPage = () => {
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
const tabId = decodeURIComponent(useParams<{ tabId: string }>().tabId);
- const { console: consolePlugin, docLinks, application, history, share } = useKibana().services;
+ const {
+ console: consolePlugin,
+ docLinks,
+ application,
+ history,
+ share,
+ searchNavigation,
+ } = useKibana().services;
const {
data: index,
refetch,
@@ -189,13 +197,14 @@ export const SearchIndexDetailsPage = () => {
}
return (
-
{isIndexError || isMappingsError || !index || !mappings || !indexDocuments ? (
{
/>
)}
{embeddableConsole}
-
+
);
};
diff --git a/x-pack/solutions/search/plugins/search_indices/public/components/quick_stats/quick_stat.tsx b/x-pack/solutions/search/plugins/search_indices/public/components/quick_stats/quick_stat.tsx
index 9df28ccec4bd6..9f2e3785b3681 100644
--- a/x-pack/solutions/search/plugins/search_indices/public/components/quick_stats/quick_stat.tsx
+++ b/x-pack/solutions/search/plugins/search_indices/public/components/quick_stats/quick_stat.tsx
@@ -54,7 +54,7 @@ export const QuickStat: React.FC = ({
const id = useGeneratedHtmlId({
prefix: 'formAccordion',
- suffix: title,
+ suffix: title.replace(/\s/g, '_'),
});
return (
diff --git a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts
index d566b90916892..a8afd69385623 100644
--- a/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts
+++ b/x-pack/solutions/search/plugins/search_indices/public/hooks/api/use_document_search.ts
@@ -33,7 +33,7 @@ export const useIndexDocumentSearch = (indexName: string) => {
refetchIntervalInBackground: true,
refetchOnWindowFocus: 'always',
queryFn: async ({ signal }) =>
- http.post(`/internal/serverless_search/indices/${indexName}/search`, {
+ http.post(`/internal/search_indices/${indexName}/documents/search`, {
body: JSON.stringify({
searchQuery: '',
trackTotalHits: true,
diff --git a/x-pack/solutions/search/plugins/search_indices/public/types.ts b/x-pack/solutions/search/plugins/search_indices/public/types.ts
index ccf4d56e13a67..a0f35333cbd5a 100644
--- a/x-pack/solutions/search/plugins/search_indices/public/types.ts
+++ b/x-pack/solutions/search/plugins/search_indices/public/types.ts
@@ -7,6 +7,7 @@
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import type { ConsolePluginSetup, ConsolePluginStart } from '@kbn/console-plugin/public';
+import type { SearchNavigationPluginStart } from '@kbn/search-navigation/public';
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import type {
@@ -53,6 +54,7 @@ export interface SearchIndicesAppPluginStartDependencies {
serverless?: ServerlessPluginStart;
usageCollection?: UsageCollectionStart;
indexManagement: IndexManagementPluginStart;
+ searchNavigation?: SearchNavigationPluginStart;
}
export interface SearchIndicesServicesContextDeps {
diff --git a/x-pack/solutions/search/plugins/search_indices/server/config.ts b/x-pack/solutions/search/plugins/search_indices/server/config.ts
index 408d45cf23e2e..7aab566c6a7f0 100644
--- a/x-pack/solutions/search/plugins/search_indices/server/config.ts
+++ b/x-pack/solutions/search/plugins/search_indices/server/config.ts
@@ -9,7 +9,7 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { PluginConfigDescriptor } from '@kbn/core/server';
const configSchema = schema.object({
- enabled: schema.boolean({ defaultValue: false }),
+ enabled: schema.boolean({ defaultValue: true }),
});
export type SearchIndicesConfig = TypeOf;
diff --git a/x-pack/solutions/search/plugins/search_indices/server/routes/indices.ts b/x-pack/solutions/search/plugins/search_indices/server/routes/indices.ts
index cd42470d3c3fc..d8a65f87300cd 100644
--- a/x-pack/solutions/search/plugins/search_indices/server/routes/indices.ts
+++ b/x-pack/solutions/search/plugins/search_indices/server/routes/indices.ts
@@ -9,8 +9,10 @@ import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import type { IRouter } from '@kbn/core/server';
import type { Logger } from '@kbn/logging';
+import { DEFAULT_DOCS_PER_PAGE } from '@kbn/search-index-documents/types';
+import { fetchSearchResults } from '@kbn/search-index-documents/lib';
-import { POST_CREATE_INDEX_ROUTE } from '../../common/routes';
+import { POST_CREATE_INDEX_ROUTE, SEARCH_DOCUMENTS_ROUTE } from '../../common/routes';
import { CreateIndexRequest } from '../../common/types';
import { createIndex } from '../lib/indices';
@@ -62,4 +64,52 @@ export function registerIndicesRoutes(router: IRouter, logger: Logger) {
}
}
);
+
+ router.post(
+ {
+ path: SEARCH_DOCUMENTS_ROUTE,
+ validate: {
+ body: schema.object({
+ searchQuery: schema.string({
+ defaultValue: '',
+ }),
+ trackTotalHits: schema.boolean({ defaultValue: false }),
+ }),
+ params: schema.object({
+ indexName: schema.string(),
+ }),
+ query: schema.object({
+ page: schema.number({ defaultValue: 0, min: 0 }),
+ size: schema.number({
+ defaultValue: DEFAULT_DOCS_PER_PAGE,
+ min: 0,
+ }),
+ }),
+ },
+ },
+ async (context, request, response) => {
+ const client = (await context.core).elasticsearch.client.asCurrentUser;
+ const indexName = decodeURIComponent(request.params.indexName);
+ const searchQuery = request.body.searchQuery;
+ const { page = 0, size = DEFAULT_DOCS_PER_PAGE } = request.query;
+ const from = page * size;
+ const trackTotalHits = request.body.trackTotalHits;
+
+ const searchResults = await fetchSearchResults(
+ client,
+ indexName,
+ searchQuery,
+ from,
+ size,
+ trackTotalHits
+ );
+
+ return response.ok({
+ body: {
+ results: searchResults,
+ },
+ headers: { 'content-type': 'application/json' },
+ });
+ }
+ );
}
diff --git a/x-pack/solutions/search/plugins/search_indices/tsconfig.json b/x-pack/solutions/search/plugins/search_indices/tsconfig.json
index 471a6a5541a5f..c69c785e087ab 100644
--- a/x-pack/solutions/search/plugins/search_indices/tsconfig.json
+++ b/x-pack/solutions/search/plugins/search_indices/tsconfig.json
@@ -39,7 +39,8 @@
"@kbn/deeplinks-search",
"@kbn/core-chrome-browser",
"@kbn/serverless",
- "@kbn/utility-types"
+ "@kbn/utility-types",
+ "@kbn/search-navigation"
],
"exclude": [
"target/**/*",
diff --git a/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.test.tsx b/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.test.tsx
index 485bb5eb5df55..ed9799abc2578 100644
--- a/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.test.tsx
+++ b/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.test.tsx
@@ -56,7 +56,8 @@ describe('CreateIndexButton', () => {
url: {
locators: {
get: jest.fn().mockReturnValue({
- getUrl: jest.fn().mockReturnValue('mock-create-index-url'),
+ getUrl: jest.fn().mockReturnValue('mock-url'),
+ getRedirectUrl: jest.fn().mockReturnValue('mock-shown-url'),
}),
},
},
@@ -72,7 +73,7 @@ describe('CreateIndexButton', () => {
fireEvent.click(createIndexButton);
await waitFor(() => {
- expect(navigateToUrl).toHaveBeenCalledWith('mock-create-index-url');
+ expect(navigateToUrl).toHaveBeenCalledWith('mock-url');
});
});
});
diff --git a/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.tsx b/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.tsx
index 5bc9f84c833fb..8471e8ab9cea8 100644
--- a/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.tsx
+++ b/x-pack/solutions/search/plugins/search_playground/public/components/setup_page/create_index_button.tsx
@@ -7,7 +7,7 @@
import { EuiButton, EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
-import React, { useCallback, useMemo } from 'react';
+import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../hooks/use_kibana';
@@ -15,27 +15,33 @@ export const CreateIndexButton: React.FC = () => {
const {
services: { application, share },
} = useKibana();
+
const createIndexLocator = useMemo(
- () =>
- share.url.locators.get('CREATE_INDEX_LOCATOR_ID') ??
- share.url.locators.get('SEARCH_CREATE_INDEX'),
+ () => share.url.locators.get('SEARCH_CREATE_INDEX'),
[share.url.locators]
);
- const handleNavigateToIndex = useCallback(async () => {
- const createIndexUrl = await createIndexLocator?.getUrl({});
- if (createIndexUrl) {
- application?.navigateToUrl(createIndexUrl);
+ const createIndexLinkProps = useMemo(() => {
+ if (createIndexLocator) {
+ return {
+ href: createIndexLocator.getRedirectUrl({}),
+ onClick: async (event: React.MouseEvent) => {
+ event.preventDefault();
+ const url = await createIndexLocator.getUrl({});
+ application?.navigateToUrl(url);
+ },
+ };
}
+ return null;
}, [application, createIndexLocator]);
- return createIndexLocator ? (
+ return createIndexLocator && createIndexLinkProps ? (
{
await PageObjects.settings.clickIndexManagement();
await PageObjects.indexManagement.clickIndexAt(0);
+ await PageObjects.searchIndexDetail.expectIndexDetailsPageIsLoaded();
await a11y.testAppSnapshot();
});
it('index details - settings', async () => {
- await PageObjects.indexManagement.clickIndexDetailsTab('settings');
+ await PageObjects.searchIndexDetail.changeTab('settingsTab');
await a11y.testAppSnapshot();
});
it('index details - edit settings', async () => {
- await PageObjects.indexManagement.clickIndexDetailsTab('settings');
+ await PageObjects.searchIndexDetail.changeTab('settingsTab');
await PageObjects.indexManagement.clickIndexDetailsEditSettingsSwitch();
await a11y.testAppSnapshot();
});
it('index details - mappings', async () => {
- await PageObjects.indexManagement.clickIndexDetailsTab('mappings');
- await a11y.testAppSnapshot();
- });
-
- it('index details - stats', async () => {
- await PageObjects.indexManagement.clickIndexDetailsTab('stats');
+ await PageObjects.searchIndexDetail.changeTab('mappingsTab');
await a11y.testAppSnapshot();
});
});
diff --git a/x-pack/test/functional/apps/index_management/index_details_page.ts b/x-pack/test/functional/apps/index_management/index_details_page.ts
index 454210ef9ab76..5151395574f74 100644
--- a/x-pack/test/functional/apps/index_management/index_details_page.ts
+++ b/x-pack/test/functional/apps/index_management/index_details_page.ts
@@ -8,7 +8,7 @@
import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
- const pageObjects = getPageObjects(['common', 'indexManagement', 'header']);
+ const pageObjects = getPageObjects(['common', 'indexManagement', 'header', 'searchIndexDetail']);
const log = getService('log');
const security = getService('security');
@@ -18,14 +18,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.common.navigateToApp('indexManagement');
});
- it('Navigates to the index details page from the home page', async () => {
+ it.skip('Navigates to the index details page from the home page', async () => {
await log.debug('Navigating to the index details page');
// display hidden indices to have some rows in the indices table
await pageObjects.indexManagement.toggleHiddenIndices();
// click the first index in the table and wait for the index details page
- await pageObjects.indexManagement.indexDetailsPage.openIndexDetailsPage(0);
- await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();
+ await pageObjects.searchIndexDetail.openIndicesDetailFromIndexManagementIndicesListTable(0);
+ await pageObjects.searchIndexDetail.expectIndexDetailsPageIsLoaded();
});
});
};
diff --git a/x-pack/test/functional/apps/remote_clusters/ccs/remote_clusters_index_management_flow.ts b/x-pack/test/functional/apps/remote_clusters/ccs/remote_clusters_index_management_flow.ts
index 4f710b3730353..5666fd540fe99 100644
--- a/x-pack/test/functional/apps/remote_clusters/ccs/remote_clusters_index_management_flow.ts
+++ b/x-pack/test/functional/apps/remote_clusters/ccs/remote_clusters_index_management_flow.ts
@@ -14,6 +14,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
'remoteClusters',
'indexManagement',
'crossClusterReplication',
+ 'searchIndexDetail',
]);
const security = getService('security');
const retry = getService('retry');
@@ -88,8 +89,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('Verify that the follower index is duplicating from the remote.', async () => {
await pageObjects.indexManagement.clickIndexAt(0);
- await pageObjects.indexManagement.performIndexAction('flush');
- await testSubjects.click('indexDetailsBackToIndicesButton');
+ await pageObjects.searchIndexDetail.expectIndexDetailsPageIsLoaded();
await pageObjects.common.navigateToApp('indexManagement');
await retry.waitForWithTimeout('indices table to be visible', 15000, async () => {
return await testSubjects.isDisplayed('indicesList');
diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts
index 0d270661a05df..0641016e9a78e 100644
--- a/x-pack/test/functional/page_objects/index.ts
+++ b/x-pack/test/functional/page_objects/index.ts
@@ -55,6 +55,10 @@ import { WatcherPageObject } from './watcher_page';
import { SearchProfilerPageProvider } from './search_profiler_page';
import { SearchPlaygroundPageProvider } from './search_playground_page';
import { SearchClassicNavigationProvider } from './search_classic_navigation';
+import { SearchStartProvider } from './search_start';
+import { SearchApiKeysProvider } from './search_api_keys';
+import { SearchNavigationServiceProvider } from './search_navigation';
+import { SearchIndexDetailProvider } from './search_index_detail';
// just like services, PageObjects are defined as a map of
// names to Providers. Merge in Kibana's or pick specific ones
@@ -94,7 +98,11 @@ export const pageObjects = {
reporting: ReportingPageObject,
roleMappings: RoleMappingsPageProvider,
rollup: RollupPageObject,
+ searchApiKeys: SearchApiKeysProvider,
searchClassicNavigation: SearchClassicNavigationProvider,
+ searchStart: SearchStartProvider,
+ searchIndexDetail: SearchIndexDetailProvider,
+ searchNavigation: SearchNavigationServiceProvider,
searchProfiler: SearchProfilerPageProvider,
searchPlayground: SearchPlaygroundPageProvider,
searchSessionsManagement: SearchSessionsPageProvider,
diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts
index a29e84e677cbc..02c4d7a1d9ffa 100644
--- a/x-pack/test/functional/page_objects/index_management_page.ts
+++ b/x-pack/test/functional/page_objects/index_management_page.ts
@@ -82,9 +82,6 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext)
async clickIndexAt(indexOfRow: number): Promise {
const indexList = await testSubjects.findAll('indexTableIndexNameLink');
await indexList[indexOfRow].click();
- await retry.waitFor('details page title to show up', async () => {
- return (await testSubjects.isDisplayed('indexDetailsHeader')) === true;
- });
},
async performIndexAction(action: string) {
diff --git a/x-pack/test/functional/page_objects/search_api_keys.ts b/x-pack/test/functional/page_objects/search_api_keys.ts
new file mode 100644
index 0000000000000..0ed836b9ab3d2
--- /dev/null
+++ b/x-pack/test/functional/page_objects/search_api_keys.ts
@@ -0,0 +1,128 @@
+/*
+ * 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 { SecurityApiKey } from '@elastic/elasticsearch/lib/api/types';
+import { FtrProviderContext } from '../ftr_provider_context';
+
+const APIKEY_MASK = '•'.repeat(60);
+
+export function SearchApiKeysProvider({ getService, getPageObjects }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+ const browser = getService('browser');
+ const pageObjects = getPageObjects(['common', 'apiKeys']);
+ const retry = getService('retry');
+ const es = getService('es');
+
+ const getAPIKeyFromSessionStorage = async () => {
+ const sessionStorageKey = await browser.getSessionStorageItem('searchApiKey');
+ return sessionStorageKey && JSON.parse(sessionStorageKey);
+ };
+
+ return {
+ async clearAPIKeySessionStorage() {
+ await browser.clearSessionStorage();
+ },
+
+ async expectAPIKeyExists() {
+ await testSubjects.existOrFail('apiKeyFormAPIKey', { timeout: 1000 });
+ },
+
+ async expectAPIKeyAvailable() {
+ await testSubjects.existOrFail('apiKeyFormAPIKey');
+ await retry.try(async () => {
+ expect(await testSubjects.getVisibleText('apiKeyFormAPIKey')).to.be(APIKEY_MASK);
+ });
+ await testSubjects.click('showAPIKeyButton');
+ let apiKey;
+ await retry.try(async () => {
+ apiKey = await testSubjects.getVisibleText('apiKeyFormAPIKey');
+ expect(apiKey).to.be.a('string');
+ expect(apiKey.length).to.be(60);
+ expect(apiKey).to.not.be(APIKEY_MASK);
+ });
+ const sessionStorageKey = await getAPIKeyFromSessionStorage();
+ expect(sessionStorageKey.encoded).to.eql(apiKey);
+ },
+
+ async expectAPIKeyNoPrivileges() {
+ await testSubjects.existOrFail('apiKeyFormNoUserPrivileges');
+ },
+
+ async getAPIKeyFromSessionStorage() {
+ return getAPIKeyFromSessionStorage();
+ },
+
+ async getAPIKeyFromUI() {
+ let apiKey = '';
+ await retry.try(async () => {
+ apiKey = await testSubjects.getVisibleText('apiKeyFormAPIKey');
+ expect(apiKey).to.not.be(APIKEY_MASK);
+ });
+ expect(apiKey).to.be.a('string');
+ return apiKey;
+ },
+
+ async invalidateAPIKey(apiKeyId: string) {
+ await es.security.invalidateApiKey({ ids: [apiKeyId] });
+ },
+
+ async createAPIKey() {
+ await es.security.createApiKey({
+ name: 'test-api-key',
+ role_descriptors: {},
+ });
+ },
+
+ async expectAPIKeyCreate() {
+ await testSubjects.existOrFail('apiKeyFormAPIKey');
+ await retry.try(async () => {
+ expect(await testSubjects.getVisibleText('apiKeyFormAPIKey')).to.be(APIKEY_MASK);
+ });
+ await testSubjects.click('showAPIKeyButton');
+ await retry.try(async () => {
+ const apiKey = await testSubjects.getVisibleText('apiKeyFormAPIKey');
+ expect(apiKey).to.be.a('string');
+ expect(apiKey.length).to.be(60);
+ expect(apiKey).to.not.be(APIKEY_MASK);
+ });
+ },
+
+ async deleteAPIKeys() {
+ const filterInvalid = (key: SecurityApiKey) => !key.invalidated;
+
+ const { api_keys: apiKeys } = await es.security.getApiKey();
+
+ const validKeys = apiKeys.filter(filterInvalid);
+
+ if (validKeys.length === 0) {
+ return;
+ }
+
+ await es.security.invalidateApiKey({
+ ids: validKeys.map((key) => key.id),
+ });
+ },
+
+ async expectCreateApiKeyAction() {
+ await testSubjects.existOrFail('createAPIKeyButton');
+ },
+
+ async createApiKeyFromFlyout() {
+ const apiKeyName = 'Happy API Key';
+ await testSubjects.click('createAPIKeyButton');
+ expect(await pageObjects.apiKeys.getFlyoutTitleText()).to.be('Create API key');
+
+ await pageObjects.apiKeys.setApiKeyName(apiKeyName);
+ await pageObjects.apiKeys.clickSubmitButtonOnApiKeyFlyout();
+ },
+
+ async expectAPIKeyNotAvailable() {
+ await testSubjects.missingOrFail('apiKeyFormAPIKey');
+ },
+ };
+}
diff --git a/x-pack/test/functional/page_objects/search_index_detail.ts b/x-pack/test/functional/page_objects/search_index_detail.ts
new file mode 100644
index 0000000000000..83d420127beed
--- /dev/null
+++ b/x-pack/test/functional/page_objects/search_index_detail.ts
@@ -0,0 +1,315 @@
+/*
+ * 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 SearchIndexDetailProvider({ getService }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+ const browser = getService('browser');
+ const retry = getService('retry');
+
+ const expectIndexDetailPageHeader = async function () {
+ await testSubjects.existOrFail('searchIndexDetailsHeader', { timeout: 2000 });
+ };
+ const expectSearchIndexDetailsTabsExists = async function () {
+ await testSubjects.existOrFail('dataTab');
+ await testSubjects.existOrFail('mappingsTab');
+ await testSubjects.existOrFail('settingsTab');
+ };
+
+ return {
+ expectIndexDetailPageHeader,
+ expectSearchIndexDetailsTabsExists,
+ async expectAPIReferenceDocLinkExists() {
+ await testSubjects.existOrFail('ApiReferenceDoc', { timeout: 2000 });
+ },
+ async expectIndexDetailsPageIsLoaded() {
+ await expectIndexDetailPageHeader();
+ await expectSearchIndexDetailsTabsExists();
+ },
+ async expectActionItemReplacedWhenHasDocs() {
+ await testSubjects.missingOrFail('ApiReferenceDoc', { timeout: 2000 });
+ await testSubjects.existOrFail('useInPlaygroundLink', { timeout: 5000 });
+ await testSubjects.existOrFail('viewInDiscoverLink', { timeout: 5000 });
+ },
+ async expectConnectionDetails() {
+ await testSubjects.existOrFail('connectionDetailsEndpoint', { timeout: 2000 });
+ expect(await (await testSubjects.find('connectionDetailsEndpoint')).getVisibleText()).match(
+ /^https?\:\/\/.*(\:\d+)?/
+ );
+ },
+ async expectQuickStats() {
+ await testSubjects.existOrFail('quickStats', { timeout: 2000 });
+ const quickStatsElem = await testSubjects.find('quickStats');
+ const quickStatsDocumentElem = await quickStatsElem.findByTestSubject(
+ 'QuickStatsDocumentCount'
+ );
+ expect(await quickStatsDocumentElem.getVisibleText()).to.contain('Document count\n0');
+ expect(await quickStatsDocumentElem.getVisibleText()).not.to.contain('Index Size');
+ await quickStatsDocumentElem.click();
+ expect(await quickStatsDocumentElem.getVisibleText()).to.contain('Index Size\n227b');
+ },
+
+ async expectQuickStatsToHaveDocumentCount(count: number) {
+ const quickStatsElem = await testSubjects.find('quickStats');
+ const quickStatsDocumentElem = await quickStatsElem.findByTestSubject(
+ 'QuickStatsDocumentCount'
+ );
+ expect(await quickStatsDocumentElem.getVisibleText()).to.contain(`Document count\n${count}`);
+ },
+
+ async expectQuickStatsAIMappings() {
+ await testSubjects.existOrFail('quickStats', { timeout: 2000 });
+ const quickStatsElem = await testSubjects.find('quickStats');
+ const quickStatsAIMappingsElem = await quickStatsElem.findByTestSubject(
+ 'QuickStatsAIMappings'
+ );
+ await quickStatsAIMappingsElem.click();
+ await testSubjects.existOrFail('setupAISearchButton', { timeout: 2000 });
+ },
+
+ async expectQuickStatsAIMappingsToHaveVectorFields() {
+ const quickStatsDocumentElem = await testSubjects.find('QuickStatsAIMappings');
+ await quickStatsDocumentElem.click();
+ expect(await quickStatsDocumentElem.getVisibleText()).to.contain('AI Search\n1 Field');
+ await testSubjects.missingOrFail('setupAISearchButton', { timeout: 2000 });
+ },
+
+ async expectAddDocumentCodeExamples() {
+ await testSubjects.existOrFail('SearchIndicesAddDocumentsCode', { timeout: 2000 });
+ },
+
+ async expectHasIndexDocuments() {
+ await retry.try(async () => {
+ await testSubjects.existOrFail('search-index-documents-result', { timeout: 2000 });
+ });
+ },
+
+ async expectMoreOptionsActionButtonExists() {
+ await testSubjects.existOrFail('moreOptionsActionButton');
+ },
+ async clickMoreOptionsActionsButton() {
+ await testSubjects.click('moreOptionsActionButton');
+ },
+ async expectMoreOptionsOverviewMenuIsShown() {
+ await testSubjects.existOrFail('moreOptionsContextMenu');
+ },
+ async expectToNavigateToPlayground(indexName: string) {
+ await testSubjects.click('moreOptionsPlayground');
+ expect(await browser.getCurrentUrl()).contain(
+ `/search_playground/chat?default-index=${indexName}`
+ );
+ await testSubjects.existOrFail('chatPage');
+ },
+ async expectAPIReferenceDocLinkExistsInMoreOptions() {
+ await testSubjects.existOrFail('moreOptionsApiReference', { timeout: 2000 });
+ },
+ async expectAPIReferenceDocLinkMissingInMoreOptions() {
+ await testSubjects.missingOrFail('moreOptionsApiReference', { timeout: 2000 });
+ },
+ async expectDeleteIndexButtonToBeDisabled() {
+ await testSubjects.existOrFail('moreOptionsDeleteIndex');
+ const deleteIndexButton = await testSubjects.isEnabled('moreOptionsDeleteIndex');
+ expect(deleteIndexButton).to.be(false);
+ await testSubjects.moveMouseTo('moreOptionsDeleteIndex');
+ await testSubjects.existOrFail('moreOptionsDeleteIndexTooltip');
+ },
+ async expectDeleteIndexButtonToBeEnabled() {
+ await testSubjects.existOrFail('moreOptionsDeleteIndex');
+ const deleteIndexButton = await testSubjects.isEnabled('moreOptionsDeleteIndex');
+ expect(deleteIndexButton).to.be(true);
+ },
+ async expectDeleteIndexButtonExistsInMoreOptions() {
+ await testSubjects.existOrFail('moreOptionsDeleteIndex');
+ },
+ async clickDeleteIndexButton() {
+ await testSubjects.click('moreOptionsDeleteIndex');
+ },
+ async expectDeleteIndexModalExists() {
+ await testSubjects.existOrFail('deleteIndexActionModal');
+ },
+ async clickConfirmingDeleteIndex() {
+ await testSubjects.existOrFail('confirmModalConfirmButton');
+ await testSubjects.click('confirmModalConfirmButton');
+ },
+ async expectPageLoadErrorExists() {
+ await retry.tryForTime(60 * 1000, async () => {
+ await testSubjects.existOrFail('pageLoadError');
+ });
+
+ await testSubjects.existOrFail('loadingErrorBackToIndicesButton');
+ await testSubjects.existOrFail('reloadButton');
+ },
+ async expectIndexNotFoundErrorExists() {
+ const pageLoadErrorElement = await (
+ await testSubjects.find('pageLoadError')
+ ).findByClassName('euiTitle');
+ expect(await pageLoadErrorElement.getVisibleText()).to.contain('Not Found');
+ },
+ async clickPageReload() {
+ await retry.tryForTime(60 * 1000, async () => {
+ await testSubjects.click('reloadButton', 2000);
+ });
+ },
+ async expectTabsExists() {
+ await testSubjects.existOrFail('mappingsTab', { timeout: 2000 });
+ await testSubjects.existOrFail('dataTab', { timeout: 2000 });
+ },
+ async changeTab(tab: 'dataTab' | 'mappingsTab' | 'settingsTab') {
+ await testSubjects.click(tab);
+ },
+ async expectUrlShouldChangeTo(tab: 'data' | 'mappings' | 'settings') {
+ expect(await browser.getCurrentUrl()).contain(`/${tab}`);
+ },
+ async expectMappingsComponentIsVisible() {
+ await testSubjects.existOrFail('indexDetailsMappingsToggleViewButton', { timeout: 2000 });
+ },
+ async expectSettingsComponentIsVisible() {
+ await testSubjects.existOrFail('indexDetailsSettingsEditModeSwitch', { timeout: 2000 });
+ },
+ async expectEditSettingsIsDisabled() {
+ await testSubjects.existOrFail('indexDetailsSettingsEditModeSwitch', { timeout: 2000 });
+ const isEditSettingsButtonDisabled = await testSubjects.isEnabled(
+ 'indexDetailsSettingsEditModeSwitch'
+ );
+ expect(isEditSettingsButtonDisabled).to.be(false);
+ await testSubjects.moveMouseTo('indexDetailsSettingsEditModeSwitch');
+ await testSubjects.existOrFail('indexDetailsSettingsEditModeSwitchToolTip');
+ },
+ async expectEditSettingsToBeEnabled() {
+ await testSubjects.existOrFail('indexDetailsSettingsEditModeSwitch', { timeout: 2000 });
+ const isEditSettingsButtonDisabled = await testSubjects.isEnabled(
+ 'indexDetailsSettingsEditModeSwitch'
+ );
+ expect(isEditSettingsButtonDisabled).to.be(true);
+ },
+ async expectSelectedLanguage(language: string) {
+ await testSubjects.existOrFail('codeExampleLanguageSelect');
+ expect(
+ (await testSubjects.getVisibleText('codeExampleLanguageSelect')).toLowerCase()
+ ).contain(language);
+ },
+ async selectCodingLanguage(language: string) {
+ await testSubjects.existOrFail('codeExampleLanguageSelect');
+ await testSubjects.click('codeExampleLanguageSelect');
+ await testSubjects.existOrFail(`lang-option-${language}`);
+ await testSubjects.click(`lang-option-${language}`);
+ expect(
+ (await testSubjects.getVisibleText('codeExampleLanguageSelect')).toLowerCase()
+ ).contain(language);
+ },
+ async codeSampleContainsValue(subject: string, value: string) {
+ const tstSubjId = `${subject}-code-block`;
+ await testSubjects.existOrFail(tstSubjId);
+ expect(await testSubjects.getVisibleText(tstSubjId)).contain(value);
+ },
+ async openConsoleCodeExample() {
+ await testSubjects.existOrFail('tryInConsoleButton');
+ await testSubjects.click('tryInConsoleButton');
+ },
+
+ async expectAPIKeyToBeVisibleInCodeBlock(apiKey: string) {
+ await testSubjects.existOrFail('ingestDataCodeExample-code-block');
+ expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
+ apiKey
+ );
+ },
+
+ async expectHasSampleDocuments() {
+ await testSubjects.existOrFail('ingestDataCodeExample-code-block');
+ expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
+ 'Yellowstone National Park'
+ );
+ expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
+ 'Yosemite National Park'
+ );
+ expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
+ 'Rocky Mountain National Park'
+ );
+ },
+
+ async expectSampleDocumentsWithCustomMappings() {
+ await browser.refresh();
+ await testSubjects.existOrFail('ingestDataCodeExample-code-block');
+ expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
+ 'Example text 1'
+ );
+ expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
+ 'Example text 2'
+ );
+ expect(await testSubjects.getVisibleText('ingestDataCodeExample-code-block')).to.contain(
+ 'Example text 3'
+ );
+ },
+
+ async clickFirstDocumentDeleteAction() {
+ await testSubjects.existOrFail('documentMetadataButton');
+ await testSubjects.click('documentMetadataButton');
+ await testSubjects.existOrFail('deleteDocumentButton');
+ await testSubjects.click('deleteDocumentButton');
+ },
+ async expectDeleteDocumentActionNotVisible() {
+ await testSubjects.existOrFail('documentMetadataButton');
+ await testSubjects.click('documentMetadataButton');
+ await testSubjects.missingOrFail('deleteDocumentButton');
+ },
+ async expectDeleteDocumentActionIsDisabled() {
+ await testSubjects.existOrFail('documentMetadataButton');
+ await testSubjects.click('documentMetadataButton');
+ await testSubjects.existOrFail('deleteDocumentButton');
+ const isDeleteDocumentEnabled = await testSubjects.isEnabled('deleteDocumentButton');
+ expect(isDeleteDocumentEnabled).to.be(false);
+ await testSubjects.moveMouseTo('deleteDocumentButton');
+ await testSubjects.existOrFail('deleteDocumentButtonToolTip');
+ },
+ async expectDeleteDocumentActionToBeEnabled() {
+ await testSubjects.existOrFail('documentMetadataButton');
+ await testSubjects.click('documentMetadataButton');
+ await testSubjects.existOrFail('deleteDocumentButton');
+ const isDeleteDocumentEnabled = await testSubjects.isEnabled('deleteDocumentButton');
+ expect(isDeleteDocumentEnabled).to.be(true);
+ },
+
+ async openIndicesDetailFromIndexManagementIndicesListTable(indexOfRow: number) {
+ const indexList = await testSubjects.findAll('indexTableIndexNameLink');
+ await indexList[indexOfRow].click();
+ await retry.waitFor('index details page title to show up', async () => {
+ return (await testSubjects.isDisplayed('searchIndexDetailsHeader')) === true;
+ });
+ },
+
+ async expectBreadcrumbNavigationWithIndexName(indexName: string) {
+ await testSubjects.existOrFail('euiBreadcrumb');
+ expect(await testSubjects.getVisibleText('breadcrumb last')).to.contain(indexName);
+ },
+
+ async clickOnIndexManagementBreadcrumb() {
+ const breadcrumbs = await testSubjects.findAll('breadcrumb');
+ for (const breadcrumb of breadcrumbs) {
+ if ((await breadcrumb.getVisibleText()) === 'Index Management') {
+ await breadcrumb.click();
+ return;
+ }
+ }
+ },
+
+ async expectAddFieldToBeDisabled() {
+ await testSubjects.existOrFail('indexDetailsMappingsAddField');
+ const isMappingsFieldEnabled = await testSubjects.isEnabled('indexDetailsMappingsAddField');
+ expect(isMappingsFieldEnabled).to.be(false);
+ await testSubjects.moveMouseTo('indexDetailsMappingsAddField');
+ await testSubjects.existOrFail('indexDetailsMappingsAddFieldTooltip');
+ },
+
+ async expectAddFieldToBeEnabled() {
+ await testSubjects.existOrFail('indexDetailsMappingsAddField');
+ const isMappingsFieldEnabled = await testSubjects.isEnabled('indexDetailsMappingsAddField');
+ expect(isMappingsFieldEnabled).to.be(true);
+ },
+ };
+}
diff --git a/x-pack/test/functional/page_objects/search_navigation.ts b/x-pack/test/functional/page_objects/search_navigation.ts
new file mode 100644
index 0000000000000..9bbfbeba33f15
--- /dev/null
+++ b/x-pack/test/functional/page_objects/search_navigation.ts
@@ -0,0 +1,56 @@
+/*
+ * 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';
+
+export function SearchNavigationServiceProvider({
+ getService,
+ getPageObjects,
+}: FtrProviderContext) {
+ const retry = getService('retry');
+ const { common, indexManagement, header } = getPageObjects([
+ 'header',
+ 'common',
+ 'indexManagement',
+ ]);
+ const testSubjects = getService('testSubjects');
+
+ return {
+ async navigateToElasticsearchStartPage(expectRedirect: boolean = false) {
+ await retry.tryForTime(60 * 1000, async () => {
+ await common.navigateToApp('elasticsearch/start', {
+ shouldLoginIfPrompted: false,
+ });
+ if (!expectRedirect) {
+ await testSubjects.existOrFail('elasticsearchStartPage', { timeout: 2000 });
+ }
+ });
+ },
+ async navigateToIndexDetailPage(indexName: string) {
+ await retry.tryForTime(60 * 1000, async () => {
+ await common.navigateToApp(`elasticsearch/indices/index_details/${indexName}`, {
+ shouldLoginIfPrompted: false,
+ });
+ });
+ await testSubjects.existOrFail('searchIndicesDetailsPage', { timeout: 2000 });
+ },
+ async navigateToInferenceManagementPage(expectRedirect: boolean = false) {
+ await common.navigateToApp('searchInferenceEndpoints', {
+ shouldLoginIfPrompted: false,
+ });
+ },
+
+ async navigateToIndexManagementPage() {
+ await retry.tryForTime(10 * 1000, async () => {
+ await common.navigateToApp(`indexManagement`);
+ await indexManagement.changeTabs('indicesTab');
+ await header.waitUntilLoadingHasFinished();
+ await indexManagement.expectToBeOnIndicesManagement();
+ });
+ },
+ };
+}
diff --git a/x-pack/test/functional/page_objects/search_start.ts b/x-pack/test/functional/page_objects/search_start.ts
new file mode 100644
index 0000000000000..7d73c17a175a6
--- /dev/null
+++ b/x-pack/test/functional/page_objects/search_start.ts
@@ -0,0 +1,114 @@
+/*
+ * 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 SearchStartProvider({ getService }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+ const browser = getService('browser');
+ const retry = getService('retry');
+
+ return {
+ async expectToBeOnStartPage() {
+ expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/start');
+ await testSubjects.existOrFail('elasticsearchStartPage', { timeout: 2000 });
+ },
+ async expectToBeOnIndexDetailsPage() {
+ await retry.tryForTime(60 * 1000, async () => {
+ expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/indices/index_details');
+ });
+ },
+ async expectToBeOnIndexListPage() {
+ await retry.tryForTime(60 * 1000, async () => {
+ expect(await browser.getCurrentUrl()).contain(
+ '/app/management/data/index_management/indices'
+ );
+ });
+ },
+ async expectToBeOnMLFileUploadPage() {
+ await retry.tryForTime(60 * 1000, async () => {
+ expect(await browser.getCurrentUrl()).contain('/app/ml/filedatavisualizer');
+ });
+ },
+ async expectIndexNameToExist() {
+ await testSubjects.existOrFail('indexNameField');
+ },
+ async setIndexNameValue(value: string) {
+ await testSubjects.existOrFail('indexNameField');
+ await testSubjects.setValue('indexNameField', value);
+ },
+ async expectCloseCreateIndexButtonExists() {
+ await testSubjects.existOrFail('closeCreateIndex');
+ },
+ async clickCloseCreateIndexButton() {
+ await testSubjects.existOrFail('closeCreateIndex');
+ await testSubjects.click('closeCreateIndex');
+ },
+ async expectSkipButtonExists() {
+ await testSubjects.existOrFail('createIndexSkipBtn');
+ },
+ async clickSkipButton() {
+ await testSubjects.existOrFail('createIndexSkipBtn');
+ await testSubjects.click('createIndexSkipBtn');
+ },
+ async expectCreateIndexButtonToExist() {
+ await testSubjects.existOrFail('createIndexBtn');
+ },
+ async expectCreateIndexButtonToBeEnabled() {
+ await testSubjects.existOrFail('createIndexBtn');
+ expect(await testSubjects.isEnabled('createIndexBtn')).equal(true);
+ },
+ async expectCreateIndexButtonToBeDisabled() {
+ await testSubjects.existOrFail('createIndexBtn');
+ expect(await testSubjects.isEnabled('createIndexBtn')).equal(false);
+ },
+ async clickCreateIndexButton() {
+ await testSubjects.existOrFail('createIndexBtn');
+ expect(await testSubjects.isEnabled('createIndexBtn')).equal(true);
+ await testSubjects.click('createIndexBtn');
+ },
+ async expectCreateIndexCodeView() {
+ await testSubjects.existOrFail('createIndexCodeView');
+ },
+ async expectCreateIndexUIView() {
+ await testSubjects.existOrFail('createIndexUIView');
+ },
+ async clickUIViewButton() {
+ await testSubjects.existOrFail('createIndexUIViewBtn');
+ await testSubjects.click('createIndexUIViewBtn');
+ },
+ async clickCodeViewButton() {
+ await testSubjects.existOrFail('createIndexCodeViewBtn');
+ await testSubjects.click('createIndexCodeViewBtn');
+ },
+ async clickFileUploadLink() {
+ await testSubjects.existOrFail('uploadFileLink');
+ await testSubjects.click('uploadFileLink');
+ },
+
+ async expectAPIKeyVisibleInCodeBlock(apiKey: string) {
+ await testSubjects.existOrFail('createIndex-code-block');
+ await retry.try(async () => {
+ expect(await testSubjects.getVisibleText('createIndex-code-block')).to.contain(apiKey);
+ });
+ },
+
+ async expectAPIKeyPreGenerated() {
+ await testSubjects.existOrFail('apiKeyHasBeenGenerated');
+ },
+
+ async expectAPIKeyNotPreGenerated() {
+ await testSubjects.existOrFail('apiKeyHasNotBeenGenerated');
+ },
+
+ async expectAPIKeyFormNotAvailable() {
+ await testSubjects.missingOrFail('apiKeyHasNotBeenGenerated');
+ await testSubjects.missingOrFail('apiKeyHasBeenGenerated');
+ },
+ };
+}
diff --git a/x-pack/test/functional_search/index.ts b/x-pack/test/functional_search/index.ts
index d48bd1d695d16..14c87b82c825d 100644
--- a/x-pack/test/functional_search/index.ts
+++ b/x-pack/test/functional_search/index.ts
@@ -12,5 +12,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
describe('Search solution tests', function () {
loadTestFile(require.resolve('./tests/classic_navigation'));
loadTestFile(require.resolve('./tests/solution_navigation'));
+ loadTestFile(require.resolve('./tests/search_start'));
+ loadTestFile(require.resolve('./tests/search_index_detail'));
});
};
diff --git a/x-pack/test/functional_search/tests/classic_navigation.ts b/x-pack/test/functional_search/tests/classic_navigation.ts
index 118fb4b7b2a4f..a6cd090bcc661 100644
--- a/x-pack/test/functional_search/tests/classic_navigation.ts
+++ b/x-pack/test/functional_search/tests/classic_navigation.ts
@@ -11,7 +11,11 @@ export default function searchSolutionNavigation({
getPageObjects,
getService,
}: FtrProviderContext) {
- const { common, searchClassicNavigation } = getPageObjects(['common', 'searchClassicNavigation']);
+ const { common, searchClassicNavigation, indexManagement } = getPageObjects([
+ 'common',
+ 'searchClassicNavigation',
+ 'indexManagement',
+ ]);
const spaces = getService('spaces');
const browser = getService('browser');
@@ -43,7 +47,7 @@ export default function searchSolutionNavigation({
await searchClassicNavigation.expectAllNavItems([
{ id: 'Home', label: 'Home' },
{ id: 'Content', label: 'Content' },
- { id: 'Indices', label: 'Indices' },
+ { id: 'Indices', label: 'Index Management' },
{ id: 'Connectors', label: 'Connectors' },
{ id: 'Crawlers', label: 'Web Crawlers' },
{ id: 'Build', label: 'Build' },
@@ -64,12 +68,6 @@ export default function searchSolutionNavigation({
await searchClassicNavigation.expectNavItemExists('Home');
- // Check Content
- // > Indices
- await searchClassicNavigation.clickNavItem('Indices');
- await searchClassicNavigation.expectNavItemActive('Indices');
- await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Content');
- await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Elasticsearch indices');
// > Connectors
await searchClassicNavigation.clickNavItem('Connectors');
await searchClassicNavigation.expectNavItemActive('Connectors');
@@ -130,5 +128,10 @@ export default function searchSolutionNavigation({
await expectNoPageReload();
});
+
+ it("should redirect to index management when clicking on 'Indices'", async () => {
+ await searchClassicNavigation.clickNavItem('Indices');
+ await indexManagement.expectToBeOnIndicesManagement();
+ });
});
}
diff --git a/x-pack/test/functional_search/tests/embedded_console.ts b/x-pack/test/functional_search/tests/embedded_console.ts
new file mode 100644
index 0000000000000..04153d4a39ee6
--- /dev/null
+++ b/x-pack/test/functional_search/tests/embedded_console.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 { FtrProviderContext } from '../ftr_provider_context';
+
+type PageObjects = Pick, 'embeddedConsole'>;
+
+export async function testHasEmbeddedConsole(pageObjects: PageObjects) {
+ await pageObjects.embeddedConsole.expectEmbeddedConsoleControlBarExists();
+ await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
+ await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
+ await pageObjects.embeddedConsole.expectEmbeddedConsoleHaveFullscreenToggle();
+ await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen();
+ await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
+ await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed();
+}
diff --git a/x-pack/test/functional_search/tests/search_index_detail.ts b/x-pack/test/functional_search/tests/search_index_detail.ts
new file mode 100644
index 0000000000000..b725e4376b25f
--- /dev/null
+++ b/x-pack/test/functional_search/tests/search_index_detail.ts
@@ -0,0 +1,317 @@
+/*
+ * 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 { testHasEmbeddedConsole } from './embedded_console';
+
+export default function ({ getPageObjects, getService }: FtrProviderContext) {
+ const {
+ searchIndexDetail,
+ common,
+ searchApiKeys,
+ searchNavigation,
+ indexManagement,
+ embeddedConsole,
+ } = getPageObjects([
+ 'header',
+ 'searchIndexDetail',
+ 'common',
+ 'searchApiKeys',
+ 'searchNavigation',
+ 'indexManagement',
+ 'embeddedConsole',
+ ]);
+ const es = getService('es');
+ const security = getService('security');
+ const browser = getService('browser');
+ const retry = getService('retry');
+ const spaces = getService('spaces');
+
+ const deleteAllIndices = getService('esDeleteAllIndices');
+ const indexName = 'test-my-index';
+
+ describe('index details page - search solution', function () {
+ describe('developer rights', function () {
+ let cleanUp: () => Promise;
+ let spaceCreated: { id: string } = { id: '' };
+
+ before(async () => {
+ // Navigate to the spaces management page which will log us in Kibana
+ await common.navigateToUrl('management', 'kibana/spaces', {
+ shouldUseHashForSubUrl: false,
+ });
+
+ // Create a space with the search solution and navigate to its home page
+ ({ cleanUp, space: spaceCreated } = await spaces.create({
+ name: 'search-ftr',
+ solution: 'es',
+ }));
+ await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
+ });
+
+ after(async () => {
+ // Clean up space created
+ await cleanUp();
+ await deleteAllIndices(indexName);
+ });
+
+ describe('search index details page', () => {
+ before(async () => {
+ await es.indices.create({ index: indexName });
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ });
+ after(async () => {
+ await deleteAllIndices(indexName);
+ });
+ it('can load index detail page', async () => {
+ await searchIndexDetail.expectIndexDetailPageHeader();
+ await searchIndexDetail.expectSearchIndexDetailsTabsExists();
+ await searchIndexDetail.expectAPIReferenceDocLinkExists();
+ await searchIndexDetail.expectAPIReferenceDocLinkMissingInMoreOptions();
+ });
+
+ it('should have connection details', async () => {
+ await searchIndexDetail.expectConnectionDetails();
+ });
+
+ describe('check code example texts', () => {
+ const indexNameCodeExample = 'test-my-index2';
+ before(async () => {
+ await es.indices.create({ index: indexNameCodeExample });
+ await searchNavigation.navigateToIndexDetailPage(indexNameCodeExample);
+ });
+
+ after(async () => {
+ await deleteAllIndices(indexNameCodeExample);
+ });
+
+ it('should have embedded dev console', async () => {
+ await testHasEmbeddedConsole({ embeddedConsole });
+ });
+
+ it('should have basic example texts', async () => {
+ await searchIndexDetail.expectHasSampleDocuments();
+ });
+
+ it('should have other example texts when mapping changed', async () => {
+ await es.indices.putMapping({
+ index: indexNameCodeExample,
+ properties: {
+ text: { type: 'text' },
+ number: { type: 'integer' },
+ },
+ });
+ await searchIndexDetail.expectSampleDocumentsWithCustomMappings();
+ });
+ });
+
+ describe('API key details', () => {
+ it('should show api key', async () => {
+ await searchApiKeys.deleteAPIKeys();
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ // sometimes the API key exists in the cluster and its lost in sessionStorage
+ // if fails we retry to delete the API key and refresh the browser
+ await retry.try(
+ async () => {
+ await searchApiKeys.expectAPIKeyExists();
+ },
+ async () => {
+ await searchApiKeys.deleteAPIKeys();
+ await browser.refresh();
+ }
+ );
+ await searchApiKeys.expectAPIKeyAvailable();
+ const apiKey = await searchApiKeys.getAPIKeyFromUI();
+ await searchIndexDetail.expectAPIKeyToBeVisibleInCodeBlock(apiKey);
+ });
+ });
+
+ it('should have quick stats', async () => {
+ await searchIndexDetail.expectQuickStats();
+ await searchIndexDetail.expectQuickStatsAIMappings();
+ await es.indices.putMapping({
+ index: indexName,
+ body: {
+ properties: {
+ my_field: {
+ type: 'dense_vector',
+ dims: 3,
+ },
+ },
+ },
+ });
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ await searchIndexDetail.expectQuickStatsAIMappingsToHaveVectorFields();
+ });
+
+ it('should show code examples for adding documents', async () => {
+ await searchIndexDetail.expectAddDocumentCodeExamples();
+ await searchIndexDetail.expectSelectedLanguage('python');
+ await searchIndexDetail.codeSampleContainsValue('installCodeExample', 'pip install');
+ await searchIndexDetail.selectCodingLanguage('javascript');
+ await searchIndexDetail.codeSampleContainsValue('installCodeExample', 'npm install');
+ await searchIndexDetail.selectCodingLanguage('curl');
+ await searchIndexDetail.openConsoleCodeExample();
+ await embeddedConsole.expectEmbeddedConsoleToBeOpen();
+ await embeddedConsole.clickEmbeddedConsoleControlBar();
+ });
+
+ describe('With data', () => {
+ before(async () => {
+ await es.index({
+ index: indexName,
+ refresh: true,
+ body: {
+ my_field: [1, 0, 1],
+ },
+ });
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ });
+ it('should have index documents', async () => {
+ await searchIndexDetail.expectHasIndexDocuments();
+ });
+ it('menu action item should be replaced with playground', async () => {
+ await searchIndexDetail.expectActionItemReplacedWhenHasDocs();
+ });
+ it('should have link to API reference doc link in options menu', async () => {
+ await searchIndexDetail.clickMoreOptionsActionsButton();
+ await searchIndexDetail.expectAPIReferenceDocLinkExistsInMoreOptions();
+ });
+ it('should have one document in quick stats', async () => {
+ await searchIndexDetail.expectQuickStatsToHaveDocumentCount(1);
+ });
+ it('should have with data tabs', async () => {
+ await searchIndexDetail.expectTabsExists();
+ await searchIndexDetail.expectUrlShouldChangeTo('data');
+ });
+ it('should be able to change tabs to mappings and mappings is shown', async () => {
+ await searchIndexDetail.changeTab('mappingsTab');
+ await searchIndexDetail.expectUrlShouldChangeTo('mappings');
+ await searchIndexDetail.expectMappingsComponentIsVisible();
+ });
+ it('should be able to change tabs to settings and settings is shown', async () => {
+ await searchIndexDetail.changeTab('settingsTab');
+ await searchIndexDetail.expectUrlShouldChangeTo('settings');
+ await searchIndexDetail.expectSettingsComponentIsVisible();
+ });
+ it('should be able to delete document', async () => {
+ await searchIndexDetail.changeTab('dataTab');
+ await searchIndexDetail.clickFirstDocumentDeleteAction();
+ await searchIndexDetail.expectAddDocumentCodeExamples();
+ await searchIndexDetail.expectQuickStatsToHaveDocumentCount(0);
+ });
+ });
+ describe('has index actions enabled', () => {
+ before(async () => {
+ await es.index({
+ index: indexName,
+ body: {
+ my_field: [1, 0, 1],
+ },
+ });
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ });
+
+ beforeEach(async () => {
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ });
+
+ it('delete document button is enabled', async () => {
+ await searchIndexDetail.expectDeleteDocumentActionToBeEnabled();
+ });
+ it('add field button is enabled', async () => {
+ await searchIndexDetail.changeTab('mappingsTab');
+ await searchIndexDetail.expectAddFieldToBeEnabled();
+ });
+ it('edit settings button is enabled', async () => {
+ await searchIndexDetail.changeTab('settingsTab');
+ await searchIndexDetail.expectEditSettingsToBeEnabled();
+ });
+ it('delete index button is enabled', async () => {
+ await searchIndexDetail.expectMoreOptionsActionButtonExists();
+ await searchIndexDetail.clickMoreOptionsActionsButton();
+ await searchIndexDetail.expectMoreOptionsOverviewMenuIsShown();
+ await searchIndexDetail.expectDeleteIndexButtonExistsInMoreOptions();
+ await searchIndexDetail.expectDeleteIndexButtonToBeEnabled();
+ });
+ });
+
+ describe('page loading error', () => {
+ before(async () => {
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ await deleteAllIndices(indexName);
+ });
+ it('has page load error section', async () => {
+ await searchIndexDetail.expectPageLoadErrorExists();
+ await searchIndexDetail.expectIndexNotFoundErrorExists();
+ });
+ it('reload button shows details page again', async () => {
+ await es.indices.create({ index: indexName });
+ await searchIndexDetail.clickPageReload();
+ await searchIndexDetail.expectIndexDetailPageHeader();
+ });
+ });
+ describe('Index more options menu', () => {
+ before(async () => {
+ await searchNavigation.navigateToIndexDetailPage(indexName);
+ });
+ it('shows action menu in actions popover', async () => {
+ await searchIndexDetail.expectMoreOptionsActionButtonExists();
+ await searchIndexDetail.clickMoreOptionsActionsButton();
+ await searchIndexDetail.expectMoreOptionsOverviewMenuIsShown();
+ });
+ it('should delete index', async () => {
+ await searchIndexDetail.expectDeleteIndexButtonExistsInMoreOptions();
+ await searchIndexDetail.clickDeleteIndexButton();
+ await searchIndexDetail.clickConfirmingDeleteIndex();
+ });
+ });
+ });
+ describe.skip('index management index list page', () => {
+ before(async () => {
+ await es.indices.create({ index: indexName });
+ await security.testUser.setRoles(['index_management_user']);
+ });
+ beforeEach(async () => {
+ await searchNavigation.navigateToIndexManagementPage();
+ });
+ after(async () => {
+ await deleteAllIndices(indexName);
+ });
+ describe('manage index action', () => {
+ beforeEach(async () => {
+ await indexManagement.manageIndex(indexName);
+ await indexManagement.manageIndexContextMenuExists();
+ });
+ it('navigates to overview tab', async () => {
+ await indexManagement.changeManageIndexTab('showOverviewIndexMenuButton');
+ await searchIndexDetail.expectIndexDetailPageHeader();
+ await searchIndexDetail.expectUrlShouldChangeTo('data');
+ });
+
+ it('navigates to settings tab', async () => {
+ await indexManagement.changeManageIndexTab('showSettingsIndexMenuButton');
+ await searchIndexDetail.expectIndexDetailPageHeader();
+ await searchIndexDetail.expectUrlShouldChangeTo('settings');
+ });
+ it('navigates to mappings tab', async () => {
+ await indexManagement.changeManageIndexTab('showMappingsIndexMenuButton');
+ await searchIndexDetail.expectIndexDetailPageHeader();
+ await searchIndexDetail.expectUrlShouldChangeTo('mappings');
+ });
+ });
+ describe('can view search index details', function () {
+ it('renders search index details with no documents', async () => {
+ await searchIndexDetail.openIndicesDetailFromIndexManagementIndicesListTable(0);
+ await searchIndexDetail.expectIndexDetailPageHeader();
+ await searchIndexDetail.expectSearchIndexDetailsTabsExists();
+ await searchIndexDetail.expectAPIReferenceDocLinkExists();
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/x-pack/test/functional_search/tests/search_start.ts b/x-pack/test/functional_search/tests/search_start.ts
new file mode 100644
index 0000000000000..b96ea0230e24c
--- /dev/null
+++ b/x-pack/test/functional_search/tests/search_start.ts
@@ -0,0 +1,198 @@
+/*
+ * 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';
+import { testHasEmbeddedConsole } from './embedded_console';
+
+export default function ({ getPageObjects, getService }: FtrProviderContext) {
+ const { common, searchApiKeys, searchStart, searchNavigation, embeddedConsole } = getPageObjects([
+ 'searchStart',
+ 'common',
+ 'searchApiKeys',
+ 'searchNavigation',
+ 'embeddedConsole',
+ ]);
+ const esDeleteAllIndices = getService('esDeleteAllIndices');
+ const es = getService('es');
+ const browser = getService('browser');
+ const retry = getService('retry');
+ const spaces = getService('spaces');
+
+ const deleteAllTestIndices = async () => {
+ await esDeleteAllIndices(['search-*', 'test-*']);
+ };
+
+ describe('Elasticsearch Start [Onboarding Empty State]', function () {
+ let cleanUp: () => Promise;
+ let spaceCreated: { id: string } = { id: '' };
+
+ before(async () => {
+ // Navigate to the spaces management page which will log us in Kibana
+ await common.navigateToUrl('management', 'kibana/spaces', {
+ shouldUseHashForSubUrl: false,
+ });
+
+ // Create a space with the search solution and navigate to its home page
+ ({ cleanUp, space: spaceCreated } = await spaces.create({
+ name: 'search-ftr',
+ solution: 'es',
+ }));
+ await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
+ });
+
+ after(async () => {
+ // Clean up space created
+ await cleanUp();
+ await deleteAllTestIndices();
+ });
+
+ describe('Developer rights', function () {
+ beforeEach(async () => {
+ await deleteAllTestIndices();
+ await searchApiKeys.deleteAPIKeys();
+ await searchNavigation.navigateToElasticsearchStartPage();
+ });
+
+ it('should have embedded dev console', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await testHasEmbeddedConsole({ embeddedConsole });
+ });
+
+ it('should support index creation flow with UI', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await searchStart.expectCreateIndexUIView();
+ await searchStart.expectCreateIndexButtonToBeEnabled();
+ await searchStart.clickCreateIndexButton();
+ await searchStart.expectToBeOnIndexDetailsPage();
+ });
+
+ it('should support setting index name', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await searchStart.expectIndexNameToExist();
+ await searchStart.setIndexNameValue('INVALID_INDEX');
+ await searchStart.expectCreateIndexButtonToBeDisabled();
+ await searchStart.setIndexNameValue('test-index-name');
+ await searchStart.expectCreateIndexButtonToBeEnabled();
+ await searchStart.clickCreateIndexButton();
+ await searchStart.expectToBeOnIndexDetailsPage();
+ });
+
+ it('should redirect to index details when index is created via API', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await es.indices.create({ index: 'test-my-index' });
+ await searchStart.expectToBeOnIndexDetailsPage();
+ });
+
+ it('should redirect to index list when multiple indices are created via API', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await es.indices.create({ index: 'test-my-index-001' });
+ await es.indices.create({ index: 'test-my-index-002' });
+ await searchStart.expectToBeOnIndexListPage();
+ });
+ it('should redirect to indices list if single index exist on page load', async () => {
+ await searchNavigation.navigateToElasticsearchStartPage();
+ await es.indices.create({ index: 'test-my-index-001' });
+ await searchNavigation.navigateToElasticsearchStartPage(true);
+ await searchStart.expectToBeOnIndexListPage();
+ });
+
+ it('should support switching between UI and Code Views', async () => {
+ await searchNavigation.navigateToElasticsearchStartPage();
+ await searchStart.expectCreateIndexUIView();
+ await searchStart.clickCodeViewButton();
+ await searchStart.expectCreateIndexCodeView();
+ await searchStart.clickUIViewButton();
+ await searchStart.expectCreateIndexUIView();
+ });
+
+ it('should show the api key in code view', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await searchStart.clickCodeViewButton();
+ // sometimes the API key exists in the cluster and its lost in sessionStorage
+ // if fails we retry to delete the API key and refresh the browser
+ await retry.try(
+ async () => {
+ await searchApiKeys.expectAPIKeyExists();
+ },
+ async () => {
+ await searchApiKeys.deleteAPIKeys();
+ await browser.refresh();
+ await searchStart.clickCodeViewButton();
+ }
+ );
+ await searchApiKeys.expectAPIKeyAvailable();
+
+ const apiKeyUI = await searchApiKeys.getAPIKeyFromUI();
+ const apiKeySession = await searchApiKeys.getAPIKeyFromSessionStorage();
+
+ expect(apiKeyUI).to.eql(apiKeySession.encoded);
+
+ // check that when browser is refreshed, the api key is still available
+ await browser.refresh();
+ await searchStart.clickCodeViewButton();
+ await searchApiKeys.expectAPIKeyAvailable();
+ await searchStart.expectAPIKeyPreGenerated();
+ const refreshBrowserApiKeyUI = await searchApiKeys.getAPIKeyFromUI();
+ expect(refreshBrowserApiKeyUI).to.eql(apiKeyUI);
+
+ // check that when api key is invalidated, a new one is generated
+ await searchApiKeys.invalidateAPIKey(apiKeySession.id);
+ await browser.refresh();
+ await searchStart.clickCodeViewButton();
+ await searchApiKeys.expectAPIKeyAvailable();
+ const newApiKeyUI = await searchApiKeys.getAPIKeyFromUI();
+ expect(newApiKeyUI).to.not.eql(apiKeyUI);
+ await searchStart.expectAPIKeyVisibleInCodeBlock(newApiKeyUI);
+ });
+
+ it('should explicitly ask to create api key when project already has an apikey', async () => {
+ await searchApiKeys.clearAPIKeySessionStorage();
+ await searchApiKeys.createAPIKey();
+ await searchStart.expectToBeOnStartPage();
+ await searchStart.clickCodeViewButton();
+ await searchApiKeys.createApiKeyFromFlyout();
+ await searchApiKeys.expectAPIKeyAvailable();
+ });
+
+ it('Same API Key should be present on start page and index detail view', async () => {
+ await searchStart.clickCodeViewButton();
+ await searchApiKeys.expectAPIKeyAvailable();
+ const apiKeyUI = await searchApiKeys.getAPIKeyFromUI();
+
+ await searchStart.clickUIViewButton();
+ await searchStart.clickCreateIndexButton();
+ await searchStart.expectToBeOnIndexDetailsPage();
+
+ await searchApiKeys.expectAPIKeyAvailable();
+ const indexDetailsApiKey = await searchApiKeys.getAPIKeyFromUI();
+
+ expect(apiKeyUI).to.eql(indexDetailsApiKey);
+ });
+
+ it('should have file upload link', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await searchStart.clickFileUploadLink();
+ await searchStart.expectToBeOnMLFileUploadPage();
+ });
+
+ it('should have close button', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await searchStart.expectCloseCreateIndexButtonExists();
+ await searchStart.clickCloseCreateIndexButton();
+ await searchStart.expectToBeOnIndexListPage();
+ });
+
+ it('should have skip button', async () => {
+ await searchStart.expectToBeOnStartPage();
+ await searchStart.expectSkipButtonExists();
+ await searchStart.clickSkipButton();
+ await searchStart.expectToBeOnIndexListPage();
+ });
+ });
+ });
+}
diff --git a/x-pack/test/functional_search/tests/solution_navigation.ts b/x-pack/test/functional_search/tests/solution_navigation.ts
index 5517a9513ea48..63ff2462bf778 100644
--- a/x-pack/test/functional_search/tests/solution_navigation.ts
+++ b/x-pack/test/functional_search/tests/solution_navigation.ts
@@ -11,7 +11,11 @@ export default function searchSolutionNavigation({
getPageObjects,
getService,
}: FtrProviderContext) {
- const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']);
+ const { common, solutionNavigation, indexManagement } = getPageObjects([
+ 'common',
+ 'indexManagement',
+ 'solutionNavigation',
+ ]);
const spaces = getService('spaces');
const browser = getService('browser');
@@ -107,15 +111,16 @@ export default function searchSolutionNavigation({
// check the Content
// > Indices section
await solutionNavigation.sidenav.clickLink({
- deepLinkId: 'enterpriseSearchContent:searchIndices',
+ deepLinkId: 'management:index_management',
});
await solutionNavigation.sidenav.expectLinkActive({
- deepLinkId: 'enterpriseSearchContent:searchIndices',
+ deepLinkId: 'management:index_management',
});
- await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Content' });
- await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' });
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Deployment' });
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' });
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
- deepLinkId: 'enterpriseSearchContent:searchIndices',
+ text: 'Indices',
});
// > Connectors
await solutionNavigation.sidenav.clickLink({
@@ -236,6 +241,13 @@ export default function searchSolutionNavigation({
await expectNoPageReload();
});
+ it("should redirect to index management when clicking on 'Indices'", async () => {
+ await solutionNavigation.sidenav.clickLink({
+ deepLinkId: 'management:index_management',
+ });
+ await indexManagement.expectToBeOnIndicesManagement();
+ });
+
it('renders only expected items', async () => {
await solutionNavigation.sidenav.openSection('search_project_nav.otherTools');
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
@@ -250,7 +262,7 @@ export default function searchSolutionNavigation({
'discover',
'dashboards',
'content',
- 'enterpriseSearchContent:searchIndices',
+ 'management:index_management',
'enterpriseSearchContent:connectors',
'enterpriseSearchContent:webCrawlers',
'build',
diff --git a/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts b/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts
index f90ea3e7b705f..743f1cad451e6 100644
--- a/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts
+++ b/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts
@@ -50,14 +50,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
// check the Content > Indices section
await solutionNavigation.sidenav.clickLink({
- deepLinkId: 'enterpriseSearchContent:searchIndices',
+ deepLinkId: 'management:index_management',
});
await solutionNavigation.sidenav.expectLinkActive({
- deepLinkId: 'enterpriseSearchContent:searchIndices',
+ deepLinkId: 'management:index_management',
});
- await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' });
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Deployment' });
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' });
+ await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
- deepLinkId: 'enterpriseSearchContent:searchIndices',
+ text: 'Indices',
});
// navigate to a different section
diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts
index 4e9401cc41481..cf2a953d0b749 100644
--- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts
+++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts
@@ -88,6 +88,8 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
'enterpriseSearchSemanticSearch',
'enterpriseSearchElasticsearch',
'searchPlayground',
+ 'elasticsearchIndices',
+ 'elasticsearchStart',
'searchInferenceEndpoints',
'searchSynonyms',
'appSearch',