From adc9ab9c52602d5550e2cdb526c7a717fa2811af Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 28 Sep 2023 11:49:13 +0200 Subject: [PATCH 1/9] [ML] AIOps: Enable `event_generating_elements_should_be_instrumented` eslint rule. (#167317) Implements #153108. This enables the `@kbn/telemetry/event_generating_elements_should_be_instrumented` eslint rule for the `aiops` plugin to enforce `data-test-subj` attributes on actionable EUI components so they are auto-instrumented by telemetry. The ids were first auto-created using `node scripts/eslint --fix x-pack/plugins/aiops` and then adapted. --- .eslintrc.js | 1 + .../components/change_point_detection/fields_config.tsx | 3 +++ .../change_point_detection/max_series_control.tsx | 1 + .../log_categorization/category_table/table_header.tsx | 2 ++ .../log_categorization/loading_categorization.tsx | 7 ++++++- .../log_categorization/log_categorization_page.tsx | 7 ++++++- .../sampling_menu/random_sampler_range_slider.tsx | 1 + .../log_categorization/sampling_menu/sampling_menu.tsx | 1 + .../log_rate_analysis/log_rate_analysis_results.tsx | 6 +++++- 9 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 6657ba3cb1f01..4e46336ec70ae 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -908,6 +908,7 @@ module.exports = { }, { files: [ + 'x-pack/plugins/aiops/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/apm/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/exploratory_view/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/infra/**/*.{js,mjs,ts,tsx}', diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx index 45b2ccc1c097a..c07af22a5f16a 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx @@ -356,6 +356,7 @@ const FieldPanel: FC = ({ = ({ !prevState)} aria-label={i18n.translate('xpack.aiops.changePointDetection.expandConfigLabel', { @@ -480,6 +482,7 @@ const FieldPanel: FC = ({ id={`panelContextMenu_${panelIndex}`} button={ = ({ <> openInDiscover(QUERY_MODE.INCLUDE)} iconType="plusInCircle" @@ -61,6 +62,7 @@ export const TableHeader: FC = ({ openInDiscover(QUERY_MODE.EXCLUDE)} iconType="minusInCircle" diff --git a/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx b/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx index 77ba11fd46cc5..208302083fd0c 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx @@ -43,7 +43,12 @@ export const LoadingCategorization: FC = ({ onClose }) => ( - onClose()}>Cancel + onClose()} + > + Cancel + diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index 5ccdca64d1036..bfa609bb5dd21 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -324,7 +324,12 @@ export const LogCategorizationPage: FC = () => { /> ) : ( - cancelRequest()}>Cancel + cancelRequest()} + > + Cancel + )} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx index 4f2b62729ca46..2cd70b35d139e 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx @@ -92,6 +92,7 @@ export const RandomSamplerRangeSlider = ({ data-test-subj="dvRandomSamplerProbabilityRange" append={ { if (setSamplingProbability && isDefined(samplingProbabilityInput)) { diff --git a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx index e4d18f6dbc260..c4e16d4fabbe6 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx @@ -118,6 +118,7 @@ export const SamplingMenu: FC = ({ randomSampler, reload }) => { id="aiopsSamplingOptions" button={ setShowSamplingOptionsPopover(!showSamplingOptionsPopover)} iconSide="right" iconType="arrowDown" diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index f1b1a6d38de12..a62db054d4d52 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -409,7 +409,11 @@ export const LogRateAnalysisResults: FC = ({ )} {overrides !== undefined ? (

- startHandler(true)}> + startHandler(true)} + > Date: Thu, 28 Sep 2023 11:54:36 +0200 Subject: [PATCH 2/9] Change GPCTL dashboard link (#167466) --- .github/workflows/create-deploy-tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-deploy-tag.yml b/.github/workflows/create-deploy-tag.yml index abe2e131165ec..85e226d384cc2 100644 --- a/.github/workflows/create-deploy-tag.yml +++ b/.github/workflows/create-deploy-tag.yml @@ -102,7 +102,7 @@ jobs: "", "", " (use Elastic Cloud Staging VPN)", - "", + "", "" ] - name: Post Slack failure message From c48cc24617df8ebcf78b1d4847bdcbca11e974e8 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 28 Sep 2023 12:06:00 +0200 Subject: [PATCH 3/9] [kbn/journeys] fixes to run journeys against ESS cluster (#166923) ## Summary I had to change `waitForRender` since `page.waitForFunction` tries to run a script on page and it is not working due to CSP settings on Cloud. Instead of injecting a script, we use a classical API to find elements/attributes in the DOM. Since `PUT /internal/core/_settings` is merged in 8.11.0, journeys run on Cloud with on-fly labels update is supported starting deployments 8.11.0+. I added error message for 404 code just in case someone runs it on earlier version. `many_fields_discover` journey was update since on Cloud the data view used by scenario is not selected by default. How it works: Create a deployment with QAF and re-configure it for journey run: ``` export EC_DEPLOYMENT_NAME=my-run-8.11 qaf elastic-cloud deployments create --stack-version 8.11.0-SNAPSHOT --environment staging --region gcp-us-central1 qaf elastic-cloud deployments configure-for-performance-journeys ``` Run any journey, e.g. many_fields_discover ``` TEST_CLOUD=1 TEST_ES_URL=https://username:pswd@es_url:443 TEST_KIBANA_URL=https://username:pswd@kibana-ur_url node scripts/functional_test_runner --config x-pack/performance/journeys/many_fields_discover.ts ``` You should see a log about labels being updated: ``` Updating telemetry & APM labels: {"testJobId":"local-a3272047-6724-44d1-9a61-5c79781b06a1","testBuildId":"local-d8edbace-f441-4ba9-ac83-5909be3acf2a","journeyName":"many_fields_discover","ftrConfig":"x-pack/performance/journeys/many_fields_discover.ts"} ``` And then able to find APM logs for the journey in [Ops](https://kibana-ops-e2e-perf.kb.us-central1.gcp.cloud.es.io:9243/app/apm/services?comparisonEnabled=true&environment=ENVIRONMENT_ALL&kuery=labels.testJobId%20%3A%20%22local-d79a878c-cc7a-423b-b884-c9b6b1a8d781%22&latencyAggregationType=avg&offset=1d&rangeFrom=now-24h%2Fh&rangeTo=now&serviceGroup=&transactionType=request) cluster --- .../running_performance_journey_in_cloud.mdx | 29 +++++++++-- packages/kbn-journeys/journey/journey.ts | 15 ++---- .../journey/journey_ftr_config.ts | 4 +- .../journey/journey_ftr_harness.ts | 36 +++++++------ packages/kbn-journeys/services/auth.ts | 14 ++---- packages/kbn-journeys/services/es.ts | 20 ++++++++ .../services/ftr_context_provider.ts | 13 +++++ packages/kbn-journeys/services/index.ts | 28 +++++++++++ packages/kbn-journeys/services/page/index.ts | 5 +- .../kbn-journeys/services/page/kibana_page.ts | 50 +++++++++++-------- .../journeys/many_fields_discover.ts | 6 ++- 11 files changed, 159 insertions(+), 61 deletions(-) create mode 100644 packages/kbn-journeys/services/es.ts create mode 100644 packages/kbn-journeys/services/ftr_context_provider.ts create mode 100644 packages/kbn-journeys/services/index.ts diff --git a/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx b/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx index 3f8b373afad39..6ab160ac93328 100644 --- a/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx +++ b/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx @@ -11,7 +11,7 @@ tags: ['kibana', 'onboarding', 'setup', 'performance', 'development', 'telemetry As a way to better understand user experience with Kibana in cloud, we support running performance journeys against Cloud deployments. The process takes a few steps: -- Create a cloud deployment +- Create a cloud deployment (8.11.0+ is supported) - Re-configure deployment with APM enabled and reporting metrics to the monitoring cluster - Create a user with `superuser` role to run tests with - Checkout the branch that matches your cloud deployment version @@ -35,7 +35,7 @@ Navigate to `Advanced Edit` page and change `Deployment Configuration` by adding ``` "user_settings_override_json": { "tracing.apm.enabled": "true", - "tracing.apm.environment": "development", + "tracing.apm.agent.environment": "development", "tracing.apm.agent.service_name": "elasticsearch", "tracing.apm.agent.server_url": "", "tracing.apm.agent.metrics_interval": "120s", @@ -50,6 +50,7 @@ Navigate to `Advanced Edit` page and change `Deployment Configuration` by adding ``` "user_settings_override_json": { + "coreApp.allowDynamicConfigOverrides": true, "elastic.apm.active": true, "elastic.apm.breakdownMetrics": false, "elastic.apm.captureBody": "all", @@ -74,8 +75,28 @@ Note: DEPLOYMENT_ID and YOUR_JOURNEY_NAME values are optional labels to find the Save changes and make sure cluster is restarted successfully. +### Use QAF to prepare the deployment +The quickest way to prepare ESS deployment is to use [QAF](https://github.com/elastic/qaf): + +- Make sure to add `~/.elastic/cloud.json` and ~/.elastic/cloud-admin.json with Cloud API (to create deployment) & Cloud Admin API (to modify it) keys +``` +{ + "api_key": { + "production": "", + "staging": "", + "qa": "" + } +} +``` +- Create deployment and modify it +``` +export EC_DEPLOYMENT_NAME=kibana-perf-8.11 +qaf elastic-cloud deployments create --stack-version 8.11.0-SNAPSHOT --environment staging --region gcp-us-central1 +qaf elastic-cloud deployments configure-for-performance-journeys +``` + ### Run the journey -Make sure you have created user with `superuser` role and the Kibana repo branch is matching your deployment version. +Make sure the Kibana repo branch is matching your deployment version. Set env variables to run FTR against your cloud deployment: ``` @@ -90,4 +111,6 @@ Run your journey with the command: node scripts/functional_test_runner.js --config x-pack/performance/journeys/$YOUR_JOURNEY_NAME.ts` ``` +APM & Telemetry labels will be updated on the fly and metrics/traces should be available in Telemetry Staging and kibana-ops-e2e-perf cluster. + diff --git a/packages/kbn-journeys/journey/journey.ts b/packages/kbn-journeys/journey/journey.ts index 7952b8ee7408d..bf3de796265f9 100644 --- a/packages/kbn-journeys/journey/journey.ts +++ b/packages/kbn-journeys/journey/journey.ts @@ -12,14 +12,9 @@ import { Page } from 'playwright'; import callsites from 'callsites'; import { ToolingLog } from '@kbn/tooling-log'; import { FtrConfigProvider } from '@kbn/test'; -import { - FtrProviderContext, - KibanaServer, - Es, - RetryService, -} from '@kbn/ftr-common-functional-services'; - -import { Auth } from '../services/auth'; +import { FtrProviderContext } from '../services/ftr_context_provider'; +import { Es, KibanaServer, Retry, Auth } from '../services'; + import { InputDelays } from '../services/input_delays'; import { KibanaUrl } from '../services/kibana_url'; @@ -37,7 +32,7 @@ export interface BaseStepCtx { kbnUrl: KibanaUrl; kibanaServer: KibanaServer; es: Es; - retry: RetryService; + retry: Retry; auth: Auth; } @@ -141,7 +136,7 @@ export class Journey { getService('kibanaServer'), getService('es'), getService('retry'), - new Auth(getService('config'), getService('log'), getService('kibanaServer')), + getService('auth'), this.config ).initMochaSuite(this.#steps); } diff --git a/packages/kbn-journeys/journey/journey_ftr_config.ts b/packages/kbn-journeys/journey/journey_ftr_config.ts index 1abc141c7bbae..b60f6fa191978 100644 --- a/packages/kbn-journeys/journey/journey_ftr_config.ts +++ b/packages/kbn-journeys/journey/journey_ftr_config.ts @@ -11,7 +11,7 @@ import Path from 'path'; import { v4 as uuidV4 } from 'uuid'; import { REPO_ROOT } from '@kbn/repo-info'; import type { FtrConfigProviderContext, FtrConfigProvider } from '@kbn/test'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { services } from '../services'; import { AnyStep } from './journey'; import { JourneyConfig } from './journey_config'; @@ -66,7 +66,7 @@ export function makeFtrConfigProvider( bail: true, }, - services: commonFunctionalServices, + services, pageObjects: {}, servicesRequiredForTestAnalysis: ['performance', 'journeyConfig'], diff --git a/packages/kbn-journeys/journey/journey_ftr_harness.ts b/packages/kbn-journeys/journey/journey_ftr_harness.ts index 9df84821de032..0f649e7a1de27 100644 --- a/packages/kbn-journeys/journey/journey_ftr_harness.ts +++ b/packages/kbn-journeys/journey/journey_ftr_harness.ts @@ -9,20 +9,19 @@ import Url from 'url'; import { inspect, format } from 'util'; import { setTimeout } from 'timers/promises'; - import * as Rx from 'rxjs'; import apmNode from 'elastic-apm-node'; import playwright, { ChromiumBrowser, Page, BrowserContext, CDPSession, Request } from 'playwright'; import { asyncMap, asyncForEach } from '@kbn/std'; import { ToolingLog } from '@kbn/tooling-log'; import { Config } from '@kbn/test'; -import { EsArchiver, KibanaServer, Es, RetryService } from '@kbn/ftr-common-functional-services'; import { ELASTIC_HTTP_VERSION_HEADER, X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; -import { Auth } from '../services/auth'; +import { AxiosError } from 'axios'; +import { Auth, Es, EsArchiver, KibanaServer, Retry } from '../services'; import { getInputDelays } from '../services/input_delays'; import { KibanaUrl } from '../services/kibana_url'; @@ -40,7 +39,7 @@ export class JourneyFtrHarness { private readonly esArchiver: EsArchiver, private readonly kibanaServer: KibanaServer, private readonly es: Es, - private readonly retry: RetryService, + private readonly retry: Retry, private readonly auth: Auth, private readonly journeyConfig: JourneyConfig ) { @@ -63,15 +62,24 @@ export class JourneyFtrHarness { private async updateTelemetryAndAPMLabels(labels: { [k: string]: string }) { this.log.info(`Updating telemetry & APM labels: ${JSON.stringify(labels)}`); - await this.kibanaServer.request({ - path: '/internal/core/_settings', - method: 'PUT', - headers: { - [ELASTIC_HTTP_VERSION_HEADER]: '1', - [X_ELASTIC_INTERNAL_ORIGIN_REQUEST]: 'ftr', - }, - body: { telemetry: { labels } }, - }); + try { + await this.kibanaServer.request({ + path: '/internal/core/_settings', + method: 'PUT', + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '1', + [X_ELASTIC_INTERNAL_ORIGIN_REQUEST]: 'ftr', + }, + body: { telemetry: { labels } }, + }); + } catch (error) { + const statusCode = (error as AxiosError).response?.status; + if (statusCode === 404) { + throw new Error( + `Failed to update labels, supported Kibana version is 8.11.0+ and must be started with "coreApp.allowDynamicConfigOverrides:true"` + ); + } else throw error; + } } private async setupApm() { @@ -385,7 +393,7 @@ export class JourneyFtrHarness { } const isServerlessProject = !!this.config.get('serverless'); - const kibanaPage = getNewPageObject(isServerlessProject, page, this.log); + const kibanaPage = getNewPageObject(isServerlessProject, page, this.log, this.retry); this.#_ctx = this.journeyConfig.getExtendedStepCtx({ kibanaPage, diff --git a/packages/kbn-journeys/services/auth.ts b/packages/kbn-journeys/services/auth.ts index 2a2a9719bd49b..1bc015d9f81cc 100644 --- a/packages/kbn-journeys/services/auth.ts +++ b/packages/kbn-journeys/services/auth.ts @@ -10,9 +10,7 @@ import Url from 'url'; import { format } from 'util'; import axios, { AxiosResponse } from 'axios'; -import { ToolingLog } from '@kbn/tooling-log'; -import { Config } from '@kbn/test'; -import { KibanaServer } from '@kbn/ftr-common-functional-services'; +import { FtrService } from './ftr_context_provider'; export interface Credentials { username: string; @@ -22,12 +20,10 @@ export interface Credentials { function extractCookieValue(authResponse: AxiosResponse) { return authResponse.headers['set-cookie']?.[0].toString().split(';')[0].split('sid=')[1] ?? ''; } -export class Auth { - constructor( - private readonly config: Config, - private readonly log: ToolingLog, - private readonly kibanaServer: KibanaServer - ) {} +export class AuthService extends FtrService { + private readonly config = this.ctx.getService('config'); + private readonly log = this.ctx.getService('log'); + private readonly kibanaServer = this.ctx.getService('kibanaServer'); public async login(credentials?: Credentials) { const baseUrl = new URL( diff --git a/packages/kbn-journeys/services/es.ts b/packages/kbn-journeys/services/es.ts new file mode 100644 index 0000000000000..16ca3079a2ab0 --- /dev/null +++ b/packages/kbn-journeys/services/es.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Client } from '@elastic/elasticsearch'; + +import { createEsClientForFtrConfig, ProvidedType } from '@kbn/test'; +import { FtrProviderContext } from './ftr_context_provider'; + +export function EsProvider({ getService }: FtrProviderContext): Client { + const config = getService('config'); + + return createEsClientForFtrConfig(config); +} + +export type Es = ProvidedType; diff --git a/packages/kbn-journeys/services/ftr_context_provider.ts b/packages/kbn-journeys/services/ftr_context_provider.ts new file mode 100644 index 0000000000000..7dd5038ef3f19 --- /dev/null +++ b/packages/kbn-journeys/services/ftr_context_provider.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; +import { services } from '.'; + +export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/packages/kbn-journeys/services/index.ts b/packages/kbn-journeys/services/index.ts new file mode 100644 index 0000000000000..93611e5d5a3f8 --- /dev/null +++ b/packages/kbn-journeys/services/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { commonFunctionalServices, RetryService } from '@kbn/ftr-common-functional-services'; +import { EsArchiverProvider } from '@kbn/ftr-common-functional-services/services/es_archiver'; +import { KibanaServerProvider } from '@kbn/ftr-common-functional-services/services/kibana_server'; +import { ProvidedType } from '@kbn/test'; +import { EsProvider } from './es'; +import { AuthService } from './auth'; + +export const services = { + es: EsProvider, + kibanaServer: commonFunctionalServices.kibanaServer, + esArchiver: commonFunctionalServices.esArchiver, + retry: commonFunctionalServices.retry, + auth: AuthService, +}; + +export type EsArchiver = ProvidedType; +export type KibanaServer = ProvidedType; +export type Es = ProvidedType; +export type Auth = AuthService; +export type Retry = RetryService; diff --git a/packages/kbn-journeys/services/page/index.ts b/packages/kbn-journeys/services/page/index.ts index 6a809eb7480f6..6e0aaafcfdc27 100644 --- a/packages/kbn-journeys/services/page/index.ts +++ b/packages/kbn-journeys/services/page/index.ts @@ -8,9 +8,10 @@ import { ToolingLog } from '@kbn/tooling-log'; import { Page } from 'playwright'; +import { Retry } from '..'; import { KibanaPage } from './kibana_page'; import { ProjectPage } from './project_page'; -export function getNewPageObject(isServerless: boolean, page: Page, log: ToolingLog) { - return isServerless ? new ProjectPage(page, log) : new KibanaPage(page, log); +export function getNewPageObject(isServerless: boolean, page: Page, log: ToolingLog, retry: Retry) { + return isServerless ? new ProjectPage(page, log, retry) : new KibanaPage(page, log, retry); } diff --git a/packages/kbn-journeys/services/page/kibana_page.ts b/packages/kbn-journeys/services/page/kibana_page.ts index 72e595601473a..170e009d0cd29 100644 --- a/packages/kbn-journeys/services/page/kibana_page.ts +++ b/packages/kbn-journeys/services/page/kibana_page.ts @@ -9,6 +9,7 @@ import { subj } from '@kbn/test-subj-selector'; import { ToolingLog } from '@kbn/tooling-log'; import { Page } from 'playwright'; +import { Retry } from '..'; interface WaitForRenderArgs { expectedItemsCount: number; @@ -19,10 +20,12 @@ interface WaitForRenderArgs { export class KibanaPage { readonly page: Page; readonly log: ToolingLog; + readonly retry: Retry; - constructor(page: Page, log: ToolingLog) { + constructor(page: Page, log: ToolingLog, retry: Retry) { this.page = page; this.log = log; + this.retry = retry; } async waitForHeader() { @@ -36,25 +39,32 @@ export class KibanaPage { } async waitForRender({ expectedItemsCount, itemLocator, checkAttribute }: WaitForRenderArgs) { - try { - await this.page.waitForFunction( - function renderCompleted(args: WaitForRenderArgs) { - const renderingItems = Array.from(document.querySelectorAll(args.itemLocator)); - const allItemsLoaded = renderingItems.length === args.expectedItemsCount; - return allItemsLoaded - ? renderingItems.every((e) => e.getAttribute(args.checkAttribute) === 'true') - : false; - }, - { expectedItemsCount, itemLocator, checkAttribute } - ); - } catch (err) { - const loaded = await this.page.$$(itemLocator); - const rendered = await this.page.$$(`${itemLocator}[${checkAttribute}="true"]`); - this.log.error( - `'waitForRendering' failed: loaded - ${loaded.length}, rendered - ${rendered.length}, expected count - ${expectedItemsCount}` - ); - throw err; - } + // we can't use `page.waitForFunction` because of CSP while testing on Cloud + await this.retry.waitFor( + `rendering of ${expectedItemsCount} elements with selector ${itemLocator} is completed`, + async () => { + const renderingItems = await this.page.$$(itemLocator); + if (renderingItems.length === expectedItemsCount) { + // all components are loaded, checking if all are rendered + const renderStatuses = await Promise.all( + renderingItems.map(async (item) => { + return (await item.getAttribute(checkAttribute)) === 'true'; + }) + ); + const rendered = renderStatuses.filter((isRendered) => isRendered === true); + this.log.debug( + `waitForRender: ${rendered.length} out of ${expectedItemsCount} are rendered...` + ); + return rendered.length === expectedItemsCount; + } else { + // not all components are loaded yet + this.log.debug( + `waitForRender: ${renderingItems.length} out of ${expectedItemsCount} are loaded...` + ); + return false; + } + } + ); } async waitForVisualizations(count: number) { diff --git a/x-pack/performance/journeys/many_fields_discover.ts b/x-pack/performance/journeys/many_fields_discover.ts index a37207f6e092d..2a801dea4478f 100644 --- a/x-pack/performance/journeys/many_fields_discover.ts +++ b/x-pack/performance/journeys/many_fields_discover.ts @@ -13,7 +13,11 @@ export const journey = new Journey({ esArchives: ['test/functional/fixtures/es_archiver/many_fields'], }) .step('Go to Discover Page', async ({ page, kbnUrl, kibanaPage }) => { - await page.goto(kbnUrl.get(`/app/discover`)); + await page.goto( + kbnUrl.get( + `/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:'35796250-bb09-11ec-a8e4-a9868e049a39',interval:auto,query:(language:kuery,query:''),sort:!())` + ) + ); await kibanaPage.waitForHeader(); await page.waitForSelector('[data-test-subj="discoverDocTable"][data-render-complete="true"]'); await page.waitForSelector(subj('globalLoadingIndicator-hidden')); From 046718c18f39d87a45846a2e7f48138d5581e13b Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Thu, 28 Sep 2023 11:09:25 +0100 Subject: [PATCH 4/9] [Transforms] Fixing use full data button (#167404) Fixing bug introduced by a merge clash between https://github.com/elastic/kibana/pull/166622 and https://github.com/elastic/kibana/pull/166651 The `hideFrozenDataTierChoice` prop is no longer available in the `FullTimeRangeSelector` component and so the transforms plugin needs to set `showFrozenDataTierChoice` in the data picker context. --- .../components/full_time_range_selector.tsx | 27 ++++++++++++------- .../components/wizard/wizard.tsx | 7 +++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx index 20e4ca43e233b..5b2a9d880c1b8 100644 --- a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx +++ b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx @@ -109,7 +109,9 @@ export const FullTimeRangeSelector: FC = (props) => toasts, http, query, - showFrozenDataTierChoice ? frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE : false, + showFrozenDataTierChoice === false + ? false + : frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE, apiPath ); if (typeof callback === 'function' && fullTimeRange !== undefined) { @@ -192,9 +194,16 @@ export const FullTimeRangeSelector: FC = (props) => [sortOptions, frozenDataPreference, setPreference] ); - const buttonTooltip = useMemo( - () => - frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? ( + const buttonTooltip = useMemo(() => { + if (showFrozenDataTierChoice === false) { + return ( + + ); + } else { + return frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? ( = (props) => id="xpack.ml.datePicker.fullTimeRangeSelector.useFullDataIncludingFrozenButtonTooltip" defaultMessage="Use full range of data including frozen data tier, which might have slower search results." /> - ), - [frozenDataPreference] - ); + ); + } + }, [frozenDataPreference, showFrozenDataTierChoice]); return ( @@ -222,7 +231,7 @@ export const FullTimeRangeSelector: FC = (props) => /> - {showFrozenDataTierChoice ? ( + {showFrozenDataTierChoice === false ? null : ( = (props) => {popoverContent} - ) : null} + )} ); }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index a26866452ff3f..b8ee028682bae 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -12,7 +12,7 @@ import { EuiSteps, EuiStepStatus } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DataView } from '@kbn/data-views-plugin/public'; -import { DatePickerContextProvider } from '@kbn/ml-date-picker'; +import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { StorageContextProvider } from '@kbn/ml-local-storage'; import { UrlStateProvider } from '@kbn/ml-url-state'; @@ -20,6 +20,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/common'; import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats'; import type { RuntimeMappings } from '@kbn/ml-runtime-field-utils'; +import { useEnabledFeatures } from '../../../../serverless_context'; import type { TransformConfigUnion } from '../../../../../../common/types/transform'; import { getCreateTransformRequestBody } from '../../../../common'; @@ -106,6 +107,7 @@ export const CreateTransformWizardContext = createContext<{ }); export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) => { + const { showNodeInfo } = useEnabledFeatures(); const appDependencies = useAppDependencies(); const { ml: { FieldStatsFlyoutProvider }, @@ -226,9 +228,10 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) const stepsConfig = [stepDefine, stepDetails, stepCreate]; - const datePickerDeps = { + const datePickerDeps: DatePickerDependencies = { ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, + showFrozenDataTierChoice: showNodeInfo, }; const fieldStatsServices: FieldStatsServices = useMemo( From f8dae67d8d9fc0f41d7f4d5317e596a7fe828dfe Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:10:39 +0200 Subject: [PATCH 5/9] fix telemetry test (#167471) Closes https://github.com/elastic/kibana/issues/156245 One test agent went from offline to inactive due to inactivity timeout and broke the tests, instead changed the `last_checkin` to now-6m so it stays in offline state. --- .../server/integration_tests/fleet_usage_telemetry.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts index b841c641c3af4..e2e7e9f7887e6 100644 --- a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts @@ -20,8 +20,7 @@ import { waitForFleetSetup } from './helpers'; const logFilePath = path.join(__dirname, 'logs.log'); -// Failing: See https://github.com/elastic/kibana/issues/156245 -describe.skip('fleet usage telemetry', () => { +describe('fleet usage telemetry', () => { let core: any; let esServer: TestElasticsearchUtils; let kbnServer: TestKibanaUtils; @@ -218,7 +217,7 @@ describe.skip('fleet usage telemetry', () => { version: '8.6.0', }, last_checkin_status: 'online', - last_checkin: '2023-09-13T12:26:24Z', + last_checkin: new Date(Date.now() - 1000 * 60 * 6).toISOString(), active: true, policy_id: 'policy2', }, From 3be21c9e562dbc420d394c253995b696bb4e3147 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 28 Sep 2023 12:21:35 +0200 Subject: [PATCH 6/9] [Log Explorer] Implement Data Views tab into selector (#166938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #166848 This work adds a new tab to navigate Data View from the Log Explorer selector. In this first iteration, when the user selects a data view, we move into discovering preselecting and loading the data view of choice. **N.B.**: this recording is made on a setup where I have no installed integrations, so having the no integrations panel is the expected behaviour. https://github.com/elastic/kibana/assets/34506779/e8d1f622-86fb-4841-b4cc-4a913067d2cc ## Updated selector state machine Screenshot 2023-09-22 at 12 15 44 ## New DataViews state machine Screenshot 2023-09-22 at 12 39 09 --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/dataset_selection/index.ts | 2 + .../components/dataset_selector/constants.tsx | 23 ++- .../dataset_selector.stories.tsx | 66 +++++++- .../dataset_selector/dataset_selector.tsx | 91 ++++++++-- .../state_machine/state_machine.ts | 80 +++++++-- .../dataset_selector/state_machine/types.ts | 28 ++- .../state_machine/use_dataset_selector.ts | 31 +++- .../sub_components/data_views_panel_title.tsx | 21 +++ .../sub_components/list_status.tsx | 3 +- .../components/dataset_selector/types.ts | 49 ++++-- .../components/dataset_selector/utils.tsx | 18 ++ .../components/log_explorer/log_explorer.tsx | 27 +-- .../custom_dataset_selector.tsx | 44 ++++- .../customizations/log_explorer_profile.tsx | 10 +- .../public/hooks/use_data_views.tsx | 104 ++++++++++++ x-pack/plugins/log_explorer/public/plugin.ts | 5 +- .../public/state_machines/data_views/index.ts | 8 + .../state_machines/data_views/src/defaults.ts | 20 +++ .../state_machines/data_views/src/index.ts | 9 + .../data_views/src/state_machine.ts | 160 ++++++++++++++++++ .../state_machines/data_views/src/types.ts | 101 +++++++++++ x-pack/plugins/log_explorer/public/types.ts | 4 +- .../public/utils/get_data_view_test_subj.ts | 20 +++ .../public/utils/parse_data_view_list_item.ts | 15 ++ .../common/translations.ts | 2 +- .../dataset_selector.ts | 153 ++++++++++++++++- .../observability_log_explorer.ts | 12 ++ .../dataset_selector.ts | 147 +++++++++++++++- 28 files changed, 1161 insertions(+), 92 deletions(-) create mode 100644 x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx create mode 100644 x-pack/plugins/log_explorer/public/hooks/use_data_views.tsx create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/index.ts create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/defaults.ts create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/index.ts create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/state_machine.ts create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/types.ts create mode 100644 x-pack/plugins/log_explorer/public/utils/get_data_view_test_subj.ts create mode 100644 x-pack/plugins/log_explorer/public/utils/parse_data_view_list_item.ts diff --git a/x-pack/plugins/log_explorer/common/dataset_selection/index.ts b/x-pack/plugins/log_explorer/common/dataset_selection/index.ts index 3284610f53bcc..f390f7a89f87c 100644 --- a/x-pack/plugins/log_explorer/common/dataset_selection/index.ts +++ b/x-pack/plugins/log_explorer/common/dataset_selection/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DataViewListItem } from '@kbn/data-views-plugin/common'; import { AllDatasetSelection } from './all_dataset_selection'; import { SingleDatasetSelection } from './single_dataset_selection'; import { UnresolvedDatasetSelection } from './unresolved_dataset_selection'; @@ -14,6 +15,7 @@ export type DatasetSelection = | SingleDatasetSelection | UnresolvedDatasetSelection; export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void; +export type DataViewSelection = (dataView: DataViewListItem) => void; export const isDatasetSelection = (input: any): input is DatasetSelection => { return ( diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx index 1cbcd6a0f032a..28a5401f98048 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx @@ -12,8 +12,10 @@ export const INTEGRATIONS_PANEL_ID = 'dataset-selector-integrations-panel'; export const INTEGRATIONS_TAB_ID = 'dataset-selector-integrations-tab'; export const UNCATEGORIZED_PANEL_ID = 'dataset-selector-uncategorized-panel'; export const UNCATEGORIZED_TAB_ID = 'dataset-selector-uncategorized-tab'; +export const DATA_VIEWS_PANEL_ID = 'dataset-selector-data-views-panel'; +export const DATA_VIEWS_TAB_ID = 'dataset-selector-data-views-tab'; -export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300; +export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 400; export const showAllLogsLabel = i18n.translate('xpack.logExplorer.datasetSelector.showAllLogs', { defaultMessage: 'Show all logs', @@ -28,6 +30,14 @@ export const uncategorizedLabel = i18n.translate( { defaultMessage: 'Uncategorized' } ); +export const dataViewsLabel = i18n.translate('xpack.logExplorer.datasetSelector.dataViews', { + defaultMessage: 'Data Views', +}); + +export const openDiscoverLabel = i18n.translate('xpack.logExplorer.datasetSelector.openDiscover', { + defaultMessage: 'Opens in Discover', +}); + export const sortOrdersLabel = i18n.translate('xpack.logExplorer.datasetSelector.sortOrders', { defaultMessage: 'Sort directions', }); @@ -43,6 +53,17 @@ export const noDatasetsDescriptionLabel = i18n.translate( } ); +export const noDataViewsLabel = i18n.translate('xpack.logExplorer.datasetSelector.noDataViews', { + defaultMessage: 'No data views found', +}); + +export const noDataViewsDescriptionLabel = i18n.translate( + 'xpack.logExplorer.datasetSelector.noDataViewsDescription', + { + defaultMessage: 'No data views or search results found.', + } +); + export const noIntegrationsLabel = i18n.translate( 'xpack.logExplorer.datasetSelector.noIntegrations', { defaultMessage: 'No integrations found' } diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx index 10bc958c8f2ce..82178164994eb 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx @@ -11,6 +11,7 @@ import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import type { Meta, Story } from '@storybook/react'; import { IndexPattern } from '@kbn/io-ts-utils'; +import { DataViewListItem } from '@kbn/data-views-plugin/common'; import { AllDatasetSelection, DatasetSelection, @@ -29,6 +30,10 @@ const meta: Meta = { options: [null, { message: 'Failed to fetch data streams' }], control: { type: 'radio' }, }, + dataViewsError: { + options: [null, { message: 'Failed to fetch data data views' }], + control: { type: 'radio' }, + }, integrationsError: { options: [null, { message: 'Failed to fetch data integrations' }], control: { type: 'radio' }, @@ -71,20 +76,39 @@ const DatasetSelectorTemplate: Story = (args) => { const sortedDatasets = search.sortOrder === 'asc' ? filteredDatasets : filteredDatasets.reverse(); + const filteredDataViews = mockDataViews.filter((dataView) => + dataView.name?.includes(search.name as string) + ); + + const sortedDataViews = + search.sortOrder === 'asc' ? filteredDataViews : filteredDataViews.reverse(); + + const { + datasetsError, + dataViewsError, + integrationsError, + isLoadingDataViews, + isLoadingIntegrations, + isLoadingUncategorized, + } = args; + return ( ); }; @@ -92,12 +116,18 @@ const DatasetSelectorTemplate: Story = (args) => { export const Basic = DatasetSelectorTemplate.bind({}); Basic.args = { datasetsError: null, + dataViewsError: null, integrationsError: null, + isLoadingDataViews: false, isLoadingIntegrations: false, - isLoadingStreams: false, + isLoadingUncategorized: false, + isSearchingIntegrations: false, + onDataViewsReload: () => alert('Reload data views...'), + onDataViewSelection: (dataView) => alert(`Navigate to data view "${dataView.name}"`), + onDataViewsTabClick: () => console.log('Load data views...'), onIntegrationsReload: () => alert('Reload integrations...'), - onStreamsEntryClick: () => console.log('Load uncategorized streams...'), - onUnmanagedStreamsReload: () => alert('Reloading streams...'), + onUncategorizedTabClick: () => console.log('Load uncategorized streams...'), + onUncategorizedReload: () => alert('Reloading streams...'), }; const mockIntegrations: Integration[] = [ @@ -477,3 +507,25 @@ const mockDatasets: Dataset[] = [ { name: 'data-load-balancing-logs-*' as IndexPattern }, { name: 'data-scaling-logs-*' as IndexPattern }, ].map((dataset) => Dataset.create(dataset)); + +const mockDataViews: DataViewListItem[] = [ + { + id: 'logs-*', + namespaces: ['default'], + title: 'logs-*', + name: 'logs-*', + }, + { + id: 'metrics-*', + namespaces: ['default'], + title: 'metrics-*', + name: 'metrics-*', + }, + { + id: '7258d186-6430-4b51-bb67-2603cdfb4652', + namespaces: ['default'], + title: 'synthetics-*', + typeMeta: {}, + name: 'synthetics-dashboard', + }, +]; diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx index f9e722effc784..10d8b4c046c9a 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx @@ -10,6 +10,9 @@ import styled from '@emotion/styled'; import { EuiContextMenu, EuiHorizontalRule, EuiTab, EuiTabs } from '@elastic/eui'; import { useIntersectionRef } from '../../hooks/use_intersection_ref'; import { + dataViewsLabel, + DATA_VIEWS_PANEL_ID, + DATA_VIEWS_TAB_ID, DATA_VIEW_POPOVER_CONTENT_WIDTH, integrationsLabel, INTEGRATIONS_PANEL_ID, @@ -25,19 +28,30 @@ import { SelectorActions } from './sub_components/selector_actions'; import { DatasetSelectorProps } from './types'; import { buildIntegrationsTree, + createDataViewsStatusItem, createIntegrationStatusItem, createUncategorizedStatusItem, } from './utils'; +import { getDataViewTestSubj } from '../../utils/get_data_view_test_subj'; +import { DataViewsPanelTitle } from './sub_components/data_views_panel_title'; export function DatasetSelector({ datasets, - datasetsError, datasetSelection, + datasetsError, + dataViews, + dataViewsError, integrations, integrationsError, + isLoadingDataViews, isLoadingIntegrations, - isLoadingStreams, + isLoadingUncategorized, isSearchingIntegrations, + onDataViewSelection, + onDataViewsReload, + onDataViewsSearch, + onDataViewsSort, + onDataViewsTabClick, onIntegrationsLoadMore, onIntegrationsReload, onIntegrationsSearch, @@ -45,10 +59,10 @@ export function DatasetSelector({ onIntegrationsStreamsSearch, onIntegrationsStreamsSort, onSelectionChange, - onStreamsEntryClick, - onUnmanagedStreamsReload, - onUnmanagedStreamsSearch, - onUnmanagedStreamsSort, + onUncategorizedReload, + onUncategorizedSearch, + onUncategorizedSort, + onUncategorizedTabClick, }: DatasetSelectorProps) { const { panelId, @@ -62,21 +76,26 @@ export function DatasetSelector({ searchByName, selectAllLogDataset, selectDataset, + selectDataView, sortByOrder, switchToIntegrationsTab, switchToUncategorizedTab, + switchToDataViewsTab, togglePopover, } = useDatasetSelector({ initialContext: { selection: datasetSelection }, + onDataViewSelection, + onDataViewsSearch, + onDataViewsSort, onIntegrationsLoadMore, onIntegrationsReload, onIntegrationsSearch, onIntegrationsSort, onIntegrationsStreamsSearch, onIntegrationsStreamsSort, - onUnmanagedStreamsSearch, - onUnmanagedStreamsSort, - onUnmanagedStreamsReload, + onUncategorizedSearch, + onUncategorizedSort, + onUncategorizedReload, onSelectionChange, }); @@ -117,8 +136,8 @@ export function DatasetSelector({ createUncategorizedStatusItem({ data: datasets, error: datasetsError, - isLoading: isLoadingStreams, - onRetry: onUnmanagedStreamsReload, + isLoading: isLoadingUncategorized, + onRetry: onUncategorizedReload, }), ]; } @@ -127,7 +146,26 @@ export function DatasetSelector({ name: dataset.title, onClick: () => selectDataset(dataset), })); - }, [datasets, datasetsError, isLoadingStreams, selectDataset, onUnmanagedStreamsReload]); + }, [datasets, datasetsError, isLoadingUncategorized, selectDataset, onUncategorizedReload]); + + const dataViewsItems = useMemo(() => { + if (!dataViews || dataViews.length === 0) { + return [ + createDataViewsStatusItem({ + data: dataViews, + error: dataViewsError, + isLoading: isLoadingDataViews, + onRetry: onDataViewsReload, + }), + ]; + } + + return dataViews.map((dataView) => ({ + 'data-test-subj': getDataViewTestSubj(dataView.title), + name: dataView.name, + onClick: () => selectDataView(dataView), + })); + }, [dataViews, dataViewsError, isLoadingDataViews, selectDataView, onDataViewsReload]); const tabs = [ { @@ -140,11 +178,20 @@ export function DatasetSelector({ id: UNCATEGORIZED_TAB_ID, name: uncategorizedLabel, onClick: () => { - onStreamsEntryClick(); // Lazy-load uncategorized datasets only when accessing the Uncategorized tab + onUncategorizedTabClick(); // Lazy-load uncategorized datasets only when accessing the Uncategorized tab switchToUncategorizedTab(); }, 'data-test-subj': 'datasetSelectorUncategorizedTab', }, + { + id: DATA_VIEWS_TAB_ID, + name: dataViewsLabel, + onClick: () => { + onDataViewsTabClick(); // Lazy-load data views only when accessing the Data Views tab + switchToDataViewsTab(); + }, + 'data-test-subj': 'datasetSelectorDataViewsTab', + }, ]; const tabEntries = tabs.map((tab) => ( @@ -175,7 +222,7 @@ export function DatasetSelector({ search={search} onSearch={searchByName} onSort={sortByOrder} - isLoading={isSearchingIntegrations || isLoadingStreams} + isLoading={isSearchingIntegrations || isLoadingUncategorized} /> {/* For a smoother user experience, we keep each tab content mount and we only show the select one @@ -215,6 +262,22 @@ export function DatasetSelector({ data-test-subj="uncategorizedContextMenu" size="s" /> + {/* Data views tab content */} +

- - - - - - - - - - - - - - - - - - -
- - ) -); diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/host_layout.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/host_layout.tsx deleted file mode 100644 index 41e0204bc7b29..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/host_layout.tsx +++ /dev/null @@ -1,376 +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 { EuiPanel } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { withTheme } from '@kbn/kibana-react-plugin/common'; -import React from 'react'; -import type { LayoutPropsWithTheme } from '../../types'; -import { ChartSectionVis } from '../chart_section_vis'; -import { GaugesSectionVis } from '../gauges_section_vis'; -import { MetadataDetails } from '../metadata_details'; -import { Section } from '../section'; -import { SubSection } from '../sub_section'; -import { AwsLayoutSection } from './aws_layout_sections'; -import { NginxLayoutSection } from './nginx_layout_sections'; - -export const HostLayout = withTheme( - ({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( - - - -
- - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - -
- - -
-
- ) -); diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx index 1f049814a23d3..15425ef618049 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx @@ -42,7 +42,13 @@ export const MetricDetailPage = () => { loading: metadataLoading, cloudId, metadata, - } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId, parsedTimeRange); + } = useMetadata({ + assetId: nodeId, + assetType: nodeType, + requiredMetrics: inventoryModel.requiredMetrics, + sourceId, + timeRange: parsedTimeRange, + }); const [sideNav, setSideNav] = useState([]); diff --git a/x-pack/plugins/infra/server/routes/node_details/index.ts b/x-pack/plugins/infra/server/routes/node_details/index.ts index cd92c902a110e..56eccbbe160e9 100644 --- a/x-pack/plugins/infra/server/routes/node_details/index.ts +++ b/x-pack/plugins/infra/server/routes/node_details/index.ts @@ -34,30 +34,46 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( - NodeDetailsRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - const soClient = (await requestContext.core).savedObjects.client; - const source = await libs.sources.getSourceConfiguration(soClient, sourceId); - - UsageCollector.countNode(nodeType); - - const options: InfraMetricsRequestOptions = { - nodeIds: { - nodeId, - cloudId, - }, - nodeType, - sourceConfiguration: source.configuration, - metrics, - timerange, - }; - return response.ok({ - body: NodeDetailsMetricDataResponseRT.encode({ - metrics: await libs.metrics.getMetrics(requestContext, options, request), - }), - }); + try { + const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( + NodeDetailsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const soClient = (await requestContext.core).savedObjects.client; + const source = await libs.sources.getSourceConfiguration(soClient, sourceId); + + UsageCollector.countNode(nodeType); + + const options: InfraMetricsRequestOptions = { + nodeIds: { + nodeId, + cloudId, + }, + nodeType, + sourceConfiguration: source.configuration, + metrics, + timerange, + }; + return response.ok({ + body: NodeDetailsMetricDataResponseRT.encode({ + metrics: await libs.metrics.getMetrics(requestContext, options, request), + }), + }); + } catch (err) { + if (Boom.isBoom(err)) { + return response.customError({ + statusCode: err.output.statusCode, + body: { message: err.output.payload.message }, + }); + } + + return response.customError({ + statusCode: err.statusCode ?? 500, + body: { + message: err.message ?? 'An unexpected error occurred', + }, + }); + } } ); }; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 260328513d6c1..b468f00d02466 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -18996,25 +18996,6 @@ "xpack.infra.metadataEmbeddable.setFilterByValueTooltip": "Filtrer par valeur", "xpack.infra.metadataEmbeddable.setRemoveFilterTooltip": "Supprimer le filtre", "xpack.infra.metadataEmbeddable.value": "Valeur", - "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "pour cent", - "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.sectionLabel": "Utilisation CPU", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.readsSeriesLabel": "lit", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.sectionLabel": "Octets d'E/S sur le disque", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.writesSeriesLabel": "écrit", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.readsSeriesLabel": "lit", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.sectionLabel": "Opérations d'E/S sur le disque", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.writesSeriesLabel": "écrit", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.rxSeriesLabel": "entrée", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.sectionLabel": "Trafic réseau", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.txSeriesLabel": "sortie", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsInSeriesLabel": "entrée", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsOutSeriesLabel": "sortie", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.sectionLabel": "Paquets réseau (moyenne)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.cpuUtilizationSeriesLabel": "Utilisation CPU", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsInLabel": "Paquets (entrée)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsOutLabel": "Paquets (sortie)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel": "Aperçu AWS", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.statusCheckFailedLabel": "La vérification du statut a échoué", "xpack.infra.metricDetailPage.containerMetricsLayout.cpuUsageSection.sectionLabel": "Utilisation CPU", "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.readRateSeriesLabel": "lit", "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.sectionLabel": "ES sur le disque (octets)", @@ -19039,32 +19020,6 @@ "xpack.infra.metricDetailPage.ec2MetricsLayout.diskIOBytesSection.writeLabel": "écrit", "xpack.infra.metricDetailPage.ec2MetricsLayout.networkTrafficSection.sectionLabel": "Trafic réseau", "xpack.infra.metricDetailPage.ec2MetricsLayout.overviewSection.sectionLabel": "Aperçu EC2 AWS", - "xpack.infra.metricDetailPage.hostMetricsLayout.cpuUsageSection.sectionLabel": "Utilisation CPU", - "xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel": "Hôte", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fifteenMinuteSeriesLabel": "15 min", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fiveMinuteSeriesLabel": "5 min", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.oneMinuteSeriesLabel": "1 min", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.sectionLabel": "Charge", - "xpack.infra.metricDetailPage.hostMetricsLayout.memoryUsageSection.sectionLabel": "Utilisation mémoire", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkRxRateSeriesLabel": "entrée", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkTxRateSeriesLabel": "sortie", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.sectionLabel": "Trafic réseau", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.cpuUsageSeriesLabel": "Utilisation CPU", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.inboundRXSeriesLabel": "Entrant (RX)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.loadSeriesLabel": "Charge (5 min)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.memoryCapacitySeriesLabel": "Utilisation mémoire", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.outboundTXSeriesLabel": "Sortant (TX)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel": "Aperçu de l'hôte", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeCpuCapacitySection.sectionLabel": "Capacité CPU du nœud", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeDiskCapacitySection.sectionLabel": "Capacité du disque du nœud", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeMemoryCapacitySection.sectionLabel": "Capacité de mémoire du nœud", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodePodCapacitySection.sectionLabel": "Capacité de pod du nœud", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.cpuUsageSeriesLabel": "Capacité CPU", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.diskCapacitySeriesLabel": "Capacité du disque", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.loadSeriesLabel": "Charge (5 min)", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.memoryUsageSeriesLabel": "Capacité de mémoire", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.podCapacitySeriesLabel": "Capacité de pod", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel": "Aperçu Kubernetes", "xpack.infra.metricDetailPage.nginxMetricsLayout.activeConnectionsSection.sectionLabel": "Connexions actives", "xpack.infra.metricDetailPage.nginxMetricsLayout.hitsSection.sectionLabel": "Résultats", "xpack.infra.metricDetailPage.nginxMetricsLayout.requestRateSection.sectionLabel": "Taux de requêtes", @@ -39848,4 +39803,4 @@ "xpack.painlessLab.walkthroughButtonLabel": "Présentation", "xpack.serverlessObservability.nav.getStarted": "Démarrer" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index db4e8cc674281..7f59456c6bd14 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19010,25 +19010,6 @@ "xpack.infra.metadataEmbeddable.setFilterByValueTooltip": "値でフィルタリング", "xpack.infra.metadataEmbeddable.setRemoveFilterTooltip": "フィルターを削除", "xpack.infra.metadataEmbeddable.value": "値", - "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "パーセント", - "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.sectionLabel": "CPU 使用状況", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.readsSeriesLabel": "読み取り", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.sectionLabel": "ディスク I/O バイト", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.writesSeriesLabel": "書き込み", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.readsSeriesLabel": "読み取り", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.sectionLabel": "ディスク I/O オペレーション", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.writesSeriesLabel": "書き込み", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.rxSeriesLabel": "in", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.sectionLabel": "ネットワークトラフィック", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.txSeriesLabel": "出", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsInSeriesLabel": "in", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsOutSeriesLabel": "出", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.sectionLabel": "ネットワークパケット(平均)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.cpuUtilizationSeriesLabel": "CPU 使用状況", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsInLabel": "パケット(受信)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsOutLabel": "パケット(送信)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel": "AWS概要", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.statusCheckFailedLabel": "ステータス確認失敗", "xpack.infra.metricDetailPage.containerMetricsLayout.cpuUsageSection.sectionLabel": "CPU使用状況", "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.readRateSeriesLabel": "読み取り", "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.sectionLabel": "ディスク IO(バイト)", @@ -19053,32 +19034,6 @@ "xpack.infra.metricDetailPage.ec2MetricsLayout.diskIOBytesSection.writeLabel": "書き込み", "xpack.infra.metricDetailPage.ec2MetricsLayout.networkTrafficSection.sectionLabel": "ネットワークトラフィック", "xpack.infra.metricDetailPage.ec2MetricsLayout.overviewSection.sectionLabel": "Aws EC2概要", - "xpack.infra.metricDetailPage.hostMetricsLayout.cpuUsageSection.sectionLabel": "CPU使用状況", - "xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel": "ホスト", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fifteenMinuteSeriesLabel": "15m", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fiveMinuteSeriesLabel": "5m", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.oneMinuteSeriesLabel": "1m", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.sectionLabel": "読み込み", - "xpack.infra.metricDetailPage.hostMetricsLayout.memoryUsageSection.sectionLabel": "メモリー使用状況", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkRxRateSeriesLabel": "in", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkTxRateSeriesLabel": "出", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.sectionLabel": "ネットワークトラフィック", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU使用状況", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.inboundRXSeriesLabel": "受信(RX)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.loadSeriesLabel": "読み込み(5m)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.memoryCapacitySeriesLabel": "メモリー使用状況", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.outboundTXSeriesLabel": "送信(TX)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel": "ホスト概要", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeCpuCapacitySection.sectionLabel": "ノード CPU 処理能力", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeDiskCapacitySection.sectionLabel": "ノードディスク容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeMemoryCapacitySection.sectionLabel": "ノードメモリー容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodePodCapacitySection.sectionLabel": "ノードポッド容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU 処理能力", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.diskCapacitySeriesLabel": "ディスク容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.loadSeriesLabel": "読み込み(5m)", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.memoryUsageSeriesLabel": "メモリー容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.podCapacitySeriesLabel": "ポッド容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel": "Kubernetes概要", "xpack.infra.metricDetailPage.nginxMetricsLayout.activeConnectionsSection.sectionLabel": "アクティブな接続", "xpack.infra.metricDetailPage.nginxMetricsLayout.hitsSection.sectionLabel": "ヒット数", "xpack.infra.metricDetailPage.nginxMetricsLayout.requestRateSection.sectionLabel": "リクエストレート", @@ -39839,4 +39794,4 @@ "xpack.painlessLab.walkthroughButtonLabel": "実地検証", "xpack.serverlessObservability.nav.getStarted": "使ってみる" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 68e8ae1196d60..8474d0289c239 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19010,25 +19010,6 @@ "xpack.infra.metadataEmbeddable.setFilterByValueTooltip": "按值筛选", "xpack.infra.metadataEmbeddable.setRemoveFilterTooltip": "移除筛选", "xpack.infra.metadataEmbeddable.value": "值", - "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "百分比", - "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.sectionLabel": "CPU 使用率", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.readsSeriesLabel": "读取数", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.sectionLabel": "磁盘 I/O 字节数", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.writesSeriesLabel": "写入数", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.readsSeriesLabel": "读取数", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.sectionLabel": "磁盘 I/O 操作数", - "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.writesSeriesLabel": "写入数", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.rxSeriesLabel": "于", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.sectionLabel": "网络流量", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.txSeriesLabel": "传出", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsInSeriesLabel": "于", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsOutSeriesLabel": "传出", - "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.sectionLabel": "网络数据包(平均值)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.cpuUtilizationSeriesLabel": "CPU 使用率", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsInLabel": "数据包(传入)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsOutLabel": "数据包(传出)", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel": "AWS 概览", - "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.statusCheckFailedLabel": "状态检查失败", "xpack.infra.metricDetailPage.containerMetricsLayout.cpuUsageSection.sectionLabel": "CPU 使用率", "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.readRateSeriesLabel": "读取数", "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.sectionLabel": "磁盘 IO(字节)", @@ -19053,32 +19034,6 @@ "xpack.infra.metricDetailPage.ec2MetricsLayout.diskIOBytesSection.writeLabel": "写入数", "xpack.infra.metricDetailPage.ec2MetricsLayout.networkTrafficSection.sectionLabel": "网络流量", "xpack.infra.metricDetailPage.ec2MetricsLayout.overviewSection.sectionLabel": "Aws EC2 概览", - "xpack.infra.metricDetailPage.hostMetricsLayout.cpuUsageSection.sectionLabel": "CPU 使用率", - "xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel": "主机", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fifteenMinuteSeriesLabel": "15 分钟", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fiveMinuteSeriesLabel": "5 分钟", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.oneMinuteSeriesLabel": "1 分钟", - "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.sectionLabel": "加载", - "xpack.infra.metricDetailPage.hostMetricsLayout.memoryUsageSection.sectionLabel": "内存利用率", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkRxRateSeriesLabel": "于", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkTxRateSeriesLabel": "传出", - "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.sectionLabel": "网络流量", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU 使用率", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.inboundRXSeriesLabel": "入站 (RX)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.loadSeriesLabel": "负载(5 分钟)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.memoryCapacitySeriesLabel": "内存利用率", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.outboundTXSeriesLabel": "出站 (TX)", - "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel": "主机概览", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeCpuCapacitySection.sectionLabel": "节点 CPU 容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeDiskCapacitySection.sectionLabel": "节点磁盘容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeMemoryCapacitySection.sectionLabel": "节点内存容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodePodCapacitySection.sectionLabel": "节点 Pod 容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU 容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.diskCapacitySeriesLabel": "磁盘容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.loadSeriesLabel": "负载(5 分钟)", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.memoryUsageSeriesLabel": "内存容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.podCapacitySeriesLabel": "Pod 容量", - "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel": "Kubernetes 概览", "xpack.infra.metricDetailPage.nginxMetricsLayout.activeConnectionsSection.sectionLabel": "活动连接", "xpack.infra.metricDetailPage.nginxMetricsLayout.hitsSection.sectionLabel": "命中数", "xpack.infra.metricDetailPage.nginxMetricsLayout.requestRateSection.sectionLabel": "请求速率", @@ -39833,4 +39788,4 @@ "xpack.painlessLab.walkthroughButtonLabel": "指导", "xpack.serverlessObservability.nav.getStarted": "开始使用" } -} +} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts index 5e0cff513002e..a1e4552df68fe 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts @@ -15,7 +15,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { DATES } from './constants'; -const { min, max } = DATES['7.0.0'].hosts; +const { min, max } = DATES['8.0.0'].pods_only; interface NodeDetailsRequest { metrics: InventoryMetric[]; @@ -31,8 +31,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('metrics', () => { - before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/7.0.0/hosts')); - after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/7.0.0/hosts')); + before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/pods_only')); + after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/8.0.0/pods_only')); const fetchNodeDetails = async ( body: NodeDetailsRequest @@ -48,14 +48,14 @@ export default function ({ getService }: FtrProviderContext) { it('should basically work', async () => { const data = fetchNodeDetails({ sourceId: 'default', - metrics: ['hostCpuUsage'], + metrics: ['podCpuUsage'], timerange: { to: max, from: min, interval: '>=1m', }, - nodeId: 'demo-stack-mysql-01', - nodeType: 'host', + nodeId: '7d6d7955-f853-42b1-8613-11f52d0d2725', + nodeType: 'pod', }); return data.then((resp) => { if (!resp) { @@ -63,28 +63,28 @@ export default function ({ getService }: FtrProviderContext) { } expect(resp.metrics.length).to.equal(1); const metric = first(resp.metrics) as any; - expect(metric).to.have.property('id', 'hostCpuUsage'); + expect(metric).to.have.property('id', 'podCpuUsage'); expect(metric).to.have.property('series'); const series = first(metric.series) as any; - expect(series).to.have.property('id', 'user'); + expect(series).to.have.property('id', 'cpu'); expect(series).to.have.property('data'); const datapoint = last(series.data) as any; - expect(datapoint).to.have.property('timestamp', 1547571780000); - expect(datapoint).to.have.property('value', 0.0015); + expect(datapoint).to.have.property('timestamp', 1642698890000); + expect(datapoint).to.have.property('value', 0.544); }); }); it('should support multiple metrics', async () => { const data = fetchNodeDetails({ sourceId: 'default', - metrics: ['hostCpuUsage', 'hostLoad'], + metrics: ['podCpuUsage', 'podMemoryUsage'], timerange: { to: max, from: min, interval: '>=1m', }, - nodeId: 'demo-stack-mysql-01', - nodeType: 'host', + nodeId: '7d6d7955-f853-42b1-8613-11f52d0d2725', + nodeType: 'pod', }); return data.then((resp) => { if (!resp) { @@ -95,28 +95,26 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should return multiple values for hostSystemOverview metric', () => { + it('should return multiple values for podOverview metric', () => { const data = fetchNodeDetails({ sourceId: 'default', - metrics: ['hostSystemOverview'], + metrics: ['podOverview'], timerange: { to: max, from: min, interval: '>=1m', }, - nodeId: 'demo-stack-mysql-01', - nodeType: 'host', + nodeId: '7d6d7955-f853-42b1-8613-11f52d0d2725', + nodeType: 'pod', }); return data.then((resp) => { if (!resp) { return; } - const hostSystemOverviewMetric = resp.metrics.find( - (metric) => metric.id === 'hostSystemOverview' - ); + const podOverviewMetric = resp.metrics.find((metric) => metric.id === 'podOverview'); - expect(hostSystemOverviewMetric?.series.length).to.be.greaterThan(1); + expect(podOverviewMetric?.series.length).to.be.greaterThan(1); }); }); }); From b90b2114a04c40b0ee5ebe6c533402309e9af60f Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Thu, 28 Sep 2023 13:17:39 +0200 Subject: [PATCH 8/9] Fix TypeScript issue in x-pack/test (#167488) Follow up to #167343 See: https://github.com/elastic/kibana/pull/167343#discussion_r1339688385 --- x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts index 3f5c414f46ef8..831659929d86d 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts @@ -10,6 +10,7 @@ import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; import expect from '@kbn/expect'; +import type { GetAgentsResponse } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry, generateAgent } from '../helpers'; import { setupFleetAndAgents } from './agents/services'; @@ -130,7 +131,7 @@ export default function (providerContext: FtrProviderContext) { expectedAgentCount: number, attempts: number, _attemptsMade = 0 - ): Promise { + ): Promise { const { body: apiResponse } = await supertest .get(`/api/fleet/agents?showInactive=true`) .set('kbn-xsrf', 'xxxx') From 92a92fff674d3574cc1f374ce1b0fce4ea383b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Thu, 28 Sep 2023 12:20:53 +0100 Subject: [PATCH 9/9] [Cloud] ES endpoint discovery (#167122) --- .github/CODEOWNERS | 1 + .i18nrc.json | 1 + docs/setup/connect-to-elasticsearch.asciidoc | 3 +- package.json | 1 + packages/cloud/README.md | 3 + .../deployment_details/deployment_details.tsx | 81 ++++++++++++ .../deployment_details_cloudid_input.tsx | 46 +++++++ .../deployment_details_es_input.tsx | 48 +++++++ .../deployment_details_modal.tsx | 69 ++++++++++ packages/cloud/deployment_details/index.ts | 11 ++ .../cloud/deployment_details/services.tsx | 123 ++++++++++++++++++ packages/cloud/jest.config.js | 13 ++ packages/cloud/kibana.jsonc | 5 + packages/cloud/package.json | 6 + packages/cloud/tsconfig.json | 21 +++ .../src/ui/header/header_help_menu.tsx | 46 +++++-- .../core-chrome-browser/src/nav_controls.ts | 4 +- tsconfig.base.json | 2 + .../cloud_links/kibana.jsonc | 3 + .../maybe_add_cloud_links/endpoints_modal.tsx | 31 +++++ ...help_menu_links.ts => help_menu_links.tsx} | 36 +++++ .../maybe_add_cloud_links.test.ts | 17 +++ .../maybe_add_cloud_links.ts | 7 +- .../cloud_links/public/plugin.test.ts | 35 +++-- .../cloud_links/public/plugin.tsx | 6 +- .../cloud_links/tsconfig.json | 3 + .../header/deployment_details.component.tsx | 102 --------------- .../header/deployment_details.stories.tsx | 55 -------- .../components/header/deployment_details.tsx | 47 +++++-- x-pack/plugins/fleet/tsconfig.json | 1 + .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - x-pack/test/functional_cloud/config.ts | 3 +- .../functional_cloud/tests/cloud_links.ts | 22 ++++ yarn.lock | 4 + 36 files changed, 663 insertions(+), 199 deletions(-) create mode 100644 packages/cloud/README.md create mode 100644 packages/cloud/deployment_details/deployment_details.tsx create mode 100644 packages/cloud/deployment_details/deployment_details_cloudid_input.tsx create mode 100644 packages/cloud/deployment_details/deployment_details_es_input.tsx create mode 100644 packages/cloud/deployment_details/deployment_details_modal.tsx create mode 100644 packages/cloud/deployment_details/index.ts create mode 100644 packages/cloud/deployment_details/services.tsx create mode 100644 packages/cloud/jest.config.js create mode 100644 packages/cloud/kibana.jsonc create mode 100644 packages/cloud/package.json create mode 100644 packages/cloud/tsconfig.json create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.tsx rename x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/{help_menu_links.ts => help_menu_links.tsx} (53%) delete mode 100644 x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx delete mode 100644 x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index be401c26dc0d2..89f769c0f12ec 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -67,6 +67,7 @@ packages/kbn-ci-stats-performance-metrics @elastic/kibana-operations packages/kbn-ci-stats-reporter @elastic/kibana-operations packages/kbn-ci-stats-shipper-cli @elastic/kibana-operations packages/kbn-cli-dev-mode @elastic/kibana-operations +packages/cloud @elastic/kibana-core x-pack/plugins/cloud_integrations/cloud_chat @elastic/kibana-core x-pack/plugins/cloud_integrations/cloud_chat_provider @elastic/kibana-core x-pack/plugins/cloud_integrations/cloud_data_migration @elastic/platform-onboarding diff --git a/.i18nrc.json b/.i18nrc.json index b5e17c18d3542..4657840019f6c 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -15,6 +15,7 @@ "customIntegrations": "src/plugins/custom_integrations", "customIntegrationsPackage": "packages/kbn-custom-integrations", "dashboard": "src/plugins/dashboard", + "cloud": "packages/cloud", "domDragDrop": "packages/kbn-dom-drag-drop", "controls": "src/plugins/controls", "data": "src/plugins/data", diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index e271eb6cce5c0..fef9ae71a085b 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -54,8 +54,9 @@ Details for each programming language library that Elastic provides are in the https://www.elastic.co/guide/en/elasticsearch/client/index.html[{es} Client documentation]. If you are running {kib} on our hosted {es} Service, -click *View deployment details* on the *Integrations* view +click *Endpoints* on the *Integrations* view to verify your {es} endpoint and Cloud ID, and create API keys for integration. +Alternatively, the *Endpoints* are also accessible through the top bar help menu. [float] === Add sample data diff --git a/package.json b/package.json index 3b7f8c030fc8f..313ee86a8171d 100644 --- a/package.json +++ b/package.json @@ -173,6 +173,7 @@ "@kbn/chart-expressions-common": "link:src/plugins/chart_expressions/common", "@kbn/chart-icons": "link:packages/kbn-chart-icons", "@kbn/charts-plugin": "link:src/plugins/charts", + "@kbn/cloud": "link:packages/cloud", "@kbn/cloud-chat-plugin": "link:x-pack/plugins/cloud_integrations/cloud_chat", "@kbn/cloud-chat-provider-plugin": "link:x-pack/plugins/cloud_integrations/cloud_chat_provider", "@kbn/cloud-data-migration-plugin": "link:x-pack/plugins/cloud_integrations/cloud_data_migration", diff --git a/packages/cloud/README.md b/packages/cloud/README.md new file mode 100644 index 0000000000000..e387c4b9be959 --- /dev/null +++ b/packages/cloud/README.md @@ -0,0 +1,3 @@ +# @kbn/cloud + +Empty package generated by @kbn/generate diff --git a/packages/cloud/deployment_details/deployment_details.tsx b/packages/cloud/deployment_details/deployment_details.tsx new file mode 100644 index 0000000000000..278709f7b6d32 --- /dev/null +++ b/packages/cloud/deployment_details/deployment_details.tsx @@ -0,0 +1,81 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; + +import { + EuiForm, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiButtonEmpty, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useDeploymentDetails } from './services'; +import { DeploymentDetailsEsInput } from './deployment_details_es_input'; +import { DeploymentDetailsCloudIdInput } from './deployment_details_cloudid_input'; + +const hasActiveModifierKey = (event: React.MouseEvent): boolean => { + return event.metaKey || event.altKey || event.ctrlKey || event.shiftKey; +}; + +export const DeploymentDetails = ({ closeModal }: { closeModal?: () => void }) => { + const { cloudId, elasticsearchUrl, managementUrl, learnMoreUrl, navigateToUrl } = + useDeploymentDetails(); + const isInsideModal = !!closeModal; + + if (!cloudId) { + return null; + } + + return ( + + {/* Elastic endpoint */} + {elasticsearchUrl && } + + {/* Cloud ID */} + + + + + {managementUrl && ( + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + { + if (!hasActiveModifierKey(e)) { + e.preventDefault(); + navigateToUrl(managementUrl); + } + if (closeModal) { + closeModal(); + } + }} + flush="left" + > + {i18n.translate('cloud.deploymentDetails.createManageApiKeysButtonLabel', { + defaultMessage: 'Create and manage API keys', + })} + + + {!isInsideModal && ( + + + {i18n.translate('cloud.deploymentDetails.learnMoreButtonLabel', { + defaultMessage: 'Learn more', + })} + + + )} + + )} + + ); +}; diff --git a/packages/cloud/deployment_details/deployment_details_cloudid_input.tsx b/packages/cloud/deployment_details/deployment_details_cloudid_input.tsx new file mode 100644 index 0000000000000..a749fe4371715 --- /dev/null +++ b/packages/cloud/deployment_details/deployment_details_cloudid_input.tsx @@ -0,0 +1,46 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { type FC } from 'react'; +import { + EuiFormRow, + EuiFieldText, + EuiCopy, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const DeploymentDetailsCloudIdInput: FC<{ cloudId: string }> = ({ cloudId }) => { + return ( + + + + + + + + {(copy) => ( + + )} + + + + + ); +}; diff --git a/packages/cloud/deployment_details/deployment_details_es_input.tsx b/packages/cloud/deployment_details/deployment_details_es_input.tsx new file mode 100644 index 0000000000000..2998b5bade543 --- /dev/null +++ b/packages/cloud/deployment_details/deployment_details_es_input.tsx @@ -0,0 +1,48 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { type FC } from 'react'; +import { + EuiFormRow, + EuiFieldText, + EuiCopy, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const DeploymentDetailsEsInput: FC<{ elasticsearchUrl: string }> = ({ + elasticsearchUrl, +}) => { + return ( + + + + + + + + {(copy) => ( + + )} + + + + + ); +}; diff --git a/packages/cloud/deployment_details/deployment_details_modal.tsx b/packages/cloud/deployment_details/deployment_details_modal.tsx new file mode 100644 index 0000000000000..2f3d628c2ca47 --- /dev/null +++ b/packages/cloud/deployment_details/deployment_details_modal.tsx @@ -0,0 +1,69 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { type FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import { useDeploymentDetails } from './services'; +import { DeploymentDetails } from './deployment_details'; + +interface Props { + closeModal: () => void; +} + +export const DeploymentDetailsModal: FC = ({ closeModal }) => { + const { learnMoreUrl } = useDeploymentDetails(); + + return ( + { + closeModal(); + }} + style={{ width: 600 }} + data-test-subj="deploymentDetailsModal" + > + + + {i18n.translate('cloud.deploymentDetails.helpMenuLinks.endpoints', { + defaultMessage: 'Endpoints', + })} + + + + + + + + + + {i18n.translate('cloud.deploymentDetails.modal.learnMoreButtonLabel', { + defaultMessage: 'Learn more', + })} + + + + + {i18n.translate('cloud.deploymentDetails.modal.closeButtonLabel', { + defaultMessage: 'Close', + })} + + + + + + ); +}; diff --git a/packages/cloud/deployment_details/index.ts b/packages/cloud/deployment_details/index.ts new file mode 100644 index 0000000000000..2f37291eecd7c --- /dev/null +++ b/packages/cloud/deployment_details/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { DeploymentDetailsKibanaProvider, DeploymentDetailsProvider } from './services'; +export { DeploymentDetails } from './deployment_details'; +export { DeploymentDetailsModal } from './deployment_details_modal'; diff --git a/packages/cloud/deployment_details/services.tsx b/packages/cloud/deployment_details/services.tsx new file mode 100644 index 0000000000000..c4e8be12bb547 --- /dev/null +++ b/packages/cloud/deployment_details/services.tsx @@ -0,0 +1,123 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC, useContext } from 'react'; + +export interface DeploymentDetailsContextValue { + cloudId?: string; + elasticsearchUrl?: string; + managementUrl?: string; + learnMoreUrl: string; + navigateToUrl(url: string): Promise; +} + +const DeploymentDetailsContext = React.createContext(null); + +/** + * Abstract external service Provider. + */ +export const DeploymentDetailsProvider: FC = ({ + children, + ...services +}) => { + return ( + + {children} + + ); +}; + +/** + * Kibana-specific service types. + */ +export interface DeploymentDetailsKibanaDependencies { + /** CoreStart contract */ + core: { + application: { + navigateToUrl(url: string): Promise; + }; + }; + /** SharePluginStart contract */ + share: { + url: { + locators: { + get( + id: string + ): undefined | { useUrl: (params: { sectionId: string; appId: string }) => string }; + }; + }; + }; + /** CloudSetup contract */ + cloud: { + isCloudEnabled: boolean; + cloudId?: string; + elasticsearchUrl?: string; + }; + /** DocLinksStart contract */ + docLinks: { + links: { + fleet: { + apiKeysLearnMore: string; + }; + }; + }; +} + +/** + * Kibana-specific Provider that maps to known dependency types. + */ +export const DeploymentDetailsKibanaProvider: FC = ({ + children, + ...services +}) => { + const { + core: { + application: { navigateToUrl }, + }, + cloud: { isCloudEnabled, cloudId, elasticsearchUrl }, + share: { + url: { locators }, + }, + docLinks: { + links: { + fleet: { apiKeysLearnMore }, + }, + }, + } = services; + + const managementUrl = locators + .get('MANAGEMENT_APP_LOCATOR') + ?.useUrl({ sectionId: 'security', appId: 'api_keys' }); + + return ( + + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + */ +export function useDeploymentDetails() { + const context = useContext(DeploymentDetailsContext); + + if (!context) { + throw new Error( + 'DeploymentDetailsContext is missing. Ensure your component or React root is wrapped with or .' + ); + } + + return context; +} diff --git a/packages/cloud/jest.config.js b/packages/cloud/jest.config.js new file mode 100644 index 0000000000000..174f01cfc1be6 --- /dev/null +++ b/packages/cloud/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/cloud'], +}; diff --git a/packages/cloud/kibana.jsonc b/packages/cloud/kibana.jsonc new file mode 100644 index 0000000000000..e39a0dbe40617 --- /dev/null +++ b/packages/cloud/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/cloud", + "owner": "@elastic/kibana-core" +} diff --git a/packages/cloud/package.json b/packages/cloud/package.json new file mode 100644 index 0000000000000..8e0023dc5c7a3 --- /dev/null +++ b/packages/cloud/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/cloud", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/cloud/tsconfig.json b/packages/cloud/tsconfig.json new file mode 100644 index 0000000000000..c4703bc51cf6c --- /dev/null +++ b/packages/cloud/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n", + ] +} diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx index e1e43d43ab401..5c3d5bb048737 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx @@ -67,6 +67,7 @@ const buildDefaultContentLinks = ({ defaultMessage: 'Open an issue in GitHub', }), href: docLinks.links.kibana.createGithubIssue, + iconType: 'logoGithub', }, ]; @@ -201,17 +202,40 @@ export class HeaderHelpMenu extends Component { return ( - {defaultContentLinks.map(({ href, title, iconType }, i) => { - const isLast = i === defaultContentLinks.length - 1; - return ( - - - {title} - - {!isLast && } - - ); - })} + {defaultContentLinks.map( + ({ href, title, iconType, onClick: _onClick, dataTestSubj }, i) => { + const isLast = i === defaultContentLinks.length - 1; + + if (href && _onClick) { + throw new Error( + 'Only one of `href` and `onClick` should be provided for the help menu link.' + ); + } + + const hrefProps = href ? { href, target: '_blank' } : {}; + const onClick = () => { + if (!_onClick) return; + _onClick(); + this.closeMenu(); + }; + + return ( + + + {title} + + {!isLast && } + + ); + } + )} ); } diff --git a/packages/core/chrome/core-chrome-browser/src/nav_controls.ts b/packages/core/chrome/core-chrome-browser/src/nav_controls.ts index 39b5d1b3b59b1..22c074862151b 100644 --- a/packages/core/chrome/core-chrome-browser/src/nav_controls.ts +++ b/packages/core/chrome/core-chrome-browser/src/nav_controls.ts @@ -18,8 +18,10 @@ export interface ChromeNavControl { /** @public */ export interface ChromeHelpMenuLink { title: string; - href: string; + href?: string; iconType?: string; + onClick?: () => void; + dataTestSubj?: string; } /** diff --git a/tsconfig.base.json b/tsconfig.base.json index 030b5c9bbed4c..bb2aec9d5819f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -128,6 +128,8 @@ "@kbn/ci-stats-shipper-cli/*": ["packages/kbn-ci-stats-shipper-cli/*"], "@kbn/cli-dev-mode": ["packages/kbn-cli-dev-mode"], "@kbn/cli-dev-mode/*": ["packages/kbn-cli-dev-mode/*"], + "@kbn/cloud": ["packages/cloud"], + "@kbn/cloud/*": ["packages/cloud/*"], "@kbn/cloud-chat-plugin": ["x-pack/plugins/cloud_integrations/cloud_chat"], "@kbn/cloud-chat-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_chat/*"], "@kbn/cloud-chat-provider-plugin": ["x-pack/plugins/cloud_integrations/cloud_chat_provider"], diff --git a/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc index 4b6625f842f79..660f6e64a2446 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc +++ b/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc @@ -14,6 +14,9 @@ ], "requiredBundles": [ "kibanaReact" + ], + "requiredPlugins": [ + "share" ] } } diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.tsx b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.tsx new file mode 100644 index 0000000000000..7c6b23d352f1a --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import type { CoreStart } from '@kbn/core/public'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import { + DeploymentDetailsKibanaProvider, + DeploymentDetailsModal, +} from '@kbn/cloud/deployment_details'; + +interface Props { + closeModal: () => void; + core: CoreStart; + docLinks: DocLinksStart; + cloud: CloudStart; + share: SharePluginStart; +} + +export const EndpointsModal = ({ core, share, cloud, docLinks, closeModal }: Props) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.tsx similarity index 53% rename from x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.ts rename to x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.tsx index 82b0e86e6569a..15270c5876214 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.tsx @@ -4,17 +4,32 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import React from 'react'; import { i18n } from '@kbn/i18n'; import { ChromeHelpMenuLink } from '@kbn/core-chrome-browser'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { CoreStart } from '@kbn/core/public'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; + +import { EndpointsModal } from './endpoints_modal'; export const createHelpMenuLinks = ({ docLinks, helpSupportUrl, + core, + cloud, + share, }: { docLinks: DocLinksStart; + core: CoreStart; + cloud: CloudStart; + share: SharePluginStart; helpSupportUrl: string; }) => { + const { overlays } = core; + const helpMenuLinks: ChromeHelpMenuLink[] = [ { title: i18n.translate('xpack.cloudLinks.helpMenuLinks.documentation', { @@ -34,6 +49,27 @@ export const createHelpMenuLinks = ({ }), href: docLinks.links.kibana.feedback, }, + { + title: i18n.translate('xpack.cloudLinks.helpMenuLinks.endpoints', { + defaultMessage: 'Endpoints', + }), + iconType: 'console', + dataTestSubj: 'endpointsHelpLink', + onClick: () => { + const modal = overlays.openModal( + toMountPoint( + modal.close()} + />, + { theme: core.theme, i18n: core.i18n } + ) + ); + }, + }, ]; return helpMenuLinks; diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts index b9045fdc9a59f..d680d6cce4f4f 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts @@ -8,6 +8,7 @@ import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { coreMock } from '@kbn/core/public/mocks'; import { securityMock } from '@kbn/security-plugin/public/mocks'; +import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; import { maybeAddCloudLinks } from './maybe_add_cloud_links'; @@ -18,6 +19,7 @@ describe('maybeAddCloudLinks', () => { maybeAddCloudLinks({ core, security, + share: sharePluginMock.createStartContract(), cloud: { ...cloudMock.createStart(), isCloudEnabled: false }, }); // Since there's a promise, let's wait for the next tick @@ -35,6 +37,7 @@ describe('maybeAddCloudLinks', () => { maybeAddCloudLinks({ security, core, + share: sharePluginMock.createStartContract(), cloud: { ...cloudMock.createStart(), isCloudEnabled: true }, }); // Since there's a promise, let's wait for the next tick @@ -90,6 +93,12 @@ describe('maybeAddCloudLinks', () => { "href": "https://www.elastic.co/products/kibana/feedback?blade=kibanafeedback", "title": "Give feedback", }, + Object { + "dataTestSubj": "endpointsHelpLink", + "iconType": "console", + "onClick": [Function], + "title": "Endpoints", + }, ], ] `); @@ -103,6 +112,7 @@ describe('maybeAddCloudLinks', () => { maybeAddCloudLinks({ security, core, + share: sharePluginMock.createStartContract(), cloud: { ...cloudMock.createStart(), isCloudEnabled: true }, }); // Since there's a promise, let's wait for the next tick @@ -157,6 +167,12 @@ describe('maybeAddCloudLinks', () => { "href": "https://www.elastic.co/products/kibana/feedback?blade=kibanafeedback", "title": "Give feedback", }, + Object { + "dataTestSubj": "endpointsHelpLink", + "iconType": "console", + "onClick": [Function], + "title": "Endpoints", + }, ], ] `); @@ -172,6 +188,7 @@ describe('maybeAddCloudLinks', () => { maybeAddCloudLinks({ security, core, + share: sharePluginMock.createStartContract(), cloud: { ...cloudMock.createStart(), isCloudEnabled: true }, }); // Since there's a promise, let's wait for the next tick diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts index 33fb4df7bfce2..2772c87d124d3 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { CoreStart } from '@kbn/core/public'; import type { SecurityPluginStart } from '@kbn/security-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; import { createUserMenuLinks } from './user_menu_links'; import { createHelpMenuLinks } from './help_menu_links'; @@ -18,9 +19,10 @@ export interface MaybeAddCloudLinksDeps { core: CoreStart; security: SecurityPluginStart; cloud: CloudStart; + share: SharePluginStart; } -export function maybeAddCloudLinks({ core, security, cloud }: MaybeAddCloudLinksDeps): void { +export function maybeAddCloudLinks({ core, security, cloud, share }: MaybeAddCloudLinksDeps): void { const userObservable = defer(() => security.authc.getCurrentUser()).pipe( // Check if user is a cloud user. map((user) => user.elastic_cloud_user), @@ -54,6 +56,9 @@ export function maybeAddCloudLinks({ core, security, cloud }: MaybeAddCloudLinks const helpMenuLinks = createHelpMenuLinks({ docLinks: core.docLinks, helpSupportUrl, + core, + share, + cloud, }); core.chrome.setHelpMenuLinks(helpMenuLinks); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts index d928b7a6f0e8a..d2f987337a440 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts @@ -11,6 +11,7 @@ import { coreMock } from '@kbn/core/public/mocks'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { securityMock } from '@kbn/security-plugin/public/mocks'; import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; +import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; describe('Cloud Links Plugin - public', () => { let plugin: CloudLinksPlugin; @@ -40,7 +41,11 @@ describe('Cloud Links Plugin - public', () => { coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud, guidedOnboarding }); + plugin.start(coreStart, { + cloud, + guidedOnboarding, + share: sharePluginMock.createStartContract(), + }); expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1); }); @@ -48,14 +53,22 @@ describe('Cloud Links Plugin - public', () => { const coreStart = coreMock.createStart(); coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud, guidedOnboarding }); + plugin.start(coreStart, { + cloud, + guidedOnboarding, + share: sharePluginMock.createStartContract(), + }); expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); }); test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => { const coreStart = coreMock.createStart(); const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; - plugin.start(coreStart, { cloud, guidedOnboarding }); + plugin.start(coreStart, { + cloud, + guidedOnboarding, + share: sharePluginMock.createStartContract(), + }); expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); }); }); @@ -72,7 +85,11 @@ describe('Cloud Links Plugin - public', () => { coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud, guidedOnboarding }); + plugin.start(coreStart, { + cloud, + guidedOnboarding, + share: sharePluginMock.createStartContract(), + }); expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); }); }); @@ -83,7 +100,7 @@ describe('Cloud Links Plugin - public', () => { coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); + plugin.start(coreStart, { cloud, security, share: sharePluginMock.createStartContract() }); expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(1); }); @@ -91,7 +108,7 @@ describe('Cloud Links Plugin - public', () => { const coreStart = coreMock.createStart(); coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud }); + plugin.start(coreStart, { cloud, share: sharePluginMock.createStartContract() }); expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); }); @@ -100,7 +117,7 @@ describe('Cloud Links Plugin - public', () => { coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); + plugin.start(coreStart, { cloud, security, share: sharePluginMock.createStartContract() }); expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); }); @@ -108,7 +125,7 @@ describe('Cloud Links Plugin - public', () => { const coreStart = coreMock.createStart(); coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); const security = securityMock.createStart(); - plugin.start(coreStart, { security }); + plugin.start(coreStart, { security, share: sharePluginMock.createStartContract() }); expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); }); @@ -117,7 +134,7 @@ describe('Cloud Links Plugin - public', () => { coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); + plugin.start(coreStart, { cloud, security, share: sharePluginMock.createStartContract() }); expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); }); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx index 38b568791b70b..bfebe531276d4 100755 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx @@ -11,6 +11,7 @@ import type { CoreStart, Plugin } from '@kbn/core/public'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; import { maybeAddCloudLinks } from './maybe_add_cloud_links'; interface CloudLinksDepsSetup { @@ -21,6 +22,7 @@ interface CloudLinksDepsSetup { interface CloudLinksDepsStart { cloud?: CloudStart; security?: SecurityPluginStart; + share: SharePluginStart; guidedOnboarding?: GuidedOnboardingPluginStart; } @@ -29,7 +31,7 @@ export class CloudLinksPlugin { public setup() {} - public start(core: CoreStart, { cloud, security, guidedOnboarding }: CloudLinksDepsStart) { + public start(core: CoreStart, { cloud, security, guidedOnboarding, share }: CloudLinksDepsStart) { if (cloud?.isCloudEnabled && !core.http.anonymousPaths.isAnonymous(window.location.pathname)) { if (guidedOnboarding?.guidedOnboardingApi?.isEnabled) { core.chrome.registerGlobalHelpExtensionMenuLink({ @@ -42,11 +44,13 @@ export class CloudLinksPlugin priority: 1000, // We want this link to be at the very top. }); } + if (security) { maybeAddCloudLinks({ core, security, cloud, + share, }); } } diff --git a/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json index f1a67895cdd5e..43f411cadf060 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json +++ b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json @@ -23,6 +23,9 @@ "@kbn/user-profile-components", "@kbn/core-lifecycle-browser", "@kbn/kibana-react-plugin", + "@kbn/share-plugin", + "@kbn/cloud", + "@kbn/react-kibana-mount", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx deleted file mode 100644 index 7e3b414cf0f6d..0000000000000 --- a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import styled from 'styled-components'; - -import { - EuiPopover, - EuiText, - EuiForm, - EuiFormRow, - EuiFieldText, - EuiCopy, - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiLink, - EuiHeaderLink, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export interface Props { - cloudId: string; - managementUrl?: string; - learnMoreUrl: string; -} - -const Description = styled(EuiText)` - margin-bottom: ${({ theme }) => theme.eui.euiSizeL}; -`; - -export const DeploymentDetails = ({ cloudId, learnMoreUrl, managementUrl }: Props) => { - const [isOpen, setIsOpen] = React.useState(false); - - const button = ( - setIsOpen(!isOpen)} iconType="iInCircle" iconSide="left" isActive> - {i18n.translate('xpack.fleet.integrations.deploymentButton', { - defaultMessage: 'View deployment details', - })} - - ); - - const management = managementUrl ? ( - - - - Create and manage API keys - - - - Learn more - - - - - ) : null; - - return ( - setIsOpen(false)} - button={button} - anchorPosition="downCenter" - > -
- - {i18n.translate('xpack.fleet.integrations.deploymentDescription', { - defaultMessage: - 'Send data to Elastic from your applications by referencing your deployment.', - })} - - - - - - - - - - {(copy) => ( - - )} - - - - - {management} - -
-
- ); -}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx deleted file mode 100644 index 5b311b3443e36..0000000000000 --- a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { Meta } from '@storybook/react'; -import { EuiHeader } from '@elastic/eui'; - -import { DeploymentDetails as ConnectedComponent } from './deployment_details'; -import type { Props as PureComponentProps } from './deployment_details.component'; -import { DeploymentDetails as PureComponent } from './deployment_details.component'; - -export default { - title: 'Sections/EPM/Deployment Details', - description: '', - decorators: [ - (storyFn) => { - const sections = [{ items: [] }, { items: [storyFn()] }]; - return ; - }, - ], -} as Meta; - -export const DeploymentDetails = () => { - return ; -}; - -DeploymentDetails.args = { - isCloudEnabled: true, -}; - -DeploymentDetails.argTypes = { - isCloudEnabled: { - type: { - name: 'boolean', - }, - defaultValue: true, - control: { - type: 'boolean', - }, - }, -}; - -export const Component = (props: PureComponentProps) => { - return ; -}; - -Component.args = { - cloudId: 'cloud-id', - learnMoreUrl: 'https://learn-more-url', - managementUrl: 'https://management-url', -}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx index 968d596a5f9d5..ea17bc3f201c4 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx @@ -6,13 +6,18 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiPopover, EuiHeaderLink } from '@elastic/eui'; +import { + DeploymentDetailsKibanaProvider, + DeploymentDetails as DeploymentDetailsComponent, +} from '@kbn/cloud/deployment_details'; import { useStartServices } from '../../hooks'; -import { DeploymentDetails as Component } from './deployment_details.component'; - export const DeploymentDetails = () => { - const { share, cloud, docLinks } = useStartServices(); + const [isOpen, setIsOpen] = React.useState(false); + const { share, cloud, docLinks, application } = useStartServices(); // If the cloud plugin isn't enabled, we can't display the flyout. if (!cloud) { @@ -21,16 +26,36 @@ export const DeploymentDetails = () => { const { isCloudEnabled, cloudId } = cloud; - // If cloud isn't enabled or we don't have a cloudId we can't display the flyout. + // If cloud isn't enabled or we don't have a cloudId we don't render the button. if (!isCloudEnabled || !cloudId) { return null; } - const managementUrl = share.url.locators - .get('MANAGEMENT_APP_LOCATOR') - ?.useUrl({ sectionId: 'security', appId: 'api_keys' }); - - const learnMoreUrl = docLinks.links.fleet.apiKeysLearnMore; - - return ; + const button = ( + setIsOpen(!isOpen)} iconType="iInCircle" iconSide="left" isActive> + {i18n.translate('xpack.fleet.integrations.endpointsButton', { + defaultMessage: 'Endpoints', + })} + + ); + + return ( + + setIsOpen(false)} + button={button} + anchorPosition="downCenter" + > +
+ +
+
+
+ ); }; diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index b3f8a96417f9a..58cdfa25d1e08 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -101,5 +101,6 @@ "@kbn/core-saved-objects-base-server-internal", "@kbn/core-http-common", "@kbn/dashboard-plugin", + "@kbn/cloud", ] } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index b468f00d02466..4ae31ffb358ef 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -16425,8 +16425,6 @@ "xpack.fleet.homeIntegration.tutorialModule.noticeText.notePrefix": "Note", "xpack.fleet.initializationErrorMessageTitle": "Initialisation de Fleet impossible", "xpack.fleet.integrations.customInputsLink": "entrées personnalisées", - "xpack.fleet.integrations.deploymentButton": "Voir les détails du déploiement", - "xpack.fleet.integrations.deploymentDescription": "Envoyez des données à Elastic à partir de vos applications en référençant votre déploiement.", "xpack.fleet.integrations.discussForumLink": "forum", "xpack.fleet.integrations.installPackage.uploadedTooltip": "Cette intégration a été installée par le biais d'un chargement et ne peut pas être réinstallée automatiquement. Veuillez la charger à nouveau pour la réinstaller.", "xpack.fleet.integrations.integrationSaved": "Paramètres de l'intégration enregistrés", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7f59456c6bd14..87e23ff29cb1f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -16439,8 +16439,6 @@ "xpack.fleet.homeIntegration.tutorialModule.noticeText.notePrefix": "注", "xpack.fleet.initializationErrorMessageTitle": "Fleet を初期化できません", "xpack.fleet.integrations.customInputsLink": "カスタム入力", - "xpack.fleet.integrations.deploymentButton": "デプロイ詳細の表示", - "xpack.fleet.integrations.deploymentDescription": "デプロイを参照し、アプリケーションのデータをElasticに送信します。", "xpack.fleet.integrations.discussForumLink": "フォーラム", "xpack.fleet.integrations.installPackage.uploadedTooltip": "この統合はアップロードによってインストールされたため、自動的に再インストールできません。再インストールするには、もう一度アップロードしてください。", "xpack.fleet.integrations.integrationSaved": "統合設定が保存されました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8474d0289c239..6e3c128ec642a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -16439,8 +16439,6 @@ "xpack.fleet.homeIntegration.tutorialModule.noticeText.notePrefix": "备注", "xpack.fleet.initializationErrorMessageTitle": "无法初始化 Fleet", "xpack.fleet.integrations.customInputsLink": "定制输入", - "xpack.fleet.integrations.deploymentButton": "查看部署详情", - "xpack.fleet.integrations.deploymentDescription": "通过引用部署,将数据从应用程序发送到 Elastic。", "xpack.fleet.integrations.discussForumLink": "论坛", "xpack.fleet.integrations.installPackage.uploadedTooltip": "此集成通过上传进行安装,因此无法自动重新安装。请再次将其上传,以便重新安装。", "xpack.fleet.integrations.integrationSaved": "已保存集成设置", diff --git a/x-pack/test/functional_cloud/config.ts b/x-pack/test/functional_cloud/config.ts index c3203677631a9..df75e83138ed5 100644 --- a/x-pack/test/functional_cloud/config.ts +++ b/x-pack/test/functional_cloud/config.ts @@ -44,7 +44,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { serverArgs: [ ...functionalConfig.get('kbnTestServer.serverArgs'), `--plugin-path=${samlIdPPlugin}`, - '--xpack.cloud.id=ftr_fake_cloud_id', + // Note: the base64 string in the cloud.id config contains the ES endpoint required in the functional tests + '--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=', '--xpack.cloud.base_url=https://cloud.elastic.co', '--xpack.cloud.deployment_url=/deployments/deploymentId', '--xpack.cloud.organization_url=/organization/organizationId', diff --git a/x-pack/test/functional_cloud/tests/cloud_links.ts b/x-pack/test/functional_cloud/tests/cloud_links.ts index 873cd943ec59d..94f67401c98fd 100644 --- a/x-pack/test/functional_cloud/tests/cloud_links.ts +++ b/x-pack/test/functional_cloud/tests/cloud_links.ts @@ -46,6 +46,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await find.byCssSelector('[data-test-subj="cloudOnboardingSetupGuideLink"]') ).to.not.be(null); }); + + it('A button to open a modal to view the CloudID and ES endpoint is added', async () => { + await PageObjects.common.clickAndValidate('helpMenuButton', 'endpointsHelpLink'); + expect(await find.byCssSelector('[data-test-subj="endpointsHelpLink"]')).to.not.be(null); + + // Open the modal + await PageObjects.common.clickAndValidate('endpointsHelpLink', 'deploymentDetailsModal'); + + const esEndpointInput = await find.byCssSelector( + '[data-test-subj="deploymentDetailsEsEndpoint"]' + ); + const esEndpointValue = await esEndpointInput.getAttribute('value'); + expect(esEndpointValue).to.be('https://ES123abc.hello.com:443'); + + const cloudIdInput = await find.byCssSelector( + '[data-test-subj="deploymentDetailsCloudID"]' + ); + const cloudIdInputValue = await cloudIdInput.getAttribute('value'); + expect(cloudIdInputValue).to.be( + 'ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=' + ); + }); }); it('"Manage this deployment" is appended to the nav list', async () => { diff --git a/yarn.lock b/yarn.lock index 41769ceb9a6ef..9c52f8dd23ae4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3243,6 +3243,10 @@ version "0.0.0" uid "" +"@kbn/cloud@link:packages/cloud": + version "0.0.0" + uid "" + "@kbn/code-editor-mocks@link:packages/shared-ux/code_editor/mocks": version "0.0.0" uid ""