From bb10859e2bdc5300e911cd55d5f65dc5f1d80a26 Mon Sep 17 00:00:00 2001 From: elena-shostak <165678770+elena-shostak@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:40:20 +0200 Subject: [PATCH 01/14] CHIPS cookie support (#188519) ## Summary Added CHIPS support. `statehood` doesn't support partitioned cookie out of the box. We can leverage [contextualize](https://github.com/hapijs/statehood/blob/master/lib/index.js#L35) function in statehood with a trick, modifying `isSameSite` property. `Partitioned` attribute is appended only if embedding is not disabled. ## How to test 1. Add to kibana config: ```yml server.securityResponseHeaders.disableEmbedding: false xpack.security.sameSiteCookies: 'None' xpack.security.secureCookies: true ``` 2. ES and Kibana need to run over https to test that, because of `SameSite=None` settings. Check the `sid` cookie was set with `Partitioned` attribute. `Set-Cookie` header has `Partitioned` attribute. ![Screenshot 2024-07-10 at 17 53 24](https://github.com/user-attachments/assets/5ddbe7c5-8648-4552-8697-504a32a42bda) Stored cookie has a `Partition Key` Screenshot 2024-07-10 at 18 04 13 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios __Fixes: https://github.com/elastic/kibana/issues/180974__ ### Release Notes Added CHIPS cookie support. --------- Co-authored-by: Elastic Machine --- .buildkite/ftr_platform_stateful_configs.yml | 1 + .../src/cookie_session_storage.ts | 20 ++++++- .../src/http_server.test.ts | 3 + .../src/http_server.ts | 9 ++- .../http/cookie_session_storage.test.ts | 57 ++++++++++++------ .../security_api_integration/chips.config.ts | 55 +++++++++++++++++ .../tests/chips/chips_cookie.ts | 60 +++++++++++++++++++ .../tests/chips/index.ts | 14 +++++ 8 files changed, 198 insertions(+), 21 deletions(-) create mode 100644 x-pack/test/security_api_integration/chips.config.ts create mode 100644 x-pack/test/security_api_integration/tests/chips/chips_cookie.ts create mode 100644 x-pack/test/security_api_integration/tests/chips/index.ts diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index f25eaa634e5a3..a0425f766f569 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -314,6 +314,7 @@ enabled: - x-pack/test/security_api_integration/saml.config.ts - x-pack/test/security_api_integration/saml.http2.config.ts - x-pack/test/security_api_integration/saml_cloud.config.ts + - x-pack/test/security_api_integration/chips.config.ts - x-pack/test/security_api_integration/session_idle.config.ts - x-pack/test/security_api_integration/session_invalidate.config.ts - x-pack/test/security_api_integration/session_lifespan.config.ts diff --git a/packages/core/http/core-http-server-internal/src/cookie_session_storage.ts b/packages/core/http/core-http-server-internal/src/cookie_session_storage.ts index ab3dd92aa6f7b..7962e0b40777a 100644 --- a/packages/core/http/core-http-server-internal/src/cookie_session_storage.ts +++ b/packages/core/http/core-http-server-internal/src/cookie_session_storage.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Request, Server } from '@hapi/hapi'; +import { Request, Server, ServerStateCookieOptions } from '@hapi/hapi'; import hapiAuthCookie from '@hapi/cookie'; import type { Logger } from '@kbn/logging'; @@ -16,6 +16,7 @@ import type { SessionStorage, SessionStorageCookieOptions, } from '@kbn/core-http-server'; + import { ensureRawRequest } from '@kbn/core-http-router-server-internal'; class ScopedCookieSessionStorage implements SessionStorage { @@ -76,6 +77,7 @@ export async function createCookieSessionStorageFactory( log: Logger, server: Server, cookieOptions: SessionStorageCookieOptions, + disableEmbedding: boolean, basePath?: string ): Promise> { validateOptions(cookieOptions); @@ -101,6 +103,22 @@ export async function createCookieSessionStorageFactory( clearInvalid: false, isHttpOnly: true, isSameSite: cookieOptions.sameSite ?? false, + contextualize: ( + definition: Omit & { isSameSite: string } + ) => { + /** + * This is a temporary solution to support the Partitioned attribute. + * Statehood performs validation for the params, but only before the contextualize function call. + * Since value for the isSameSite is used directly when making segment, + * we can leverage that to append the Partitioned attribute to the cookie. + * + * Once statehood is updated to support the Partitioned attribute, we can remove this. + * Issue: https://github.com/elastic/kibana/issues/188720 + */ + if (definition.isSameSite === 'None' && definition.isSecure && !disableEmbedding) { + definition.isSameSite = 'None;Partitioned'; + } + }, }, validateFunc: async (req: Request, session: T | T[]) => { const result = cookieOptions.validate(session); diff --git a/packages/core/http/core-http-server-internal/src/http_server.test.ts b/packages/core/http/core-http-server-internal/src/http_server.test.ts index 235fab185e12d..d64e174d9ae86 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.test.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.test.ts @@ -83,6 +83,9 @@ beforeEach(() => { cors: { enabled: false, }, + csp: { + disableEmbedding: true, + }, cdn: {}, shutdownTimeout: moment.duration(500, 'ms'), } as any; diff --git a/packages/core/http/core-http-server-internal/src/http_server.ts b/packages/core/http/core-http-server-internal/src/http_server.ts index 478d1d746bbac..23d112bddfae4 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.ts @@ -290,7 +290,12 @@ export class HttpServer { registerOnPreResponse: this.registerOnPreResponse.bind(this), createCookieSessionStorageFactory: ( cookieOptions: SessionStorageCookieOptions - ) => this.createCookieSessionStorageFactory(cookieOptions, config.basePath), + ) => + this.createCookieSessionStorageFactory( + cookieOptions, + config.csp.disableEmbedding, + config.basePath + ), basePath: basePathService, csp: config.csp, auth: { @@ -553,6 +558,7 @@ export class HttpServer { private async createCookieSessionStorageFactory( cookieOptions: SessionStorageCookieOptions, + disableEmbedding: boolean, basePath?: string ) { if (this.server === undefined) { @@ -569,6 +575,7 @@ export class HttpServer { this.logger.get('http', 'server', this.name, 'cookie-session-storage'), this.server, cookieOptions, + disableEmbedding, basePath ); return sessionStorageFactory; diff --git a/src/core/server/integration_tests/http/cookie_session_storage.test.ts b/src/core/server/integration_tests/http/cookie_session_storage.test.ts index 192a3daf8fd2d..ef2b62ff40a0c 100644 --- a/src/core/server/integration_tests/http/cookie_session_storage.test.ts +++ b/src/core/server/integration_tests/http/cookie_session_storage.test.ts @@ -128,7 +128,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), innerServer, - cookieOptions + cookieOptions, + true ); await server.start(); @@ -166,7 +167,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), innerServer, - cookieOptions + cookieOptions, + true ); await server.start(); @@ -198,7 +200,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), innerServer, - cookieOptions + cookieOptions, + true ); await server.start(); @@ -229,7 +232,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), innerServer, - cookieOptions + cookieOptions, + true ); await server.start(); @@ -275,7 +279,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), innerServer, - cookieOptions + cookieOptions, + true ); await server.start(); @@ -313,7 +318,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), mockServer as any, - cookieOptions + cookieOptions, + true ); expect(mockServer.register).toBeCalledTimes(1); @@ -347,7 +353,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), mockServer as any, - cookieOptions + cookieOptions, + true ); expect(mockServer.register).toBeCalledTimes(1); @@ -379,7 +386,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), mockServer as any, - cookieOptions + cookieOptions, + true ); expect(mockServer.register).toBeCalledTimes(1); @@ -412,7 +420,8 @@ describe('Cookie based SessionStorage', () => { const factory = await createCookieSessionStorageFactory( logger.get(), innerServer, - cookieOptions + cookieOptions, + true ); await server.start(); @@ -440,10 +449,15 @@ describe('Cookie based SessionStorage', () => { const { server: innerServer } = await server.setup(setupDeps); await expect( - createCookieSessionStorageFactory(logger.get(), innerServer, { - ...cookieOptions, - sameSite: 'None', - }) + createCookieSessionStorageFactory( + logger.get(), + innerServer, + { + ...cookieOptions, + sameSite: 'None', + }, + true + ) ).rejects.toThrowErrorMatchingInlineSnapshot( `"\\"SameSite: None\\" requires Secure connection"` ); @@ -465,12 +479,17 @@ describe('Cookie based SessionStorage', () => { return res.ok({ body: { value: sessionValue.value } }); }); - const factory = await createCookieSessionStorageFactory(logger.get(), innerServer, { - ...cookieOptions, - isSecure: true, - name: `sid-${sameSite}`, - sameSite, - }); + const factory = await createCookieSessionStorageFactory( + logger.get(), + innerServer, + { + ...cookieOptions, + isSecure: true, + name: `sid-${sameSite}`, + sameSite, + }, + true + ); await server.start(); const response = await supertest(innerServer.listener).get('/').expect(200); diff --git a/x-pack/test/security_api_integration/chips.config.ts b/x-pack/test/security_api_integration/chips.config.ts new file mode 100644 index 0000000000000..fae73f4c38446 --- /dev/null +++ b/x-pack/test/security_api_integration/chips.config.ts @@ -0,0 +1,55 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; +import { resolve } from 'path'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaAPITestsConfig = await readConfigFile( + require.resolve('../../../test/api_integration/config.js') + ); + const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + + const auditLogPath = resolve(__dirname, './plugins/audit_log/anonymous.log'); + + return { + testFiles: [require.resolve('./tests/chips')], + servers: xPackAPITestsConfig.get('servers'), + security: { disableTestUser: true }, + services: { + ...kibanaAPITestsConfig.get('services'), + ...xPackAPITestsConfig.get('services'), + }, + junit: { + reportName: 'X-Pack Security API Integration Tests (CHIPS)', + }, + + esTestCluster: { ...xPackAPITestsConfig.get('esTestCluster') }, + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + `--server.securityResponseHeaders.disableEmbedding=false`, + `--xpack.security.sameSiteCookies=None`, + `--xpack.security.secureCookies=true`, + `--xpack.security.authc.selector.enabled=false`, + `--xpack.security.authc.providers=${JSON.stringify({ + basic: { basic1: { order: 1 } }, + })}`, + '--xpack.security.audit.enabled=true', + '--xpack.security.audit.appender.type=file', + `--xpack.security.audit.appender.fileName=${auditLogPath}`, + '--xpack.security.audit.appender.layout.type=json', + `--xpack.security.audit.ignore_filters=${JSON.stringify([ + { actions: ['http_request'] }, + { categories: ['database'] }, + ])}`, + ], + }, + }; +} diff --git a/x-pack/test/security_api_integration/tests/chips/chips_cookie.ts b/x-pack/test/security_api_integration/tests/chips/chips_cookie.ts new file mode 100644 index 0000000000000..9a6a811578664 --- /dev/null +++ b/x-pack/test/security_api_integration/tests/chips/chips_cookie.ts @@ -0,0 +1,60 @@ +/* + * 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. + */ + +/* + * 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 { parse as parseCookie } from 'tough-cookie'; +import { adminTestUser } from '@kbn/test'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + + function extractSessionCookie(response: { headers: Record }) { + const cookie = (response.headers['set-cookie'] || []).find((header) => + header.startsWith('sid=') + ); + return cookie ? parseCookie(cookie) : undefined; + } + + describe('CHIPS', () => { + it('accepts valid session cookie', async () => { + const response = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ + providerType: 'basic', + providerName: 'basic1', + currentURL: '/', + params: { username: adminTestUser.username, password: adminTestUser.password }, + }) + .expect(200); + + const cookie = extractSessionCookie(response)!; + + expect(cookie.sameSite).to.eql('none'); + expect(cookie.secure).to.eql(true); + expect(cookie.toString()).contain('Partitioned'); + + const { body: user } = await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', cookie.cookieString()) + .expect(200); + + expect(user.username).to.eql(adminTestUser.username); + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic1' }); + expect(user.authentication_type).to.eql('realm'); + }); + }); +} diff --git a/x-pack/test/security_api_integration/tests/chips/index.ts b/x-pack/test/security_api_integration/tests/chips/index.ts new file mode 100644 index 0000000000000..2379a5feae5d8 --- /dev/null +++ b/x-pack/test/security_api_integration/tests/chips/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('security APIs - CHIPS support', function () { + loadTestFile(require.resolve('./chips_cookie')); + }); +} From dd428342097ece892af861ffa5d2a40e6e463324 Mon Sep 17 00:00:00 2001 From: Tre Date: Mon, 22 Jul 2024 10:47:47 +0100 Subject: [PATCH 02/14] [FTR][kbn-test] Update logging (#188292) ## Summary Redact url auth data from `description` symbol. Drop much from the `AxiosError` object, a field of the `KbnClientRequesterError` class. ## Before ``` ERROR [POST http://elastic_serverless:changeme@localhost:5620/internal/kibana/settings] request failed (attempt=1/3): ERROR [POST http://elastic_serverless:changeme@localhost:5620/internal/kibana/settings] request failed (attempt=2/3): ERROR [POST http://elastic_serverless:changeme@localhost:5620/internal/kibana/settings] request failed (attempt=3/3): ERROR KbnClientRequesterError: [POST http://elastic_serverless:changeme@localhost:5620/internal/kibana/settings] request failed (attempt=3/3): -- and ran out of retries at KbnClientRequester.request (kbn_client_requester.ts:172:15) at processTicksAndRejections (node:internal/process/task_queues:95:5) at KbnClientUiSettings.update (kbn_client_ui_settings.ts:91:5) at kibana_server.ts:30:7 at lifecycle_phase.ts:76:11 at async Promise.all (index 1) at LifecyclePhase.trigger (lifecycle_phase.ts:73:5) at functional_test_runner.ts:114:7 at FunctionalTestRunner.runHarness (functional_test_runner.ts:252:14) at FunctionalTestRunner.run (functional_test_runner.ts:48:12) at log.defaultLevel (cli.ts:112:32) at run.ts:73:10 at withProcRunner (with_proc_runner.ts:29:5) at run (run.ts:71:5) ``` ## After ``` ERROR Requesting url (redacted): [http://localhost:5620/internal/kibana/settings] ERROR Requesting url (redacted): [http://localhost:5620/internal/kibana/settings] ERROR Requesting url (redacted): [http://localhost:5620/internal/kibana/settings] ERROR KbnClientRequesterError: [POST - http://localhost:5620/internal/kibana/settings] request failed (attempt=3/3): -- and ran out of retries at KbnClientRequester.request (kbn_client_requester.ts:131:15) at processTicksAndRejections (node:internal/process/task_queues:95:5) at KbnClientUiSettings.update (kbn_client_ui_settings.ts:91:5) at kibana_server.ts:30:7 at lifecycle_phase.ts:76:11 at async Promise.all (index 1) at LifecyclePhase.trigger (lifecycle_phase.ts:73:5) at functional_test_runner.ts:114:7 at FunctionalTestRunner.runHarness (functional_test_runner.ts:252:14) at FunctionalTestRunner.run (functional_test_runner.ts:48:12) at log.defaultLevel (cli.ts:112:32) at run.ts:73:10 at withProcRunner (with_proc_runner.ts:29:5) at run (run.ts:71:5) ``` --------- Co-authored-by: Elastic Machine --- .../kbn_client/kbn_client_requester.test.ts | 24 ++-- .../src/kbn_client/kbn_client_requester.ts | 115 +++++++++++------- .../kbn_client/kbn_client_requester_error.ts | 13 +- 3 files changed, 97 insertions(+), 55 deletions(-) diff --git a/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts b/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts index bb2f923ad1f01..832effd81959d 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts @@ -6,30 +6,40 @@ * Side Public License, v 1. */ -import { pathWithSpace } from './kbn_client_requester'; +import { pathWithSpace, redactUrl } from './kbn_client_requester'; -describe('pathWithSpace()', () => { - it('adds a space to the path', () => { +describe('KBN Client Requester Functions', () => { + it('pathWithSpace() adds a space to the path', () => { expect(pathWithSpace('hello')`/foo/bar`).toMatchInlineSnapshot(`"/s/hello/foo/bar"`); }); - it('ignores the space when it is empty', () => { + it('pathWithSpace() ignores the space when it is empty', () => { expect(pathWithSpace(undefined)`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); expect(pathWithSpace('')`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); }); - it('ignores the space when it is the default space', () => { + it('pathWithSpace() ignores the space when it is the default space', () => { expect(pathWithSpace('default')`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); }); - it('uriencodes variables in the path', () => { + it('pathWithSpace() uriencodes variables in the path', () => { expect(pathWithSpace('space')`hello/${'funky/username🏴‍☠️'}`).toMatchInlineSnapshot( `"/s/space/hello/funky%2Fusername%F0%9F%8F%B4%E2%80%8D%E2%98%A0%EF%B8%8F"` ); }); - it('ensures the path always starts with a slash', () => { + it('pathWithSpace() ensures the path always starts with a slash', () => { expect(pathWithSpace('foo')`hello/world`).toMatchInlineSnapshot(`"/s/foo/hello/world"`); expect(pathWithSpace()`hello/world`).toMatchInlineSnapshot(`"/hello/world"`); }); + + it(`redactUrl() takes a string such as 'http://some-user:some-password@localhost:5620' and returns the url without the auth info`, () => { + expect( + redactUrl( + 'http://testing-internal:someawesomepassword@localhost:5620/internal/ftr/kbn_client_so/task/serverless-security%3Anlp-cleanup-task%3A1.0.0' + ) + ).toEqual( + 'http://localhost:5620/internal/ftr/kbn_client_so/task/serverless-security%3Anlp-cleanup-task%3A1.0.0' + ); + }); }); diff --git a/packages/kbn-test/src/kbn_client/kbn_client_requester.ts b/packages/kbn-test/src/kbn_client/kbn_client_requester.ts index 1f9718e06794c..2c81f833888f6 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_requester.ts @@ -116,61 +116,90 @@ export class KbnClientRequester { async request(options: ReqOptions): Promise> { const url = this.resolveUrl(options.path); - const description = options.description || `${options.method} ${url}`; + const redacted = redactUrl(url); let attempt = 0; const maxAttempts = options.retries ?? DEFAULT_MAX_ATTEMPTS; + const msgOrThrow = errMsg({ + redacted, + attempt, + maxAttempts, + requestedRetries: options.retries !== undefined, + failedToGetResponseSvc: (error: Error) => isAxiosRequestError(error), + ...options, + }); while (true) { attempt += 1; - try { - const response = await Axios.request({ - method: options.method, - url, - data: options.body, - params: options.query, - headers: { - ...options.headers, - 'kbn-xsrf': 'kbn-client', - 'x-elastic-internal-origin': 'kbn-client', - }, - httpsAgent: this.httpsAgent, - responseType: options.responseType, - // work around https://github.com/axios/axios/issues/2791 - transformResponse: options.responseType === 'text' ? [(x) => x] : undefined, - maxContentLength: 30000000, - maxBodyLength: 30000000, - paramsSerializer: (params) => Qs.stringify(params), - }); - - return response; + this.log.info(`Requesting url (redacted): [${redacted}]`); + return await Axios.request(buildRequest(url, this.httpsAgent, options)); } catch (error) { - const conflictOnGet = isConcliftOnGetError(error); - const requestedRetries = options.retries !== undefined; - const failedToGetResponse = isAxiosRequestError(error); - - if (isIgnorableError(error, options.ignoreErrors)) { - return error.response; - } - - let errorMessage; - if (conflictOnGet) { - errorMessage = `Conflict on GET (path=${options.path}, attempt=${attempt}/${maxAttempts})`; - this.log.error(errorMessage); - } else if (requestedRetries || failedToGetResponse) { - errorMessage = `[${description}] request failed (attempt=${attempt}/${maxAttempts}): ${error.message}`; - this.log.error(errorMessage); - } else { - throw error; - } - + if (isIgnorableError(error, options.ignoreErrors)) return error.response; if (attempt < maxAttempts) { await delay(1000 * attempt); continue; } - - throw new KbnClientRequesterError(`${errorMessage} -- and ran out of retries`, error); + throw new KbnClientRequesterError(`${msgOrThrow(error)} -- and ran out of retries`, error); } } } } + +export function errMsg({ + redacted, + attempt, + maxAttempts, + requestedRetries, + failedToGetResponseSvc, + path, + method, + description, +}: ReqOptions & { + redacted: string; + attempt: number; + maxAttempts: number; + requestedRetries: boolean; + failedToGetResponseSvc: (x: Error) => boolean; +}) { + return function errMsgOrReThrow(_: any) { + const result = isConcliftOnGetError(_) + ? `Conflict on GET (path=${path}, attempt=${attempt}/${maxAttempts})` + : requestedRetries || failedToGetResponseSvc(_) + ? `[${ + description || `${method} - ${redacted}` + }] request failed (attempt=${attempt}/${maxAttempts}): ${_?.code}` + : ''; + if (result === '') throw _; + return result; + }; +} + +export function redactUrl(_: string): string { + const url = new URL(_); + return url.password ? `${url.protocol}//${url.host}${url.pathname}` : _; +} + +export function buildRequest( + url: any, + httpsAgent: Https.Agent | null, + { method, body, query, headers, responseType }: any +) { + return { + method, + url, + data: body, + params: query, + headers: { + ...headers, + 'kbn-xsrf': 'kbn-client', + 'x-elastic-internal-origin': 'kbn-client', + }, + httpsAgent, + responseType, + // work around https://github.com/axios/axios/issues/2791 + transformResponse: responseType === 'text' ? [(x: any) => x] : undefined, + maxContentLength: 30000000, + maxBodyLength: 30000000, + paramsSerializer: (params: any) => Qs.stringify(params), + }; +} diff --git a/packages/kbn-test/src/kbn_client/kbn_client_requester_error.ts b/packages/kbn-test/src/kbn_client/kbn_client_requester_error.ts index d338b24cd16ad..186687a71289a 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_requester_error.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_requester_error.ts @@ -7,15 +7,18 @@ */ import { AxiosError } from 'axios'; - export class KbnClientRequesterError extends Error { axiosError?: AxiosError; constructor(message: string, error: unknown) { super(message); this.name = 'KbnClientRequesterError'; - - if (error instanceof AxiosError) { - this.axiosError = error; - } + if (error instanceof AxiosError) this.axiosError = clean(error); } } +function clean(error: Error): AxiosError { + const _ = AxiosError.from(error); + delete _.config; + delete _.request; + delete _.response; + return _; +} From d9c651f20a629f4489ebd8df86ee7d2097670efe Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 22 Jul 2024 12:06:21 +0200 Subject: [PATCH 03/14] [SLO] Fix slo manifest (#187139) ## Summary Fix slo manifest !! `node scripts/plugin_check --dependencies slo ` image --- .../observability_solution/slo/kibana.jsonc | 6 ++---- .../slo/public/embeddable/slo/alerts/types.ts | 19 +----------------- .../components/events_chart_panel.tsx | 14 ++++++++----- .../components/common/documents_table.tsx | 20 +++++++++++++------ .../common/good_bad_events_chart.tsx | 2 +- .../slo/public/plugin.ts | 7 ++----- .../slo/public/types.ts | 18 ++--------------- .../slo/public/utils/slo/get_discover_link.ts | 8 ++++---- .../slo/server/plugin.ts | 2 +- .../observability_solution/slo/tsconfig.json | 2 -- 10 files changed, 36 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/observability_solution/slo/kibana.jsonc b/x-pack/plugins/observability_solution/slo/kibana.jsonc index c03e89b691255..c00145f96362e 100644 --- a/x-pack/plugins/observability_solution/slo/kibana.jsonc +++ b/x-pack/plugins/observability_solution/slo/kibana.jsonc @@ -15,8 +15,6 @@ "alerting", "cases", "charts", - "contentManagement", - "controls", "dashboard", "data", "dataViews", @@ -27,7 +25,6 @@ "observability", "observabilityShared", "ruleRegistry", - "security", "taskManager", "triggersActionsUi", "share", @@ -37,7 +34,7 @@ "presentationUtil", "features", "licensing", - "usageCollection", + "usageCollection" ], "optionalPlugins": [ "cloud", @@ -47,6 +44,7 @@ "observabilityAIAssistant" ], "requiredBundles": [ + "controls", "kibanaReact", "kibanaUtils", "unifiedSearch", diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/types.ts b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/types.ts index d3cb4629584ff..c411514e7269d 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/types.ts +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { DefaultEmbeddableApi, EmbeddableInput } from '@kbn/embeddable-plugin/public'; +import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; import { type CoreStart, IUiSettingsClient, @@ -15,7 +15,6 @@ import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui- import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { CasesPublicStart } from '@kbn/cases-plugin/public'; import { SettingsStart } from '@kbn/core-ui-settings-browser'; -import { SecurityPluginStart } from '@kbn/security-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { ServerlessPluginStart } from '@kbn/serverless/public'; @@ -23,7 +22,6 @@ import { SerializedTitles, PublishesWritablePanelTitle, PublishesPanelTitle, - EmbeddableApiContext, HasEditCapabilities, } from '@kbn/presentation-publishing'; import { ObservabilityPublicStart } from '@kbn/observability-plugin/public'; @@ -40,8 +38,6 @@ export interface EmbeddableSloProps { showAllGroupByInstances?: boolean; } -export type SloAlertsEmbeddableInput = EmbeddableInput & EmbeddableSloProps; - export type SloAlertsEmbeddableState = SerializedTitles & EmbeddableSloProps; export type SloAlertsApi = DefaultEmbeddableApi & @@ -55,18 +51,6 @@ export interface HasSloAlertsConfig { updateSloAlertsConfig: (next: EmbeddableSloProps) => void; } -export const apiHasSloAlertsConfig = (api: unknown | null): api is HasSloAlertsConfig => { - return Boolean( - api && - typeof (api as HasSloAlertsConfig).getSloAlertsConfig === 'function' && - typeof (api as HasSloAlertsConfig).updateSloAlertsConfig === 'function' - ); -}; - -export type SloAlertsEmbeddableActionContext = EmbeddableApiContext & { - embeddable: SloAlertsApi; -}; - export interface SloEmbeddableDeps { uiSettings: IUiSettingsClient; http: CoreStart['http']; @@ -78,7 +62,6 @@ export interface SloEmbeddableDeps { notifications: NotificationsStart; cases: CasesPublicStart; settings: SettingsStart; - security: SecurityPluginStart; charts: ChartsPluginStart; uiActions: UiActionsStart; serverless?: ServerlessPluginStart; diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx index d61428ef6fb33..cd0251fd5ab64 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx @@ -104,11 +104,15 @@ export function EventsChartPanel({ slo, range, selectedTabId, onBrushed }: Props diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx index 6d21c3331ed3e..ed250da853f9e 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx @@ -16,12 +16,12 @@ import { EuiResizableContainer, EuiProgress, EuiCallOut, EuiSpacer } from '@elas import { buildFilter, FILTERS, TimeRange } from '@kbn/es-query'; import { FieldPath, useFormContext } from 'react-hook-form'; import { Serializable } from '@kbn/utility-types'; -import { useFieldSidebar } from './use_field_sidebar'; -import { useTableDocs } from './use_table_docs'; -import { SearchBarProps } from './query_builder'; -import { QuerySearchBar } from './query_search_bar'; -import { CreateSLOForm } from '../../types'; import { useKibana } from '../../../../utils/kibana_react'; +import { CreateSLOForm } from '../../types'; +import { QuerySearchBar } from './query_search_bar'; +import { SearchBarProps } from './query_builder'; +import { useTableDocs } from './use_table_docs'; +import { useFieldSidebar } from './use_field_sidebar'; export function DocumentsTable({ dataView, @@ -131,7 +131,15 @@ export function DocumentsTable({ } } }} - services={services} + services={{ + theme: services.theme, + fieldFormats: services.fieldFormats, + uiSettings: services.uiSettings, + dataViewFieldEditor: services.dataViewFieldEditor, + toastNotifications: services.notifications.toasts, + storage: services.storage, + data: services.data, + }} ariaLabelledBy={i18n.translate('xpack.slo.edit.documentsTableAriaLabel', { defaultMessage: 'Documents table', })} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx index 5f9cb4daa9dcc..601ac64372595 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx @@ -89,7 +89,7 @@ export function GoodBadEventsChart({ to: moment(datum.x).add(intervalInMilliseconds, 'ms').toISOString(), mode: 'absolute' as const, }; - openInDiscover(discover, slo, isBad, !isBad, timeRange); + openInDiscover(slo, isBad, !isBad, timeRange, discover); } }; diff --git a/x-pack/plugins/observability_solution/slo/public/plugin.ts b/x-pack/plugins/observability_solution/slo/public/plugin.ts index 5748a55c21489..8147512e019e9 100644 --- a/x-pack/plugins/observability_solution/slo/public/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/public/plugin.ts @@ -56,7 +56,6 @@ export class SloPlugin const mount = async (params: AppMountParameters) => { const { renderApp } = await import('./application'); const [coreStart, pluginsStart] = await coreSetup.getStartServices(); - const { ruleTypeRegistry, actionTypeRegistry } = pluginsStart.triggersActionsUi; const { observabilityRuleTypeRegistry } = pluginsStart.observability; return renderApp({ @@ -67,7 +66,7 @@ export class SloPlugin kibanaVersion, usageCollection: pluginsSetup.usageCollection, ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate, - plugins: { ...pluginsStart, ruleTypeRegistry, actionTypeRegistry }, + plugins: pluginsStart, isServerless: !!pluginsStart.serverless, experimentalFeatures: this.experimentalFeatures, }); @@ -156,8 +155,6 @@ export class SloPlugin public start(coreStart: CoreStart, pluginsStart: SloPublicPluginsStart) { const kibanaVersion = this.initContext.env.packageInfo.version; - const { ruleTypeRegistry, actionTypeRegistry } = pluginsStart.triggersActionsUi; - return { getCreateSLOFlyout: getCreateSLOFlyoutLazy({ core: coreStart, @@ -165,7 +162,7 @@ export class SloPlugin kibanaVersion, observabilityRuleTypeRegistry: pluginsStart.observability.observabilityRuleTypeRegistry, ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate, - plugins: { ...pluginsStart, ruleTypeRegistry, actionTypeRegistry }, + plugins: pluginsStart, isServerless: !!pluginsStart.serverless, experimentalFeatures: this.experimentalFeatures, }), diff --git a/x-pack/plugins/observability_solution/slo/public/types.ts b/x-pack/plugins/observability_solution/slo/public/types.ts index 76e2bcad185ba..9e730bd429541 100644 --- a/x-pack/plugins/observability_solution/slo/public/types.ts +++ b/x-pack/plugins/observability_solution/slo/public/types.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ToastsStart } from '@kbn/core/public'; import { ObservabilityPublicSetup, ObservabilityPublicStart, @@ -22,7 +21,6 @@ import type { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; -import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import type { LicensingPluginSetup } from '@kbn/licensing-plugin/public'; import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; @@ -31,10 +29,6 @@ import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/ import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { - ActionTypeRegistryContract, - RuleTypeRegistryContract, -} from '@kbn/triggers-actions-ui-plugin/public'; import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { UsageCollectionSetup, @@ -44,12 +38,10 @@ import { ObservabilityAIAssistantPublicSetup, ObservabilityAIAssistantPublicStart, } from '@kbn/observability-ai-assistant-plugin/public'; -import { SecurityPluginStart } from '@kbn/security-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import type { IUiSettingsClient } from '@kbn/core/public'; import { CasesPublicStart } from '@kbn/cases-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; @@ -67,13 +59,12 @@ export interface SloPublicPluginsSetup { embeddable: EmbeddableSetup; uiActions: UiActionsSetup; serverless?: ServerlessPluginSetup; - presentationUtil?: PresentationUtilPluginStart; + presentationUtil: PresentationUtilPluginStart; observabilityAIAssistant?: ObservabilityAIAssistantPublicSetup; usageCollection: UsageCollectionSetup; } export interface SloPublicPluginsStart { - actionTypeRegistry: ActionTypeRegistryContract; aiops: AiopsPluginStart; cases: CasesPublicStart; cloud?: CloudStart; @@ -83,8 +74,6 @@ export interface SloPublicPluginsStart { observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; - navigation: NavigationPublicPluginStart; - security: SecurityPluginStart; spaces?: SpacesPluginStart; share: SharePluginStart; licensing: LicensingPluginStart; @@ -94,16 +83,13 @@ export interface SloPublicPluginsStart { serverless?: ServerlessPluginStart; data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; - ruleTypeRegistry: RuleTypeRegistryContract; observabilityAIAssistant?: ObservabilityAIAssistantPublicStart; lens: LensPublicStart; charts: ChartsPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; - uiSettings: IUiSettingsClient; usageCollection: UsageCollectionStart; - discover: DiscoverStart; + discover?: DiscoverStart; dataViewFieldEditor: DataViewFieldEditorStart; - toastNotifications: ToastsStart; } export type SloPublicSetup = ReturnType; diff --git a/x-pack/plugins/observability_solution/slo/public/utils/slo/get_discover_link.ts b/x-pack/plugins/observability_solution/slo/public/utils/slo/get_discover_link.ts index 6166a56ae73ba..80ef9c12ee99e 100644 --- a/x-pack/plugins/observability_solution/slo/public/utils/slo/get_discover_link.ts +++ b/x-pack/plugins/observability_solution/slo/public/utils/slo/get_discover_link.ts @@ -145,20 +145,20 @@ function createDiscoverLocator( } export function getDiscoverLink( - discover: DiscoverStart, slo: SLOWithSummaryResponse, - timeRange: TimeRange + timeRange: TimeRange, + discover?: DiscoverStart ) { const config = createDiscoverLocator(slo, false, false, timeRange); return discover?.locator?.getRedirectUrl(config); } export function openInDiscover( - discover: DiscoverStart, slo: SLOWithSummaryResponse, showBad = false, showGood = false, - timeRange?: TimeRange + timeRange?: TimeRange, + discover?: DiscoverStart ) { const config = createDiscoverLocator(slo, showBad, showGood, timeRange); discover?.locator?.navigate(config); diff --git a/x-pack/plugins/observability_solution/slo/server/plugin.ts b/x-pack/plugins/observability_solution/slo/server/plugin.ts index 9c986a1f5fa8d..76e20c45630ad 100644 --- a/x-pack/plugins/observability_solution/slo/server/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/server/plugin.ts @@ -53,7 +53,7 @@ export interface PluginSetup { taskManager: TaskManagerSetupContract; spaces?: SpacesPluginSetup; cloud?: CloudSetup; - usageCollection?: UsageCollectionSetup; + usageCollection: UsageCollectionSetup; } export interface PluginStart { diff --git a/x-pack/plugins/observability_solution/slo/tsconfig.json b/x-pack/plugins/observability_solution/slo/tsconfig.json index 25d6c32b6d0db..e4df98f918c9a 100644 --- a/x-pack/plugins/observability_solution/slo/tsconfig.json +++ b/x-pack/plugins/observability_solution/slo/tsconfig.json @@ -17,7 +17,6 @@ "@kbn/i18n-react", "@kbn/shared-ux-router", "@kbn/core", - "@kbn/navigation-plugin", "@kbn/translations-plugin", "@kbn/rule-data-utils", "@kbn/triggers-actions-ui-plugin", @@ -38,7 +37,6 @@ "@kbn/cases-plugin", "@kbn/data-plugin", "@kbn/core-ui-settings-browser", - "@kbn/security-plugin", "@kbn/charts-plugin", "@kbn/ui-actions-plugin", "@kbn/serverless", From acf25bc64dec925818bb277c95d53c3ef1955e4a Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid Date: Mon, 22 Jul 2024 12:18:36 +0200 Subject: [PATCH 04/14] [Logs Explorer] Fixe flaky virtual column popover actions e2e tests (#188773) The PR attempts to fix the flakiness in the e2e tests by avoiding clicks on an already opened popover. The click statement within `retry.tryForTime` can be called in succession, which could inadvertently close the popover, which we want to avoid in this case. The screenshot from failed tests suggests that the assertion is made on a closed down popover: ![image](https://github.com/user-attachments/assets/bd3a9e2c-c292-47db-be89-b4f0a35911f9) --- .../columns_selection.ts | 33 ++++++++++++------- .../columns_selection.ts | 33 ++++++++++++------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts index 2ea761cb9913c..b944ebd9b8306 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts @@ -184,12 +184,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.tryForTime(TEST_TIMEOUT, async () => { const cellElement = await dataGrid.getCellElement(0, 4); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - await logLevelChip.click(); + + const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; + // Open popover if not already open + if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { + await logLevelChip.click(); + } // Find Filter In button - const filterInButton = await testSubjects.find( - 'dataTableCellAction_addToFilterAction_log.level' - ); + const filterInButton = await testSubjects.find(actionSelector); await filterInButton.click(); const rowWithLogLevelInfo = await testSubjects.findAll('*logLevelBadge-'); @@ -202,12 +205,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.tryForTime(TEST_TIMEOUT, async () => { const cellElement = await dataGrid.getCellElement(0, 4); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - await logLevelChip.click(); + + const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; + // Open popover if not already open + if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { + await logLevelChip.click(); + } // Find Filter Out button - const filterOutButton = await testSubjects.find( - 'dataTableCellAction_removeFromFilterAction_log.level' - ); + const filterOutButton = await testSubjects.find(actionSelector); await filterOutButton.click(); await testSubjects.missingOrFail('*logLevelBadge-'); @@ -220,12 +226,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); - await serviceNameChip.click(); + + const actionSelector = 'dataTableCellAction_addToFilterAction_service.name'; + // Open popover if not already open + if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { + await serviceNameChip.click(); + } // Find Filter In button - const filterInButton = await testSubjects.find( - 'dataTableCellAction_addToFilterAction_service.name' - ); + const filterInButton = await testSubjects.find(actionSelector); await filterInButton.click(); const rowWithLogLevelInfo = await testSubjects.findAll( diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts index 98a9edf649a00..0a10b95a27e5b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts @@ -185,12 +185,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.tryForTime(TEST_TIMEOUT, async () => { const cellElement = await dataGrid.getCellElement(0, 4); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - await logLevelChip.click(); + + const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; + // Open popover if not already open + if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { + await logLevelChip.click(); + } // Find Filter In button - const filterInButton = await testSubjects.find( - 'dataTableCellAction_addToFilterAction_log.level' - ); + const filterInButton = await testSubjects.find(actionSelector); await filterInButton.click(); const rowWithLogLevelInfo = await testSubjects.findAll('*logLevelBadge-'); @@ -203,12 +206,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.tryForTime(TEST_TIMEOUT, async () => { const cellElement = await dataGrid.getCellElement(0, 4); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - await logLevelChip.click(); + + const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; + // Open popover if not already open + if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { + await logLevelChip.click(); + } // Find Filter Out button - const filterOutButton = await testSubjects.find( - 'dataTableCellAction_removeFromFilterAction_log.level' - ); + const filterOutButton = await testSubjects.find(actionSelector); await filterOutButton.click(); await testSubjects.missingOrFail('*logLevelBadge-'); @@ -221,12 +227,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); - await serviceNameChip.click(); + + const actionSelector = 'dataTableCellAction_addToFilterAction_service.name'; + // Open popover if not already open + if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { + await serviceNameChip.click(); + } // Find Filter In button - const filterInButton = await testSubjects.find( - 'dataTableCellAction_addToFilterAction_service.name' - ); + const filterInButton = await testSubjects.find(actionSelector); await filterInButton.click(); const rowWithLogLevelInfo = await testSubjects.findAll( From 47842f9c4342ba8380d5f53c05ebc378df429e27 Mon Sep 17 00:00:00 2001 From: Tre Date: Mon, 22 Jul 2024 11:41:53 +0100 Subject: [PATCH 05/14] [SKIP ON MKI] x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts (#188818) ## Summary see details: https://github.com/elastic/kibana/issues/188816 --- .../functional/test_suites/common/discover/esql/_esql_view.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index 598b89bb0bff1..53d52aa162ecc 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -36,6 +36,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; describe('discover esql view', async function () { + // see details: https://github.com/elastic/kibana/issues/188816 + this.tags(['failsOnMKI']); + before(async () => { await kibanaServer.savedObjects.cleanStandardList(); log.debug('load kibana index with default index pattern'); From aa6aa2686641b428730f64c65a30003031d39c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Zaffarano?= Date: Mon, 22 Jul 2024 12:56:27 +0200 Subject: [PATCH 06/14] [Telemetry][Security Solution] Enrich endpoint alerts with license info (#188760) --- .../server/integration_tests/lib/helpers.ts | 4 +-- .../integration_tests/telemetry.test.ts | 27 +++++++++++++++---- .../server/lib/telemetry/async_sender.ts | 10 ++++++- .../server/lib/telemetry/receiver.ts | 8 ++++++ .../server/lib/telemetry/tasks/diagnostic.ts | 5 ++-- 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/server/integration_tests/lib/helpers.ts b/x-pack/plugins/security_solution/server/integration_tests/lib/helpers.ts index 4cf1b69e0873a..ccc73435636e9 100644 --- a/x-pack/plugins/security_solution/server/integration_tests/lib/helpers.ts +++ b/x-pack/plugins/security_solution/server/integration_tests/lib/helpers.ts @@ -22,8 +22,8 @@ const asyncUnlink = Util.promisify(Fs.unlink); */ export async function eventually( cb: () => Promise, - duration: number = 60000, - interval: number = 1000 + duration: number = 120000, + interval: number = 3000 ) { let elapsed = 0; diff --git a/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts index 36284722cbf59..7a38948c0c46d 100644 --- a/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts +++ b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts @@ -148,8 +148,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/187719 - describe.skip('detection-rules', () => { + describe('detection-rules', () => { it('should execute when scheduled', async () => { await mockAndScheduleDetectionRulesTask(); @@ -263,7 +262,7 @@ describe('telemetry tasks', () => { // wait until the events are sent to the telemetry server const body = await eventually(async () => { const found = mockedAxiosPost.mock.calls.find(([url]) => { - return url.startsWith(ENDPOINT_STAGING) && url.endsWith('alerts-endpoint'); + return url.startsWith(ENDPOINT_STAGING) && url.endsWith(TelemetryChannel.ENDPOINT_ALERTS); }); expect(found).not.toBeFalsy(); @@ -274,6 +273,25 @@ describe('telemetry tasks', () => { expect(body).not.toBeFalsy(); expect(body.Endpoint).not.toBeFalsy(); }); + + it('should enrich with license info', async () => { + await mockAndScheduleEndpointDiagnosticsTask(); + + // wait until the events are sent to the telemetry server + const body = await eventually(async () => { + const found = mockedAxiosPost.mock.calls.find(([url]) => { + return url.startsWith(ENDPOINT_STAGING) && url.endsWith(TelemetryChannel.ENDPOINT_ALERTS); + }); + + expect(found).not.toBeFalsy(); + + return JSON.parse((found ? found[1] : '{}') as string); + }); + + expect(body).not.toBeFalsy(); + expect(body.license).not.toBeFalsy(); + expect(body.license.status).not.toBeFalsy(); + }); }); describe('endpoint-meta-telemetry', () => { @@ -680,8 +698,7 @@ describe('telemetry tasks', () => { expect(body.file).toStrictEqual(alertsDetectionsRequest.file); }); - // Flaky: https://github.com/elastic/kibana/issues/188234 - it.skip('should manage runtime errors searching endpoint metrics', async () => { + it('should manage runtime errors searching endpoint metrics', async () => { const errorMessage = 'Something went wront'; async function* mockedGenerator( diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts index 07e098b1cc979..6c2def2abb61d 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/async_sender.ts @@ -21,7 +21,7 @@ import { TelemetryChannel, TelemetryCounter } from './types'; import * as collections from './collections_helpers'; import { CachedSubject, retryOnError$ } from './rxjs_helpers'; import { SenderUtils } from './sender_helpers'; -import { newTelemetryLogger } from './helpers'; +import { copyLicenseFields, newTelemetryLogger } from './helpers'; import { type TelemetryLogger } from './telemetry_logger'; export const DEFAULT_QUEUE_CONFIG: QueueConfig = { @@ -291,6 +291,14 @@ export class AsyncTelemetryEventsSender implements IAsyncTelemetryEventsSender { }; } + if (event.channel === TelemetryChannel.ENDPOINT_ALERTS) { + const licenseInfo = this.telemetryReceiver?.getLicenseInfo(); + additional = { + ...additional, + ...(licenseInfo ? { license: copyLicenseFields(licenseInfo) } : {}), + }; + } + event.payload = { ...event.payload, ...additional, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index ebff5655d99e0..22f85d19c83d8 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -102,6 +102,8 @@ export interface ITelemetryReceiver { fetchClusterInfo(): Promise; + getLicenseInfo(): Nullable; + fetchLicenseInfo(): Promise>; closePointInTime(pitId: string): Promise; @@ -248,6 +250,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { private getIndexForType?: (type: string) => string; private alertsIndex?: string; private clusterInfo?: ESClusterInfo; + private licenseInfo?: Nullable; private processTreeFetcher?: Fetcher; private packageService?: PackageService; private experimentalFeatures: ExperimentalFeatures | undefined; @@ -280,6 +283,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { this.soClient = core?.savedObjects.createInternalRepository() as unknown as SavedObjectsClientContract; this.clusterInfo = await this.fetchClusterInfo(); + this.licenseInfo = await this.fetchLicenseInfo(); this.experimentalFeatures = endpointContextService?.experimentalFeatures; const elasticsearch = core?.elasticsearch.client as unknown as IScopedClusterClient; this.processTreeFetcher = new Fetcher(elasticsearch); @@ -291,6 +295,10 @@ export class TelemetryReceiver implements ITelemetryReceiver { return this.clusterInfo; } + public getLicenseInfo(): Nullable { + return this.licenseInfo; + } + public getAlertsIndex(): string | undefined { return this.alertsIndex; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts index 8148a81e8f915..a6825a7517b4f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts @@ -8,11 +8,10 @@ import type { Logger } from '@kbn/core/server'; import { newTelemetryLogger, getPreviousDiagTaskTimestamp } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; -import type { TelemetryEvent } from '../types'; +import { TelemetryChannel, type TelemetryEvent } from '../types'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; import type { ITaskMetricsService } from '../task_metrics.types'; -import { TELEMETRY_CHANNEL_ENDPOINT_ALERTS } from '../constants'; import { copyAllowlistedFields, filterList } from '../filterlists'; export function createTelemetryDiagnosticsTaskConfig() { @@ -65,7 +64,7 @@ export function createTelemetryDiagnosticsTaskConfig() { log.l('Sending diagnostic alerts', { alerts_count: alerts.length, }); - await sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_ALERTS, processedAlerts); + sender.sendAsync(TelemetryChannel.ENDPOINT_ALERTS, processedAlerts); } await taskMetricsService.end(trace); From 7089f35803f500c18b35a407d50c9d0b883fc05f Mon Sep 17 00:00:00 2001 From: Katerina Date: Mon, 22 Jul 2024 14:04:30 +0300 Subject: [PATCH 07/14] [APM] Updated eem schema (#188763) ## Summary closes: https://github.com/elastic/kibana/issues/188761 ### changes - identityFields returns only the fields, query directly service name and service environment from entity document (EEM [change](https://github.com/elastic/kibana/pull/187699)) - Rename `logRatePerMinute` to `logRate` (EEM [change](https://github.com/elastic/kibana/pull/187021)) --- .../apm/common/entities/types.ts | 2 +- .../apm/common/es_fields/entities.ts | 1 - .../app/entities/charts/log_rate_chart.tsx | 4 +- .../table/get_service_columns.tsx | 10 +- .../table/multi_signal_services_table.tsx | 2 +- .../server/routes/entities/get_entities.ts | 21 +++-- .../apm/server/routes/entities/types.ts | 12 +-- .../utils/calculate_avg_metrics.test.ts | 24 ++--- .../entities/utils/merge_entities.test.ts | 94 ++++++++++--------- .../routes/entities/utils/merge_entities.ts | 6 +- 10 files changed, 92 insertions(+), 84 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/common/entities/types.ts b/x-pack/plugins/observability_solution/apm/common/entities/types.ts index 43b5531d211a9..7399dc4796814 100644 --- a/x-pack/plugins/observability_solution/apm/common/entities/types.ts +++ b/x-pack/plugins/observability_solution/apm/common/entities/types.ts @@ -17,7 +17,7 @@ export interface EntityMetrics { latency: number | null; throughput: number | null; failedTransactionRate: number; - logRatePerMinute: number; + logRate: number; logErrorRate: number | null; } diff --git a/x-pack/plugins/observability_solution/apm/common/es_fields/entities.ts b/x-pack/plugins/observability_solution/apm/common/es_fields/entities.ts index 043d611d37ce5..9c94b77743574 100644 --- a/x-pack/plugins/observability_solution/apm/common/es_fields/entities.ts +++ b/x-pack/plugins/observability_solution/apm/common/es_fields/entities.ts @@ -9,4 +9,3 @@ export const LAST_SEEN = 'entity.lastSeenTimestamp'; export const FIRST_SEEN = 'entity.firstSeenTimestamp'; export const ENTITY = 'entity'; -export const ENTITY_ENVIRONMENT = 'entity.identityFields.service.environment'; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_rate_chart.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_rate_chart.tsx index d8f3ebbf7f665..c46c55511dbf2 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_rate_chart.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_rate_chart.tsx @@ -92,7 +92,7 @@ export function LogRateChart({ height }: { height: number }) { description={ {i18n.translate( - 'xpack.apm.multiSignal.servicesTable.logRatePerMinute.tooltip.serviceNameLabel', + 'xpack.apm.multiSignal.servicesTable.logRate.tooltip.serviceNameLabel', { defaultMessage: 'service.name', } diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx index 0fe647b2c5127..01350075c6bf6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx @@ -173,17 +173,17 @@ export function getServiceColumns({ }, }, { - field: ServiceInventoryFieldName.LogRatePerMinute, + field: ServiceInventoryFieldName.logRate, name: ( {i18n.translate( - 'xpack.apm.multiSignal.servicesTable.logRatePerMinute.tooltip.serviceNameLabel', + 'xpack.apm.multiSignal.servicesTable.logRate.tooltip.serviceNameLabel', { defaultMessage: 'service.name', } @@ -215,7 +215,7 @@ export function getServiceColumns({ isLoading={false} color={currentPeriodColor} series={timeseriesData?.currentPeriod?.logRate[serviceName] ?? []} - valueLabel={asDecimalOrInteger(metrics.logRatePerMinute)} + valueLabel={asDecimalOrInteger(metrics.logRate)} hideSeries={!showWhenSmallOrGreaterThanLarge} /> ); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/multi_signal_services_table.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/multi_signal_services_table.tsx index 5320fef8f22db..12980917d82c0 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/multi_signal_services_table.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/multi_signal_services_table.tsx @@ -26,7 +26,7 @@ export enum ServiceInventoryFieldName { Throughput = 'metrics.throughput', Latency = 'metrics.latency', FailedTransactionRate = 'metrics.failedTransactionRate', - LogRatePerMinute = 'metrics.logRatePerMinute', + logRate = 'metrics.logRate', LogErrorRate = 'metrics.logErrorRate', } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts index b129a544d9fe3..3fd32c9b07a81 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts @@ -6,13 +6,13 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { kqlQuery } from '@kbn/observability-plugin/server'; -import { AGENT_NAME, DATA_STEAM_TYPE } from '../../../common/es_fields/apm'; import { - ENTITY_ENVIRONMENT, - FIRST_SEEN, - LAST_SEEN, - ENTITY, -} from '../../../common/es_fields/entities'; + AGENT_NAME, + DATA_STEAM_TYPE, + SERVICE_ENVIRONMENT, + SERVICE_NAME, +} from '../../../common/es_fields/apm'; +import { FIRST_SEEN, LAST_SEEN, ENTITY } from '../../../common/es_fields/entities'; import { environmentQuery } from '../../../common/utils/environment_query'; import { EntitiesESClient } from '../../lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients'; import { EntitiesRaw, ServiceEntities } from './types'; @@ -56,12 +56,12 @@ export async function getEntities({ body: { size, track_total_hits: false, - _source: [AGENT_NAME, ENTITY, DATA_STEAM_TYPE], + _source: [AGENT_NAME, ENTITY, DATA_STEAM_TYPE, SERVICE_NAME, SERVICE_ENVIRONMENT], query: { bool: { filter: [ ...kqlQuery(kuery), - ...environmentQuery(environment, ENTITY_ENVIRONMENT), + ...environmentQuery(environment, SERVICE_ENVIRONMENT), ...entitiesRangeQuery(start, end), ], }, @@ -72,7 +72,10 @@ export async function getEntities({ return entities.map((entity): ServiceEntities => { return { - serviceName: entity.entity.identityFields.service.name, + serviceName: entity.service.name, + environment: Array.isArray(entity.service?.environment) // TODO fix this in the EEM + ? entity.service.environment[0] + : entity.service.environment, agentName: entity.agent.name[0], signalTypes: entity.data_stream.type, entity: entity.entity, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts index f55be3bb7ffb3..b5279e053cfd0 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts @@ -10,12 +10,7 @@ import { SignalTypes, EntityMetrics } from '../../../common/entities/types'; export interface Entity { id: string; latestTimestamp: string; - identityFields: { - service: { - name: string; - environment?: string | null; - }; - }; + identityFields: string[]; metrics: EntityMetrics; } @@ -27,6 +22,7 @@ export interface TraceMetrics { export interface ServiceEntities { serviceName: string; + environment?: string; agentName: AgentName; signalTypes: string[]; entity: Entity; @@ -39,6 +35,10 @@ export interface EntitiesRaw { data_stream: { type: string[]; }; + service: { + name: string; + environment: string; + }; entity: Entity; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts index 14a156aef3663..41d6e8e8b0979 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts @@ -22,14 +22,14 @@ describe('calculateAverageMetrics', () => { failedTransactionRate: 5, latency: 5, logErrorRate: 5, - logRatePerMinute: 5, + logRate: 5, throughput: 5, }, { failedTransactionRate: 10, latency: 10, logErrorRate: 10, - logRatePerMinute: 10, + logRate: 10, throughput: 10, }, ], @@ -45,14 +45,14 @@ describe('calculateAverageMetrics', () => { failedTransactionRate: 15, latency: 15, logErrorRate: 15, - logRatePerMinute: 15, + logRate: 15, throughput: 15, }, { failedTransactionRate: 5, latency: 5, logErrorRate: 5, - logRatePerMinute: 5, + logRate: 5, throughput: 5, }, ], @@ -72,7 +72,7 @@ describe('calculateAverageMetrics', () => { failedTransactionRate: 7.5, latency: 7.5, logErrorRate: 7.5, - logRatePerMinute: 7.5, + logRate: 7.5, throughput: 7.5, }, serviceName: 'service-1', @@ -86,7 +86,7 @@ describe('calculateAverageMetrics', () => { failedTransactionRate: 10, latency: 10, logErrorRate: 10, - logRatePerMinute: 10, + logRate: 10, throughput: 10, }, serviceName: 'service-2', @@ -105,14 +105,14 @@ describe('calculateAverageMetrics', () => { failedTransactionRate: 5, latency: null, logErrorRate: 5, - logRatePerMinute: 5, + logRate: 5, throughput: 5, }, { failedTransactionRate: 10, latency: null, logErrorRate: 10, - logRatePerMinute: 10, + logRate: 10, throughput: 10, }, ], @@ -131,7 +131,7 @@ describe('calculateAverageMetrics', () => { metrics: { failedTransactionRate: 7.5, logErrorRate: 7.5, - logRatePerMinute: 7.5, + logRate: 7.5, throughput: 7.5, }, serviceName: 'service-1', @@ -147,14 +147,14 @@ describe('mergeMetrics', () => { failedTransactionRate: 5, latency: 5, logErrorRate: 5, - logRatePerMinute: 5, + logRate: 5, throughput: 5, }, { failedTransactionRate: 10, latency: 10, logErrorRate: 10, - logRatePerMinute: 10, + logRate: 10, throughput: 10, }, ]; @@ -165,7 +165,7 @@ describe('mergeMetrics', () => { failedTransactionRate: [5, 10], latency: [5, 10], logErrorRate: [5, 10], - logRatePerMinute: [5, 10], + logRate: [5, 10], throughput: [5, 10], }); }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts index 88566aa1d379c..544513fd501d2 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts @@ -13,18 +13,19 @@ describe('mergeEntities', () => { const entities = [ { serviceName: 'service-1', + environment: 'test', agentName: 'nodejs' as AgentName as AgentName, signalTypes: ['metrics', 'logs'], entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1', environment: 'test' } }, + identityFields: ['service.name', 'service.environment'], id: 'service-1:test', }, }, @@ -41,7 +42,7 @@ describe('mergeEntities', () => { failedTransactionRate: 0.3333333333333333, latency: 10, logErrorRate: null, - logRatePerMinute: 1, + logRate: 1, throughput: 0, }, ], @@ -54,70 +55,73 @@ describe('mergeEntities', () => { const entities = [ { serviceName: 'service-1', + environment: 'env-service-1', agentName: 'nodejs' as AgentName as AgentName, signalTypes: ['foo'], entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - - identityFields: { service: { name: 'apm-only-1', environment: 'env-service-1' } }, + identityFields: ['service.name', 'service.environment'], id: 'service-1:env-service-1', }, }, { serviceName: 'service-1', + environment: 'env-service-2', agentName: 'nodejs' as AgentName as AgentName, signalTypes: ['bar'], entity: { latestTimestamp: '2024-03-05T10:34:40.810Z', metrics: { - logRatePerMinute: 10, + logRate: 10, logErrorRate: 10, throughput: 10, failedTransactionRate: 10, latency: 10, }, - identityFields: { service: { name: 'service-1', environment: 'env-service-2' } }, + identityFields: ['service.name', 'service.environment'], id: 'apm-only-1:synthtrace-env-2', }, }, { serviceName: 'service-2', + environment: 'env-service-3', agentName: 'java' as AgentName, signalTypes: ['baz'], entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 15, + logRate: 15, logErrorRate: 15, throughput: 15, failedTransactionRate: 15, latency: 15, }, - identityFields: { service: { name: 'service-2', environment: 'env-service-3' } }, + identityFields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', }, }, { serviceName: 'service-2', + environment: 'env-service-4', agentName: 'java' as AgentName, signalTypes: ['baz'], entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 5, + logRate: 5, logErrorRate: 5, throughput: 5, failedTransactionRate: 5, latency: 5, }, - identityFields: { service: { name: 'service-2', environment: 'env-service-4' } }, + identityFields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', }, }, @@ -135,14 +139,14 @@ describe('mergeEntities', () => { failedTransactionRate: 0.3333333333333333, latency: 10, logErrorRate: null, - logRatePerMinute: 1, + logRate: 1, throughput: 0, }, { failedTransactionRate: 10, latency: 10, logErrorRate: 10, - logRatePerMinute: 10, + logRate: 10, throughput: 10, }, ], @@ -158,14 +162,14 @@ describe('mergeEntities', () => { failedTransactionRate: 15, latency: 15, logErrorRate: 15, - logRatePerMinute: 15, + logRate: 15, throughput: 15, }, { failedTransactionRate: 5, latency: 5, logErrorRate: 5, - logRatePerMinute: 5, + logRate: 5, throughput: 5, }, ], @@ -177,52 +181,55 @@ describe('mergeEntities', () => { const entities = [ { serviceName: 'service-1', + environment: 'test', agentName: 'nodejs' as AgentName, signalTypes: ['metrics', 'logs'], entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 5, + logRate: 5, logErrorRate: 5, throughput: 5, failedTransactionRate: 5, latency: 5, }, - identityFields: { service: { name: 'service-1', environment: 'test' } }, + identityFields: ['service.name', 'service.environment'], id: 'service-1:test', }, }, { serviceName: 'service-1', + environment: 'test', agentName: 'nodejs' as AgentName, signalTypes: ['metrics', 'logs'], entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 10, + logRate: 10, logErrorRate: 10, throughput: 10, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1', environment: 'test' } }, + identityFields: ['service.name', 'service.environment'], id: 'service-1:test', }, }, { serviceName: 'service-1', + environment: 'prod', agentName: 'nodejs' as AgentName, signalTypes: ['foo'], entity: { latestTimestamp: '2024-23-05T10:34:40.810Z', metrics: { - logRatePerMinute: 0.333, + logRate: 0.333, logErrorRate: 0.333, throughput: 0.333, failedTransactionRate: 0.333, latency: 0.333, }, - identityFields: { service: { name: 'service-1', environment: 'prod' } }, + identityFields: ['service.name', 'service.environment'], id: 'service-1:prod', }, }, @@ -239,21 +246,21 @@ describe('mergeEntities', () => { failedTransactionRate: 5, latency: 5, logErrorRate: 5, - logRatePerMinute: 5, + logRate: 5, throughput: 5, }, { failedTransactionRate: 0.3333333333333333, latency: 10, logErrorRate: 10, - logRatePerMinute: 10, + logRate: 10, throughput: 10, }, { failedTransactionRate: 0.333, latency: 0.333, logErrorRate: 0.333, - logRatePerMinute: 0.333, + logRate: 0.333, throughput: 0.333, }, ], @@ -265,18 +272,19 @@ describe('mergeEntities', () => { const entity = [ { serviceName: 'service-1', + environment: undefined, agentName: 'nodejs' as AgentName, signalTypes: [], entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1', environment: null } }, + identityFields: ['service.name'], id: 'service-1:test', }, }, @@ -293,7 +301,7 @@ describe('mergeEntities', () => { failedTransactionRate: 0.3333333333333333, latency: 10, logErrorRate: null, - logRatePerMinute: 1, + logRate: 1, throughput: 0, }, ], @@ -309,13 +317,13 @@ describe('mergeEntities', () => { entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1', environment: null } }, + identityFields: ['service.name'], id: 'service-1:test', }, }, @@ -326,13 +334,13 @@ describe('mergeEntities', () => { entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1', environment: null } }, + identityFields: ['service.name'], id: 'service-1:test', }, }, @@ -349,11 +357,11 @@ describe('mergeEntities', () => { failedTransactionRate: 0.3333333333333333, latency: 10, logErrorRate: null, - logRatePerMinute: 1, + logRate: 1, throughput: 0, }, { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, @@ -374,13 +382,13 @@ describe('mergeEntities', () => { entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1' } }, + identityFields: ['service.name'], id: 'service-1:test', }, }, @@ -397,7 +405,7 @@ describe('mergeEntities', () => { failedTransactionRate: 0.3333333333333333, latency: 10, logErrorRate: null, - logRatePerMinute: 1, + logRate: 1, throughput: 0, }, ], @@ -413,13 +421,13 @@ describe('mergeEntities', () => { entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1' } }, + identityFields: ['service.name'], id: 'service-1:test', }, }, @@ -430,13 +438,13 @@ describe('mergeEntities', () => { entity: { latestTimestamp: '2024-06-05T10:34:40.810Z', metrics: { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, latency: 10, }, - identityFields: { service: { name: 'service-1' } }, + identityFields: ['service.name'], id: 'service-1:test', }, }, @@ -453,11 +461,11 @@ describe('mergeEntities', () => { failedTransactionRate: 0.3333333333333333, latency: 10, logErrorRate: null, - logRatePerMinute: 1, + logRate: 1, throughput: 0, }, { - logRatePerMinute: 1, + logRate: 1, logErrorRate: null, throughput: 0, failedTransactionRate: 0.3333333333333333, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts index dd32a57776192..7dd8bfdace7bf 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts @@ -33,7 +33,7 @@ function mergeFunc(entity: ServiceEntities, existingEntity?: MergedServiceEntiti serviceName: entity.serviceName, agentName: entity.agentName, signalTypes: entity.signalTypes, - environments: compact([entity.entity.identityFields.service?.environment]), + environments: compact([entity?.environment]), latestTimestamp: entity.entity.latestTimestamp, metrics: [entity.entity.metrics], }; @@ -42,9 +42,7 @@ function mergeFunc(entity: ServiceEntities, existingEntity?: MergedServiceEntiti serviceName: entity.serviceName, agentName: entity.agentName, signalTypes: uniq(compact([...(existingEntity?.signalTypes ?? []), ...entity.signalTypes])), - environments: uniq( - compact([...existingEntity?.environments, entity.entity.identityFields?.service?.environment]) - ), + environments: uniq(compact([...existingEntity?.environments, entity?.environment])), latestTimestamp: entity.entity.latestTimestamp, metrics: [...existingEntity?.metrics, entity.entity.metrics], }; From 1bf7b5814cb7eb3b0df447bec2fedf3445c44b0b Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Mon, 22 Jul 2024 13:18:58 +0200 Subject: [PATCH 08/14] [Fleet] Show host id in agent overview page (#188822) Fixes https://github.com/elastic/kibana/issues/182680 ## Summary Display the host id in agent detail page. Previously this info wasn't displayed anywhere in the UI ![Screenshot 2024-07-22 at 11 29 20](https://github.com/user-attachments/assets/828c4069-8595-4de0-b9a6-b00c6fa66fe0) ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../agent_details/agent_details_overview.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 197d64810c199..35fd048cc13cd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -149,7 +149,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ description: agent.last_checkin_message ? agent.last_checkin_message : '-', }, { - title: i18n.translate('xpack.fleet.agentDetails.hostIdLabel', { + title: i18n.translate('xpack.fleet.agentDetails.agentIdLabel', { defaultMessage: 'Agent ID', }), description: agent.id, @@ -197,6 +197,15 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ ? agent.local_metadata.host.hostname : '-', }, + { + title: i18n.translate('xpack.fleet.agentDetails.hostIdLabel', { + defaultMessage: 'Host ID', + }), + description: + typeof agent.local_metadata?.host?.id === 'string' + ? agent.local_metadata.host.id + : '-', + }, { title: i18n.translate('xpack.fleet.agentDetails.logLevel', { defaultMessage: 'Logging level', From ce5ca1db2e32606df0901e2c8216c54104881616 Mon Sep 17 00:00:00 2001 From: Joe McElroy Date: Mon, 22 Jul 2024 12:26:01 +0100 Subject: [PATCH 09/14] [Search] [Playground] Session persistence (#188523) ## Summary Stores working state into localstorage so when you visit again, you are where you left off. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../instructions_field.tsx | 1 + .../summarization_model.test.tsx | 2 + .../hooks/use_source_indices_fields.test.tsx | 7 + .../public/providers/form_provider.test.tsx | 154 +++++++++++++++++- .../public/providers/form_provider.tsx | 54 +++++- .../plugins/search_playground/public/types.ts | 3 +- .../page_objects/search_playground_page.ts | 35 ++++ .../search_playground/playground_overview.ts | 12 ++ 8 files changed, 252 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/search_playground/public/components/summarization_panel/instructions_field.tsx b/x-pack/plugins/search_playground/public/components/summarization_panel/instructions_field.tsx index 5db2dcc6c6d86..0bf870202f1e9 100644 --- a/x-pack/plugins/search_playground/public/components/summarization_panel/instructions_field.tsx +++ b/x-pack/plugins/search_playground/public/components/summarization_panel/instructions_field.tsx @@ -56,6 +56,7 @@ export const InstructionsField: React.FC = ({ value, onC fullWidth > { it('renders correctly with models', () => { const models = [ { + id: 'model1', name: 'Model1', disabled: false, icon: MockIcon, @@ -47,6 +48,7 @@ describe('SummarizationModel', () => { connectorType: LLMs.openai_azure, }, { + id: 'model2', name: 'Model2', disabled: true, icon: MockIcon, diff --git a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx index 996d963eaeb16..7f0efed994c9b 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx +++ b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx @@ -26,6 +26,13 @@ import { IndicesQuerySourceFields } from '../types'; describe('useSourceIndicesFields Hook', () => { let postMock: jest.Mock; + beforeEach(() => { + // Playground Provider has the formProvider which + // persists the form state into local storage + // We need to clear the local storage before each test + localStorage.clear(); + }); + const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx index 9ae9f4faac99b..73def5031615e 100644 --- a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx +++ b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx @@ -6,13 +6,14 @@ */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, waitFor, act } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { FormProvider } from './form_provider'; +import { FormProvider, LOCAL_STORAGE_KEY } from './form_provider'; import { useLoadFieldsByIndices } from '../hooks/use_load_fields_by_indices'; import { useLLMsModels } from '../hooks/use_llms_models'; import * as ReactHookForm from 'react-hook-form'; import { ChatFormFields } from '../types'; +import { useSearchParams } from 'react-router-dom-v5-compat'; jest.mock('../hooks/use_load_fields_by_indices'); jest.mock('../hooks/use_llms_models'); @@ -24,6 +25,21 @@ let formHookSpy: jest.SpyInstance; const mockUseLoadFieldsByIndices = useLoadFieldsByIndices as jest.Mock; const mockUseLLMsModels = useLLMsModels as jest.Mock; +const mockUseSearchParams = useSearchParams as jest.Mock; + +const localStorageMock = (() => { + let store: Record = {}; + + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value; + }, + clear: () => { + store = {}; + }, + }; +})() as Storage; describe('FormProvider', () => { beforeEach(() => { @@ -34,11 +50,12 @@ describe('FormProvider', () => { afterEach(() => { jest.clearAllMocks(); + localStorageMock.clear(); }); it('renders the form provider with initial values, no default model', async () => { render( - +
Test Child Component
); @@ -65,7 +82,7 @@ describe('FormProvider', () => { mockUseLLMsModels.mockReturnValueOnce(mockModels); render( - +
Test Child Component
); @@ -88,14 +105,139 @@ describe('FormProvider', () => { mockUseLLMsModels.mockReturnValueOnce(modelsWithAllDisabled); render( - +
Test Child Component
); await waitFor(() => { expect(mockUseLoadFieldsByIndices).toHaveBeenCalled(); - expect(modelsWithAllDisabled.find((model) => !model.disabled)).toBeUndefined(); + }); + + expect( + formHookSpy.mock.results[0].value.getValues(ChatFormFields.summarizationModel) + ).toBeUndefined(); + }); + + it('saves form state to localStorage', async () => { + render( + +
Test Child Component
+
+ ); + + const { setValue } = formHookSpy.mock.results[0].value; + + act(() => { + setValue(ChatFormFields.prompt, 'New prompt'); + }); + + await waitFor(() => { + expect(localStorageMock.getItem(LOCAL_STORAGE_KEY)).toEqual( + JSON.stringify({ + prompt: 'New prompt', + doc_size: 3, + source_fields: {}, + indices: [], + summarization_model: undefined, + }) + ); + }); + }); + + it('loads form state from localStorage', async () => { + localStorageMock.setItem( + LOCAL_STORAGE_KEY, + JSON.stringify({ + prompt: 'Loaded prompt', + doc_size: 3, + source_fields: {}, + indices: [], + summarization_model: undefined, + }) + ); + + render( + +
Test Child Component
+
+ ); + + const { getValues } = formHookSpy.mock.results[0].value; + + await waitFor(() => { + expect(getValues()).toEqual({ + prompt: 'Loaded prompt', + doc_size: 3, + source_fields: {}, + indices: [], + summarization_model: undefined, + }); + }); + }); + + it('overrides the session model to the default when not found in list', async () => { + const mockModels = [ + { id: 'model1', name: 'Model 1', disabled: false }, + { id: 'model2', name: 'Model 2', disabled: true }, + ]; + + mockUseLLMsModels.mockReturnValueOnce(mockModels); + + localStorageMock.setItem( + LOCAL_STORAGE_KEY, + JSON.stringify({ + prompt: 'Loaded prompt', + doc_size: 3, + source_fields: {}, + indices: [], + summarization_model: { id: 'non-exist-model', name: 'Model 1', disabled: false }, + }) + ); + + render( + +
Test Child Component
+
+ ); + + const { getValues } = formHookSpy.mock.results[0].value; + + await waitFor(() => { + expect(getValues().summarization_model).toEqual({ + id: 'model1', + name: 'Model 1', + disabled: false, + }); + }); + }); + + it('updates indices from search params', async () => { + const mockSearchParams = new URLSearchParams(); + mockSearchParams.get = jest.fn().mockReturnValue('new-index'); + mockUseSearchParams.mockReturnValue([mockSearchParams]); + + localStorageMock.setItem( + LOCAL_STORAGE_KEY, + JSON.stringify({ + prompt: 'Loaded prompt', + doc_size: 3, + source_fields: {}, + indices: ['old-index'], + summarization_model: undefined, + }) + ); + + render( + +
Test Child Component
+
+ ); + + const { getValues } = formHookSpy.mock.results[0].value; + + await waitFor(() => { + expect(getValues(ChatFormFields.indices)).toEqual(['new-index']); }); }); }); diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.tsx index a319d15f63d20..03c0ce5652e19 100644 --- a/x-pack/plugins/search_playground/public/providers/form_provider.tsx +++ b/x-pack/plugins/search_playground/public/providers/form_provider.tsx @@ -11,25 +11,61 @@ import { useLoadFieldsByIndices } from '../hooks/use_load_fields_by_indices'; import { ChatForm, ChatFormFields } from '../types'; import { useLLMsModels } from '../hooks/use_llms_models'; -export const FormProvider: React.FC = ({ children }) => { +type PartialChatForm = Partial; +export const LOCAL_STORAGE_KEY = 'search_playground_session'; + +const DEFAULT_FORM_VALUES: PartialChatForm = { + prompt: 'You are an assistant for question-answering tasks.', + doc_size: 3, + source_fields: {}, + indices: [], + summarization_model: undefined, +}; + +const getLocalSession = (storage: Storage): PartialChatForm => { + try { + const localSessionJSON = storage.getItem(LOCAL_STORAGE_KEY); + const sessionState = localSessionJSON ? JSON.parse(localSessionJSON) : {}; + + return { + ...DEFAULT_FORM_VALUES, + ...sessionState, + }; + } catch (e) { + return DEFAULT_FORM_VALUES; + } +}; + +const setLocalSession = (state: PartialChatForm, storage: Storage) => { + storage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(state)); +}; + +interface FormProviderProps { + storage?: Storage; +} + +export const FormProvider: React.FC = ({ children, storage = localStorage }) => { const models = useLLMsModels(); const [searchParams] = useSearchParams(); const index = useMemo(() => searchParams.get('default-index'), [searchParams]); + const sessionState = useMemo(() => getLocalSession(storage), [storage]); const form = useForm({ - defaultValues: { - prompt: 'You are an assistant for question-answering tasks.', - doc_size: 3, - source_fields: {}, - indices: index ? [index] : [], - summarization_model: undefined, - }, + defaultValues: { ...sessionState, indices: index ? [index] : sessionState.indices }, }); useLoadFieldsByIndices({ watch: form.watch, setValue: form.setValue, getValues: form.getValues }); + useEffect(() => { + const subscription = form.watch((values) => + setLocalSession(values as Partial, storage) + ); + return () => subscription.unsubscribe(); + }, [form, storage]); + useEffect(() => { const defaultModel = models.find((model) => !model.disabled); + const currentModel = form.getValues(ChatFormFields.summarizationModel); - if (defaultModel && !form.getValues(ChatFormFields.summarizationModel)) { + if (defaultModel && (!currentModel || !models.find((model) => currentModel.id === model.id))) { form.setValue(ChatFormFields.summarizationModel, defaultModel); } }, [form, models]); diff --git a/x-pack/plugins/search_playground/public/types.ts b/x-pack/plugins/search_playground/public/types.ts index f44e354d411db..2bbd45ff16230 100644 --- a/x-pack/plugins/search_playground/public/types.ts +++ b/x-pack/plugins/search_playground/public/types.ts @@ -73,7 +73,7 @@ export interface ChatForm { [ChatFormFields.citations]: boolean; [ChatFormFields.indices]: string[]; [ChatFormFields.summarizationModel]: LLMModel; - [ChatFormFields.elasticsearchQuery]: { retriever: unknown }; // RetrieverContainer leads to "Type instantiation is excessively deep and possibly infinite" error + [ChatFormFields.elasticsearchQuery]: { retriever: any }; // RetrieverContainer leads to "Type instantiation is excessively deep and possibly infinite" error [ChatFormFields.sourceFields]: { [index: string]: string[] }; [ChatFormFields.docSize]: number; [ChatFormFields.queryFields]: { [index: string]: string[] }; @@ -203,6 +203,7 @@ export interface UseChatHelpers { } export interface LLMModel { + id: string; name: string; value?: string; showConnectorName?: boolean; diff --git a/x-pack/test/functional/page_objects/search_playground_page.ts b/x-pack/test/functional/page_objects/search_playground_page.ts index 133d988f04653..97e53e87ed2f9 100644 --- a/x-pack/test/functional/page_objects/search_playground_page.ts +++ b/x-pack/test/functional/page_objects/search_playground_page.ts @@ -19,7 +19,35 @@ export function SearchPlaygroundPageProvider({ getService }: FtrProviderContext) await testSubjects.click('saveButton'); }; + const SESSION_KEY = 'search_playground_session'; + return { + session: { + async clearSession(): Promise { + await browser.setLocalStorageItem(SESSION_KEY, '{}'); + }, + + async setSession(): Promise { + await browser.setLocalStorageItem( + SESSION_KEY, + JSON.stringify({ + prompt: 'You are a fireman in london that helps answering question-answering tasks.', + }) + ); + }, + + async expectSession(): Promise { + const session = (await browser.getLocalStorageItem(SESSION_KEY)) || '{}'; + const state = JSON.parse(session); + expect(state.prompt).to.be('You are an assistant for question-answering tasks.'); + expect(state.doc_size).to.be(3); + expect(state.elasticsearch_query).eql({ + retriever: { + standard: { query: { multi_match: { query: '{query}', fields: ['baz'] } } }, + }, + }); + }, + }, PlaygroundStartChatPage: { async expectPlaygroundStartChatPageComponentsToExist() { await testSubjects.existOrFail('setupPage'); @@ -85,6 +113,13 @@ export function SearchPlaygroundPageProvider({ getService }: FtrProviderContext) await testSubjects.existOrFail('chatPage'); }, + async expectPromptToBe(text: string) { + await testSubjects.existOrFail('instructionsPrompt'); + const instructionsPromptElement = await testSubjects.find('instructionsPrompt'); + const promptInstructions = await instructionsPromptElement.getVisibleText(); + expect(promptInstructions).to.contain(text); + }, + async expectChatWindowLoaded() { expect(await testSubjects.getAttribute('viewModeSelector', 'disabled')).to.be(null); expect(await testSubjects.isEnabled('dataSourceActionButton')).to.be(true); diff --git a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts index 2e8538e154cbe..656671f277d75 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts @@ -137,6 +137,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await createConnector(); await createIndex(); await browser.refresh(); + await pageObjects.searchPlayground.session.clearSession(); await pageObjects.searchPlayground.PlaygroundChatPage.navigateToChatPage(); }); it('loads successfully', async () => { @@ -153,6 +154,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ) === undefined ); + await pageObjects.searchPlayground.session.expectSession(); + await pageObjects.searchPlayground.PlaygroundChatPage.sendQuestion(); const conversationSimulator = await conversationInterceptor.waitForIntercept(); @@ -180,6 +183,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('save selected fields between modes', async () => { await pageObjects.searchPlayground.PlaygroundChatPage.expectSaveFieldsBetweenModes(); }); + + it('loads a session from localstorage', async () => { + await pageObjects.searchPlayground.session.setSession(); + await browser.refresh(); + await pageObjects.searchPlayground.PlaygroundChatPage.navigateToChatPage(); + await pageObjects.searchPlayground.PlaygroundChatPage.expectPromptToBe( + 'You are a fireman in london that helps answering question-answering tasks.' + ); + }); }); after(async () => { From 2d2d8cf603b18afc32e16c0b805fc96f4f96e19c Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 22 Jul 2024 13:49:12 +0200 Subject: [PATCH 10/14] [ES|QL] Max/Min support IP fields (#188808) ## Summary Follow up of https://github.com/elastic/elasticsearch/pull/110921 ES|QL max and min aggregations support now IP fields. ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../autocomplete.command.stats.test.ts | 4 +- .../src/definitions/aggs.ts | 8 ++ .../esql_validation_meta_tests.json | 104 ++++++++++++++++++ .../src/validation/validation.test.ts | 52 +++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts index 2d81fe794193b..a37756ecd0866 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts @@ -116,8 +116,8 @@ describe('autocomplete.suggest', () => { test('when typing inside function left paren', async () => { const { assertSuggestions } = await setup(); const expected = [ - ...getFieldNamesByType(['number', 'date', 'boolean']), - ...getFunctionSignaturesByReturnType('stats', ['number', 'date', 'boolean'], { + ...getFieldNamesByType(['number', 'date', 'boolean', 'ip']), + ...getFunctionSignaturesByReturnType('stats', ['number', 'date', 'boolean', 'ip'], { evalMath: true, }), ]; diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts index 69f8f76807daf..a27b8a68a9be0 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts @@ -112,6 +112,10 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }], returnType: 'boolean', }, + { + params: [{ name: 'column', type: 'ip', noNestingFunctions: true }], + returnType: 'ip', + }, ], examples: [`from index | stats result = max(field)`, `from index | stats max(field)`], }, @@ -135,6 +139,10 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }], returnType: 'boolean', }, + { + params: [{ name: 'column', type: 'ip', noNestingFunctions: true }], + returnType: 'ip', + }, ], examples: [`from index | stats result = min(field)`, `from index | stats min(field)`], }, diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index a2cc5bf55ff35..65dc30e79064b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -24201,6 +24201,58 @@ ], "warning": [] }, + { + "query": "from a_index | stats var = max(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where max(ipField)", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(ipField) > 0", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(ipField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(ipField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(ipField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(ipField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, { "query": "from a_index | stats var = min(numberField)", "error": [], @@ -24526,6 +24578,58 @@ ], "warning": [] }, + { + "query": "from a_index | stats var = min(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where min(ipField)", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(ipField) > 0", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(ipField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(ipField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(ipField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(ipField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, { "query": "from a_index | stats var = count(stringField)", "error": [], diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index afbd3db5458bb..e9a707fd85e98 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -9202,6 +9202,32 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval max(booleanField) > 0', [ 'EVAL does not support function max', ]); + testErrorsAndWarnings('from a_index | stats var = max(ipField)', []); + testErrorsAndWarnings('from a_index | stats max(ipField)', []); + + testErrorsAndWarnings('from a_index | where max(ipField)', [ + 'WHERE does not support function max', + ]); + + testErrorsAndWarnings('from a_index | where max(ipField) > 0', [ + 'WHERE does not support function max', + ]); + + testErrorsAndWarnings('from a_index | eval var = max(ipField)', [ + 'EVAL does not support function max', + ]); + + testErrorsAndWarnings('from a_index | eval var = max(ipField) > 0', [ + 'EVAL does not support function max', + ]); + + testErrorsAndWarnings('from a_index | eval max(ipField)', [ + 'EVAL does not support function max', + ]); + + testErrorsAndWarnings('from a_index | eval max(ipField) > 0', [ + 'EVAL does not support function max', + ]); }); describe('min', () => { @@ -9374,6 +9400,32 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval min(booleanField) > 0', [ 'EVAL does not support function min', ]); + testErrorsAndWarnings('from a_index | stats var = min(ipField)', []); + testErrorsAndWarnings('from a_index | stats min(ipField)', []); + + testErrorsAndWarnings('from a_index | where min(ipField)', [ + 'WHERE does not support function min', + ]); + + testErrorsAndWarnings('from a_index | where min(ipField) > 0', [ + 'WHERE does not support function min', + ]); + + testErrorsAndWarnings('from a_index | eval var = min(ipField)', [ + 'EVAL does not support function min', + ]); + + testErrorsAndWarnings('from a_index | eval var = min(ipField) > 0', [ + 'EVAL does not support function min', + ]); + + testErrorsAndWarnings('from a_index | eval min(ipField)', [ + 'EVAL does not support function min', + ]); + + testErrorsAndWarnings('from a_index | eval min(ipField) > 0', [ + 'EVAL does not support function min', + ]); }); describe('count', () => { From e5638db74e1c2ceb43fa7124298286936abdbc5a Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 22 Jul 2024 14:02:12 +0200 Subject: [PATCH 11/14] [core rendering] get rid of `getInjectedVar` (#188755) ## Summary Fix https://github.com/elastic/kibana/issues/54376 Fix https://github.com/elastic/kibana/issues/127733 - get rid of the `InjectedMetadata.vars` and `getInjectedVar` deprecated "API" - Add `apmConfig` as an explicit `InjectedMetadata` property instead of passing it via `vars` - Inject the apm config from the `rendering` service instead of `httpResource`, as it's just how it should be and how all other things get injected. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/http_resources_service.test.mocks.ts | 13 ---- .../src/http_resources_service.test.ts | 14 ---- .../src/http_resources_service.ts | 10 --- .../tsconfig.json | 1 - .../src/injected_metadata_service.test.ts | 76 ------------------- .../src/injected_metadata_service.ts | 9 --- .../src/types.ts | 4 - .../src/injected_metadata_service.mock.ts | 2 - .../src/types.ts | 2 +- .../rendering_service.test.ts.snap | 64 ++++++++++++---- .../src/get_apm_config.test.mocks.ts | 0 .../src/get_apm_config.test.ts | 0 .../src/get_apm_config.ts | 0 .../src/rendering_service.test.mocks.ts | 7 ++ .../src/rendering_service.test.ts | 21 +++++ .../src/rendering_service.tsx | 6 +- .../src/types.ts | 7 -- .../tsconfig.json | 1 + .../src/kbn_bootstrap.ts | 8 +- .../core-root-browser-internal/tsconfig.json | 1 + 20 files changed, 89 insertions(+), 157 deletions(-) delete mode 100644 packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts rename packages/core/{http/core-http-resources-server-internal => rendering/core-rendering-server-internal}/src/get_apm_config.test.mocks.ts (100%) rename packages/core/{http/core-http-resources-server-internal => rendering/core-rendering-server-internal}/src/get_apm_config.test.ts (100%) rename packages/core/{http/core-http-resources-server-internal => rendering/core-rendering-server-internal}/src/get_apm_config.ts (100%) diff --git a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts deleted file mode 100644 index 0d0c637a768df..0000000000000 --- a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts +++ /dev/null @@ -1,13 +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 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 const getApmConfigMock = jest.fn(); - -jest.doMock('./get_apm_config', () => ({ - getApmConfig: getApmConfigMock, -})); diff --git a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts index 481b0b694747a..acf8950d0d147 100644 --- a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { getApmConfigMock } from './http_resources_service.test.mocks'; - import type { RouteConfig } from '@kbn/core-http-server'; - import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock, httpServerMock } from '@kbn/core-http-server-mocks'; import { renderingServiceMock } from '@kbn/core-rendering-server-mocks'; @@ -29,11 +26,6 @@ describe('HttpResources service', () => { let router: ReturnType; const kibanaRequest = httpServerMock.createKibanaRequest(); const context = createCoreRequestHandlerContextMock(); - const apmConfig = { mockApmConfig: true }; - - beforeEach(() => { - getApmConfigMock.mockReturnValue(apmConfig); - }); describe('#createRegistrar', () => { beforeEach(() => { @@ -93,9 +85,6 @@ describe('HttpResources service', () => { }, { isAnonymousPage: false, - vars: { - apmConfig, - }, } ); }); @@ -118,9 +107,6 @@ describe('HttpResources service', () => { }, { isAnonymousPage: true, - vars: { - apmConfig, - }, } ); }); diff --git a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts index a896d6b98542f..bb9320bc5cb48 100644 --- a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts @@ -33,8 +33,6 @@ import type { import type { InternalHttpResourcesSetup } from './types'; -import { getApmConfig } from './get_apm_config'; - /** * @internal */ @@ -112,13 +110,9 @@ export class HttpResourcesService implements CoreService { }).toThrowError(); }); }); - -describe('setup.getInjectedVar()', () => { - it('returns values from injectedMetadata.vars', () => { - const setup = new InjectedMetadataService({ - injectedMetadata: { - vars: { - foo: { - bar: '1', - }, - 'baz:box': { - foo: 2, - }, - }, - }, - } as any).setup(); - - expect(setup.getInjectedVar('foo')).toEqual({ - bar: '1', - }); - expect(setup.getInjectedVar('foo.bar')).toBe('1'); - expect(setup.getInjectedVar('baz:box')).toEqual({ - foo: 2, - }); - expect(setup.getInjectedVar('')).toBe(undefined); - }); - - it('returns read-only values', () => { - const setup = new InjectedMetadataService({ - injectedMetadata: { - vars: { - foo: { - bar: 1, - }, - }, - }, - } as any).setup(); - - const foo: any = setup.getInjectedVar('foo'); - expect(() => { - foo.bar = 2; - }).toThrowErrorMatchingInlineSnapshot( - `"Cannot assign to read only property 'bar' of object '#'"` - ); - expect(() => { - foo.newProp = 2; - }).toThrowErrorMatchingInlineSnapshot( - `"Cannot add property newProp, object is not extensible"` - ); - }); -}); - -describe('setup.getInjectedVars()', () => { - it('returns all injected vars, readonly', () => { - const setup = new InjectedMetadataService({ - injectedMetadata: { - vars: { - foo: { - bar: 1, - }, - }, - }, - } as any).setup(); - - const vars: any = setup.getInjectedVars(); - expect(() => { - vars.foo = 2; - }).toThrowErrorMatchingInlineSnapshot( - `"Cannot assign to read only property 'foo' of object '#'"` - ); - expect(() => { - vars.newProp = 2; - }).toThrowErrorMatchingInlineSnapshot( - `"Cannot add property newProp, object is not extensible"` - ); - }); -}); diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/injected_metadata_service.ts b/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/injected_metadata_service.ts index a50de7ca3f5e7..104500ef19215 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/injected_metadata_service.ts +++ b/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/injected_metadata_service.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { get } from 'lodash'; import { deepFreeze } from '@kbn/std'; import type { InjectedMetadata } from '@kbn/core-injected-metadata-common-internal'; import type { @@ -76,14 +75,6 @@ export class InjectedMetadataService { return this.state.legacyMetadata; }, - getInjectedVar: (name: string, defaultValue?: any): unknown => { - return get(this.state.vars, name, defaultValue); - }, - - getInjectedVars: () => { - return this.state.vars; - }, - getKibanaBuildNumber: () => { return this.state.buildNumber; }, diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/types.ts b/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/types.ts index bf77a2531660a..12bee868702b6 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/types.ts +++ b/packages/core/injected-metadata/core-injected-metadata-browser-internal/src/types.ts @@ -56,10 +56,6 @@ export interface InternalInjectedMetadataSetup { user?: Record | undefined; }; }; - getInjectedVar: (name: string, defaultValue?: any) => unknown; - getInjectedVars: () => { - [key: string]: unknown; - }; getCustomBranding: () => CustomBranding; } diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/src/injected_metadata_service.mock.ts b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/src/injected_metadata_service.mock.ts index 69148ca90e31b..e2dad19650a2c 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/src/injected_metadata_service.mock.ts +++ b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/src/injected_metadata_service.mock.ts @@ -27,8 +27,6 @@ const createSetupContractMock = () => { getLegacyMetadata: jest.fn(), getTheme: jest.fn(), getPlugins: jest.fn(), - getInjectedVar: jest.fn(), - getInjectedVars: jest.fn(), getKibanaBuildNumber: jest.fn(), getCustomBranding: jest.fn(), }; diff --git a/packages/core/injected-metadata/core-injected-metadata-common-internal/src/types.ts b/packages/core/injected-metadata/core-injected-metadata-common-internal/src/types.ts index 6d4e3df08a5ef..c2f1e85e1e60d 100644 --- a/packages/core/injected-metadata/core-injected-metadata-common-internal/src/types.ts +++ b/packages/core/injected-metadata/core-injected-metadata-common-internal/src/types.ts @@ -71,7 +71,7 @@ export interface InjectedMetadata { warnLegacyBrowsers: boolean; }; externalUrl: { policy: InjectedMetadataExternalUrlPolicy[] }; - vars: Record; + apmConfig: Record | null; uiPlugins: InjectedMetadataPlugin[]; legacyMetadata: { uiSettings: { diff --git a/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap b/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap index 69f534cf837b4..e92e760b400e5 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap +++ b/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap @@ -3,6 +3,9 @@ exports[`RenderingService preboot() render() renders "core" CDN url injected 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "http://foo.bar:1773", "basePath": "/mock-server-basepath", "branch": Any, @@ -75,7 +78,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -83,6 +85,9 @@ Object { exports[`RenderingService preboot() render() renders "core" page 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -151,7 +156,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -159,6 +163,9 @@ Object { exports[`RenderingService preboot() render() renders "core" page driven by settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -231,7 +238,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -239,6 +245,9 @@ Object { exports[`RenderingService preboot() render() renders "core" page for blank basepath 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "", "branch": Any, @@ -307,7 +316,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -315,6 +323,9 @@ Object { exports[`RenderingService preboot() render() renders "core" page for unauthenticated requests 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -383,7 +394,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -391,6 +401,9 @@ Object { exports[`RenderingService preboot() render() renders "core" page with global settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -463,7 +476,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -471,6 +483,9 @@ Object { exports[`RenderingService preboot() render() renders "core" with excluded global user settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -539,7 +554,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -547,6 +561,9 @@ Object { exports[`RenderingService preboot() render() renders "core" with excluded user settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -615,7 +632,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -623,6 +639,9 @@ Object { exports[`RenderingService setup() render() renders "core" CDN url injected 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -700,7 +719,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -708,6 +726,9 @@ Object { exports[`RenderingService setup() render() renders "core" page 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -776,7 +797,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -784,6 +804,9 @@ Object { exports[`RenderingService setup() render() renders "core" page driven by settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -861,7 +884,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -869,6 +891,9 @@ Object { exports[`RenderingService setup() render() renders "core" page for blank basepath 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "", "branch": Any, @@ -942,7 +967,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -950,6 +974,9 @@ Object { exports[`RenderingService setup() render() renders "core" page for unauthenticated requests 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -1018,7 +1045,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -1026,6 +1052,9 @@ Object { exports[`RenderingService setup() render() renders "core" page with global settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -1103,7 +1132,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -1111,6 +1139,9 @@ Object { exports[`RenderingService setup() render() renders "core" with excluded global user settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -1184,7 +1215,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; @@ -1192,6 +1222,9 @@ Object { exports[`RenderingService setup() render() renders "core" with excluded user settings 1`] = ` Object { "anonymousStatusPage": false, + "apmConfig": Object { + "stubApmConfig": true, + }, "assetsHrefBase": "/mock-server-basepath", "basePath": "/mock-server-basepath", "branch": Any, @@ -1265,7 +1298,6 @@ Object { "version": "v8", }, "uiPlugins": Array [], - "vars": Object {}, "version": Any, } `; diff --git a/packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.mocks.ts b/packages/core/rendering/core-rendering-server-internal/src/get_apm_config.test.mocks.ts similarity index 100% rename from packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.mocks.ts rename to packages/core/rendering/core-rendering-server-internal/src/get_apm_config.test.mocks.ts diff --git a/packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.ts b/packages/core/rendering/core-rendering-server-internal/src/get_apm_config.test.ts similarity index 100% rename from packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.ts rename to packages/core/rendering/core-rendering-server-internal/src/get_apm_config.test.ts diff --git a/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts b/packages/core/rendering/core-rendering-server-internal/src/get_apm_config.ts similarity index 100% rename from packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts rename to packages/core/rendering/core-rendering-server-internal/src/get_apm_config.ts diff --git a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.mocks.ts b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.mocks.ts index e7d95312ed56a..96807a64b9560 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.mocks.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.mocks.ts @@ -28,3 +28,10 @@ jest.doMock('./render_utils', () => ({ getScriptPaths: getScriptPathsMock, getBrowserLoggingConfig: getBrowserLoggingConfigMock, })); + +export const getApmConfigMock = jest.fn(); +jest.doMock('./get_apm_config', () => { + return { + getApmConfig: getApmConfigMock, + }; +}); diff --git a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts index b07b8a1cd6fa1..9adf0a0ea3d69 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.test.ts @@ -14,6 +14,7 @@ import { getThemeStylesheetPathsMock, getScriptPathsMock, getBrowserLoggingConfigMock, + getApmConfigMock, } from './rendering_service.test.mocks'; import { load } from 'cheerio'; @@ -259,6 +260,25 @@ function renderTestCases( expect(data.logging).toEqual(loggingConfig); }); + it('renders "core" with APM config injected', async () => { + const someApmConfig = { someConfig: 9000 }; + getApmConfigMock.mockReturnValue(someApmConfig); + + const request = createKibanaRequest(); + + const [render] = await getRender(); + const content = await render(createKibanaRequest(), uiSettings, { + isAnonymousPage: false, + }); + + expect(getApmConfigMock).toHaveBeenCalledTimes(1); + expect(getApmConfigMock).toHaveBeenCalledWith(request.url.pathname); + + const dom = load(content); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); + expect(data.apmConfig).toEqual(someApmConfig); + }); + it('use the correct translation url when CDN is enabled', async () => { const userSettings = { 'theme:darkMode': { userValue: true } }; uiSettings.client.getUserProvided.mockResolvedValue(userSettings); @@ -511,6 +531,7 @@ describe('RenderingService', () => { getThemeStylesheetPathsMock.mockReturnValue(['/style-1.css', '/style-2.css']); getScriptPathsMock.mockReturnValue(['/script-1.js']); getBrowserLoggingConfigMock.mockReset().mockReturnValue({}); + getApmConfigMock.mockReset().mockReturnValue({ stubApmConfig: true }); }); describe('preboot()', () => { diff --git a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx index 7a7e6e56fb49f..4b7e75ea9fb84 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx +++ b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx @@ -42,6 +42,7 @@ import { getBrowserLoggingConfig, } from './render_utils'; import { filterUiPlugins } from './filter_ui_plugins'; +import { getApmConfig } from './get_apm_config'; import type { InternalRenderingRequestHandlerContext } from './internal_types'; type RenderOptions = @@ -121,7 +122,7 @@ export class RenderingService { client: IUiSettingsClient; globalClient: IUiSettingsClient; }, - { isAnonymousPage = false, vars, includeExposedConfigKeys }: IRenderOptions = {} + { isAnonymousPage = false, includeExposedConfigKeys }: IRenderOptions = {} ) { const { elasticsearch, http, uiPlugins, status, customBranding, userSettings, i18n } = renderOptions; @@ -221,6 +222,7 @@ export class RenderingService { translationsUrl = `${serverBasePath}/translations/${translationHash}/${locale}.json`; } + const apmConfig = getApmConfig(request.url.pathname); const filteredPlugins = filterUiPlugins({ uiPlugins, isAnonymousPage }); const bootstrapScript = isAnonymousPage ? 'bootstrap-anonymous.js' : 'bootstrap.js'; const metadata: RenderingMetadata = { @@ -249,6 +251,7 @@ export class RenderingService { logging: loggingConfig, env, clusterInfo, + apmConfig, anonymousStatusPage: status?.isStatusPageAnonymous() ?? false, i18n: { translationsUrl, @@ -268,7 +271,6 @@ export class RenderingService { }, csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers }, externalUrl: http.externalUrl, - vars: vars ?? {}, uiPlugins: await Promise.all( filteredPlugins.map(async ([id, plugin]) => { const { browserConfig, exposedConfigKeys } = await getUiConfig(uiPlugins, id); diff --git a/packages/core/rendering/core-rendering-server-internal/src/types.ts b/packages/core/rendering/core-rendering-server-internal/src/types.ts index ed6a45d071423..98887a9f9da29 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/types.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/types.ts @@ -64,13 +64,6 @@ export interface IRenderOptions { */ isAnonymousPage?: boolean; - /** - * Inject custom vars into the page metadata. - * @deprecated for legacy use only. Can be removed when https://github.com/elastic/kibana/issues/127733 is done. - * @internal - */ - vars?: Record; - /** * @internal * This is only used for integration tests that allow us to verify which config keys are exposed to the browser. diff --git a/packages/core/rendering/core-rendering-server-internal/tsconfig.json b/packages/core/rendering/core-rendering-server-internal/tsconfig.json index e306dca24059c..2689069f79d79 100644 --- a/packages/core/rendering/core-rendering-server-internal/tsconfig.json +++ b/packages/core/rendering/core-rendering-server-internal/tsconfig.json @@ -44,6 +44,7 @@ "@kbn/core-i18n-server", "@kbn/core-i18n-server-internal", "@kbn/core-i18n-server-mocks", + "@kbn/apm-config-loader", ], "exclude": [ "target/**/*", diff --git a/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.ts b/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.ts index c1ca8cb752d2d..f7a0685967ba8 100644 --- a/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.ts +++ b/packages/core/root/core-root-browser-internal/src/kbn_bootstrap.ts @@ -7,6 +7,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { InjectedMetadata } from '@kbn/core-injected-metadata-common-internal'; import { KBN_LOAD_MARKS } from './events'; import { CoreSystem } from './core_system'; import { ApmSystem } from './apm_system'; @@ -19,12 +20,15 @@ export async function __kbnBootstrap__() { detail: LOAD_BOOTSTRAP_START, }); - const injectedMetadata = JSON.parse( + const injectedMetadata: InjectedMetadata = JSON.parse( document.querySelector('kbn-injected-metadata')!.getAttribute('data')! ); let i18nError: Error | undefined; - const apmSystem = new ApmSystem(injectedMetadata.vars.apmConfig, injectedMetadata.basePath); + const apmSystem = new ApmSystem( + injectedMetadata.apmConfig ?? undefined, + injectedMetadata.basePath + ); await Promise.all([ // eslint-disable-next-line no-console diff --git a/packages/core/root/core-root-browser-internal/tsconfig.json b/packages/core/root/core-root-browser-internal/tsconfig.json index 152c7d3683e38..e576ecf8cf920 100644 --- a/packages/core/root/core-root-browser-internal/tsconfig.json +++ b/packages/core/root/core-root-browser-internal/tsconfig.json @@ -66,6 +66,7 @@ "@kbn/core-security-browser-internal", "@kbn/core-user-profile-browser-mocks", "@kbn/core-user-profile-browser-internal", + "@kbn/core-injected-metadata-common-internal", ], "exclude": [ "target/**/*", From 0db883a9d5adfbf27b753a8bfe768b9665769b06 Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Mon, 22 Jul 2024 14:09:57 +0200 Subject: [PATCH 12/14] [Infra] Disable Top 10 functions full screen table in a flyout (#188743) ## Summary This PR disables functions full-screen option in the asset details flyout. It adds control of the `showFullScreenSelector` to the `EmbeddableFunctionsGrid` so we can set it based on the render mode in the asset details - in apm it will be set to true as before. ## Testing - Go to the asset details page - Check the Universal Profiling tab > Top 10 Functions tab - The full-screen option should be visible: ![image](https://github.com/user-attachments/assets/aace7ca0-ed4f-404d-8cbf-91c29088c133) - Same in the tab inside APM service view: ![image](https://github.com/user-attachments/assets/f885630a-4f43-4c23-9ff4-05c03f7ede72) - Go to Hosts view and open the flyout for a host - The full-screen option should not be visible: ![image](https://github.com/user-attachments/assets/e724c569-df7a-4e4a-b6c3-9dcfa7b60ad6) --------- Co-authored-by: Elastic Machine --- .../components/asset_details/tabs/profiling/functions.tsx | 3 +++ .../profiling/embeddables/embeddable_functions.tsx | 1 + .../profiling/public/components/topn_functions/index.tsx | 4 +++- .../public/embeddables/functions/embeddable_functions.tsx | 8 +++++++- .../embeddables/functions/embeddable_functions_grid.tsx | 4 +++- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/profiling/functions.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/profiling/functions.tsx index 811172fca2695..5dee9438b280c 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/profiling/functions.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/profiling/functions.tsx @@ -31,11 +31,13 @@ export function Functions({ kuery }: Props) { const { dateRange, getDateRangeInTimestamp } = useDatePickerContext(); const { from, to } = getDateRangeInTimestamp(); const { request$ } = useRequestObservable(); + const { renderMode } = useAssetDetailsRenderPropsContext(); const profilingLinkLocator = services.observabilityShared.locators.profiling.topNFunctionsLocator; const profilingLinkLabel = i18n.translate('xpack.infra.flamegraph.profilingAppTopFunctionsLink', { defaultMessage: 'Go to Universal Profiling Functions', }); + const showFullScreenSelector = renderMode.mode === 'page'; const params = useMemo( () => ({ @@ -86,6 +88,7 @@ export function Functions({ kuery }: Props) { rangeFrom={from} rangeTo={to} height="60vh" + showFullScreenSelector={showFullScreenSelector} /> ); diff --git a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx index c4fad57b890ce..aa5d3c335ec05 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx @@ -17,6 +17,7 @@ interface Props { rangeFrom: number; rangeTo: number; height?: string; + showFullScreenSelector?: boolean; } export function EmbeddableFunctions(props: Props) { diff --git a/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/index.tsx b/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/index.tsx index 4f56865d54a84..cad716654a843 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/topn_functions/index.tsx @@ -32,6 +32,7 @@ interface Props { comparisonTopNFunctions?: TopNFunctions; totalSeconds: number; isDifferentialView: boolean; + showFullScreenSelector?: boolean; baselineScaleFactor?: number; comparisonScaleFactor?: number; onFrameClick?: (functionName: string) => void; @@ -50,6 +51,7 @@ export const TopNFunctionsGrid = ({ topNFunctions, comparisonTopNFunctions, totalSeconds, + showFullScreenSelector = true, isDifferentialView, baselineScaleFactor, comparisonScaleFactor, @@ -316,7 +318,7 @@ export const TopNFunctionsGrid = ({ showColumnSelector: false, showKeyboardShortcuts: !isDifferentialView, showDisplaySelector: !isDifferentialView, - showFullScreenSelector: !isDifferentialView, + showFullScreenSelector: showFullScreenSelector && !isDifferentialView, showSortSelector: false, }} virtualizationOptions={{ diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx index e281f12224c8f..851eeeb8fe103 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx @@ -23,6 +23,7 @@ export interface FunctionsProps { isLoading: boolean; rangeFrom: number; rangeTo: number; + showFullScreenSelector?: boolean; } export function EmbeddableFunctions({ @@ -30,6 +31,7 @@ export function EmbeddableFunctions({ isLoading, rangeFrom, rangeTo, + showFullScreenSelector, ...deps }: EmbeddableFunctionsProps) { const totalSeconds = useMemo(() => (rangeTo - rangeFrom) / 1000, [rangeFrom, rangeTo]); @@ -37,7 +39,11 @@ export function EmbeddableFunctions({
- +
diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions_grid.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions_grid.tsx index 5579217a59d22..8b4dfd5d62c27 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions_grid.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions_grid.tsx @@ -13,9 +13,10 @@ import { TopNFunctionsGrid } from '../../components/topn_functions'; interface Props { data?: TopNFunctions; totalSeconds: number; + showFullScreenSelector?: boolean; } -export function EmbeddableFunctionsGrid({ data, totalSeconds }: Props) { +export function EmbeddableFunctionsGrid({ data, totalSeconds, showFullScreenSelector }: Props) { const [sortField, setSortField] = useState(TopNFunctionSortField.Rank); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); const [pageIndex, setPageIndex] = useState(0); @@ -34,6 +35,7 @@ export function EmbeddableFunctionsGrid({ data, totalSeconds }: Props) { setSortDirection(sorting.direction); }} isEmbedded + showFullScreenSelector={showFullScreenSelector} /> ); } From bc42310da54a6f60944b408126a0eb58736cd3a5 Mon Sep 17 00:00:00 2001 From: Joe McElroy Date: Mon, 22 Jul 2024 13:19:35 +0100 Subject: [PATCH 13/14] [Search] [Getting Started Guide] Update examples (#188642) ## Summary Updating the quick start guides. Changes: - introduce a new semantic search guide which talks through semantic_text - vector search updates to make the examples simpler + callouts to use semantic search with semantic_text - Updates to AI search to make it more up to date ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Co-authored-by: Rodney Norris --- packages/kbn-doc-links/src/get_doc_links.ts | 3 + packages/kbn-doc-links/src/types.ts | 3 + .../collectors/application_usage/schema.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 131 +++++++ .../enterprise_search/common/constants.ts | 17 + .../ai_search_guide/ai_search_guide.tsx | 6 +- .../ai_search_guide/elser_panel.tsx | 177 +++------ .../rank_aggregation_section.tsx | 4 +- .../ai_search_guide/rrf_ranking_panel.tsx | 42 +- .../semantic_search_section.tsx | 8 +- .../ai_search_guide/vector_search_panel.tsx | 197 +++------- .../components/layout/page_template.tsx | 38 ++ .../semantic_search_guide.scss | 6 + .../semantic_search_guide.tsx | 360 ++++++++++++++++++ .../applications/semantic_search/index.tsx | 41 ++ .../semantic_search/jest.config.js | 26 ++ .../applications/semantic_search/routes.ts | 8 + .../shared/doc_links/doc_links.ts | 9 + .../kibana_chrome/generate_breadcrumbs.ts | 4 + .../shared/kibana_chrome/generate_title.ts | 4 + .../shared/kibana_chrome/index.ts | 1 + .../shared/kibana_chrome/set_chrome.tsx | 18 + .../applications/shared/layout/nav.test.tsx | 6 + .../public/applications/shared/layout/nav.tsx | 9 + .../vector_search_guide.tsx | 83 ++-- .../enterprise_search/public/plugin.ts | 22 ++ .../enterprise_search/server/plugin.ts | 2 + .../translations/translations/fr-FR.json | 15 - .../translations/translations/ja-JP.json | 15 - .../translations/translations/zh-CN.json | 15 - .../security_and_spaces/tests/catalogue.ts | 2 + .../security_and_spaces/tests/nav_links.ts | 2 + .../spaces_only/tests/catalogue.ts | 1 + .../spaces_only/tests/nav_links.ts | 1 + 34 files changed, 900 insertions(+), 377 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/semantic_search/components/layout/page_template.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/semantic_search/index.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js create mode 100644 x-pack/plugins/enterprise_search/public/applications/semantic_search/routes.ts diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 98f5ba5d8b5da..fef52545f8d2f 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -196,9 +196,11 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D crawlerOverview: `${ENTERPRISE_SEARCH_DOCS}crawler.html`, deployTrainedModels: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-models.html`, documentLevelSecurity: `${ELASTICSEARCH_DOCS}document-level-security.html`, + e5Model: `${MACHINE_LEARNING_DOCS}ml-nlp-e5.html`, elser: `${ELASTICSEARCH_DOCS}semantic-search-elser.html`, engines: `${ENTERPRISE_SEARCH_DOCS}engines.html`, indexApi: `${ELASTICSEARCH_DOCS}docs-index_.html`, + inferenceApiCreate: `${ELASTICSEARCH_DOCS}put-inference-api.html`, ingestionApis: `${ELASTICSEARCH_DOCS}search-with-elasticsearch.html`, ingestPipelines: `${ELASTICSEARCH_DOCS}ingest-pipeline-search.html`, knnSearch: `${ELASTICSEARCH_DOCS}knn-search.html`, @@ -216,6 +218,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D searchLabs: `${SEARCH_LABS_URL}`, searchLabsRepo: `${SEARCH_LABS_REPO}`, searchTemplates: `${ELASTICSEARCH_DOCS}search-template.html`, + semanticTextField: `${ELASTICSEARCH_DOCS}semantic-text.html`, start: `${ENTERPRISE_SEARCH_DOCS}start.html`, supportedNlpModels: `${MACHINE_LEARNING_DOCS}ml-nlp-model-ref.html`, syncRules: `${ENTERPRISE_SEARCH_DOCS}sync-rules.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index c6b1b753ce883..d271ed918327a 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -160,9 +160,11 @@ export interface DocLinks { readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; + readonly e5Model: string; readonly elser: string; readonly engines: string; readonly indexApi: string; + readonly inferenceApiCreate: string; readonly ingestionApis: string; readonly ingestPipelines: string; readonly knnSearch: string; @@ -180,6 +182,7 @@ export interface DocLinks { readonly searchLabs: string; readonly searchLabsRepo: string; readonly searchTemplates: string; + readonly semanticTextField: string; readonly start: string; readonly supportedNlpModels: string; readonly syncRules: string; diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 8d6343337f152..9d14c3f1f7317 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -139,6 +139,7 @@ export const applicationUsageSchema = { enterpriseSearchAnalytics: commonSchema, enterpriseSearchApplications: commonSchema, enterpriseSearchAISearch: commonSchema, + enterpriseSearchSemanticSearch: commonSchema, enterpriseSearchVectorSearch: commonSchema, enterpriseSearchElasticsearch: commonSchema, appSearch: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index d36836e0f0747..c0689afef492d 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -2622,6 +2622,137 @@ } } }, + "enterpriseSearchSemanticSearch": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "enterpriseSearchVectorSearch": { "properties": { "appId": { diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index f2be720d1c04c..1e498b92fd0ac 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -177,6 +177,23 @@ export const VECTOR_SEARCH_PLUGIN = { URL: '/app/enterprise_search/vector_search', }; +export const SEMANTIC_SEARCH_PLUGIN = { + DESCRIPTION: i18n.translate('xpack.enterpriseSearch.SemanticSearch.description', { + defaultMessage: + 'Easily add semantic search to Elasticsearch with inference endpoints and the semantic_text field type, to boost search relevance.', + }), + ID: 'enterpriseSearchSemanticSearch', + LOGO: 'logoEnterpriseSearch', + NAME: i18n.translate('xpack.enterpriseSearch.SemanticSearch.productName', { + defaultMessage: 'Semantic Search', + }), + NAV_TITLE: i18n.translate('xpack.enterpriseSearch.SemanticSearch.navTitle', { + defaultMessage: 'Semantic Search', + }), + SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/', + URL: '/app/enterprise_search/semantic_search', +}; + export const INFERENCE_ENDPOINTS_PLUGIN = { ID: ENTERPRISE_SEARCH_RELEVANCE_APP_ID, NAME: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.productName', { diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx index 67c014d572ac8..7374ecd0ac359 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx @@ -40,7 +40,7 @@ export const AISearchGuide: React.FC = () => { bottomBorder={false} pageHeader={{ pageTitle: i18n.translate('xpack.enterpriseSearch.aiSearch.guide.pageTitle', { - defaultMessage: 'Enhance your search with AI', + defaultMessage: 'Improve search revelance with AI', }), }} > @@ -81,11 +81,11 @@ export const AISearchGuide: React.FC = () => { - + - + diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/elser_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/elser_panel.tsx index c56ea6aa1b277..65003e362c3ed 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/elser_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/elser_panel.tsx @@ -7,125 +7,72 @@ import React from 'react'; -import { generatePath } from 'react-router-dom'; +import { useValues } from 'kea'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiSteps, - EuiText, -} from '@elastic/eui'; -import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../../common/constants'; -import { NEW_INDEX_PATH } from '../../../enterprise_search_content/routes'; +import { SEMANTIC_SEARCH_PLUGIN } from '../../../../../common/constants'; import { docLinks } from '../../../shared/doc_links'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; +import { HttpLogic } from '../../../shared/http'; +import { KibanaLogic } from '../../../shared/kibana'; -const steps: EuiContainedStepProps[] = [ - { - title: i18n.translate('xpack.enterpriseSearch.aiSearch.elserPanel.step1.title', { - defaultMessage: 'Create an index', - }), - children: ( - - - {i18n.translate('xpack.enterpriseSearch.aiSearch.elserPanel.step1.buttonLabel', { - defaultMessage: 'Create an index', - })} - - - ), - status: 'incomplete', - }, - { - title: i18n.translate('xpack.enterpriseSearch.aiSearch.elserPanel.step2.title', { - defaultMessage: "Navigate to index's Pipelines tab", - }), - children: ( - -

- - " - {i18n.translate( - 'xpack.enterpriseSearch.aiSearch.elserPanel.step2.description.pipelinesName', - { - defaultMessage: 'Pipelines', - } - )} - " - - ), - }} - /> -

-
- ), - status: 'incomplete', - }, - { - title: i18n.translate('xpack.enterpriseSearch.aiSearch.elserPanel.step3.title', { - defaultMessage: 'Follow the on-screen instructions to deploy ELSER', - }), - children: ( - -

- -

-
- ), - status: 'incomplete', - }, -]; +export const ElserPanel: React.FC = () => { + const { http } = useValues(HttpLogic); + const { application } = useValues(KibanaLogic); -export const ElserPanel: React.FC = () => ( - <> - - - - -

- - {i18n.translate( - 'xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText', - { - defaultMessage: 'Elastic Learned Sparse Encoder v2', - } - )} - - ), - }} - /> -

-
-
- - - -
- -); + return ( + <> + + + + +

+ + {i18n.translate( + 'xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText', + { + defaultMessage: 'Elastic Learned Sparse Encoder v2', + } + )} + + ), + }} + /> +

+
+
+ + { + application.navigateToUrl( + http.basePath.prepend(`${SEMANTIC_SEARCH_PLUGIN.URL}?model_example=elser`) + ); + }} + > + + {i18n.translate('xpack.enterpriseSearch.aiSearch.elserPanel.buttonLabel', { + defaultMessage: 'Set up Semantic Search', + })} + + + +
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rank_aggregation_section.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rank_aggregation_section.tsx index b495df58d5435..64e39d1e58f34 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rank_aggregation_section.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rank_aggregation_section.tsx @@ -30,7 +30,7 @@ export const RankAggregationSection: React.FC = () => {

@@ -40,7 +40,7 @@ export const RankAggregationSection: React.FC = () => {

diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rrf_ranking_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rrf_ranking_panel.tsx index 8353a7de8a148..75eade2b2e07b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rrf_ranking_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/rrf_ranking_panel.tsx @@ -7,24 +7,12 @@ import React from 'react'; -import { generatePath } from 'react-router-dom'; - -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiSteps, - EuiText, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiSteps, EuiText } from '@elastic/eui'; import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DEV_TOOLS_CONSOLE_PATH } from '../../../enterprise_search_content/routes'; import { docLinks } from '../../../shared/doc_links'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; const steps: EuiContainedStepProps[] = [ { @@ -33,6 +21,7 @@ const steps: EuiContainedStepProps[] = [ }), children: ( - - {i18n.translate('xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.buttonLabel', { - defaultMessage: 'Open Console', - })} - - + {i18n.translate('xpack.enterpriseSearch.aiSearch.rrfRankingPanel.step2.buttonLabel', { + defaultMessage: 'View Notebook', + })} + ), status: 'incomplete', }, @@ -79,7 +68,12 @@ export const RrfRankingPanel: React.FC = () => ( defaultMessage="Use {rrf} to combine rankings from multiple result sets with different relevance indicators, with no fine tuning required." values={{ rrf: ( - + {i18n.translate('xpack.enterpriseSearch.aiSearch.rrfRankingPanel.rrfLinkText', { defaultMessage: 'Reciprocal Rank Fusion (RRF)', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/semantic_search_section.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/semantic_search_section.tsx index 622e3144eeaee..14a61bfa3e3cf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/semantic_search_section.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/semantic_search_section.tsx @@ -32,7 +32,7 @@ export const SemanticSearchSection: React.FC = () => {

@@ -42,7 +42,7 @@ export const SemanticSearchSection: React.FC = () => {

@@ -58,7 +58,7 @@ export const SemanticSearchSection: React.FC = () => { initialIsOpen icon={elserIllustration} title={i18n.translate('xpack.enterpriseSearch.aiSearch.elserAccordion.title', { - defaultMessage: 'Elastic Learned Sparse Encoder', + defaultMessage: 'Semantic search with ELSER', })} description={i18n.translate( 'xpack.enterpriseSearch.aiSearch.elserAccordion.description', @@ -106,7 +106,7 @@ export const SemanticSearchSection: React.FC = () => { description={i18n.translate( 'xpack.enterpriseSearch.aiSearch.nlpEnrichmentAccordion.description', { - defaultMessage: 'Insightful data enrichment with trained ML models', + defaultMessage: 'Extract entities and sentiment from text', } )} currentExpandedId={currentExpandedId} diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/vector_search_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/vector_search_panel.tsx index f9adf365efadc..f28f1fca8839a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/vector_search_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/vector_search_panel.tsx @@ -7,156 +7,71 @@ import React from 'react'; -import { generatePath } from 'react-router-dom'; +import { useValues } from 'kea'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiSteps, - EuiText, -} from '@elastic/eui'; -import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../../common/constants'; -import { - ML_MANAGE_TRAINED_MODELS_PATH, - NEW_INDEX_PATH, -} from '../../../enterprise_search_content/routes'; +import { VECTOR_SEARCH_PLUGIN } from '../../../../../common/constants'; import { docLinks } from '../../../shared/doc_links'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; +import { HttpLogic } from '../../../shared/http'; +import { KibanaLogic } from '../../../shared/kibana'; -const steps: EuiContainedStepProps[] = [ - { - title: i18n.translate('xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.title', { - defaultMessage: 'Learn how to upload ML models', - }), - children: ( +export const VectorSearchPanel: React.FC = () => { + const { http } = useValues(HttpLogic); + const { application } = useValues(KibanaLogic); + + return ( + <> + - - - {i18n.translate( - 'xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.guideToTrainedModelsLinkText', - { defaultMessage: 'Guide to trained models' } - )} - + + +

+ + {i18n.translate( + 'xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText', + { + defaultMessage: "Elasticsearch's vector DB capabilities", + } + )} + + ), + }} + /> +

+
- - + { + application.navigateToUrl(http.basePath.prepend(`${VECTOR_SEARCH_PLUGIN.URL}`)); + }} > - - {i18n.translate( - 'xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.buttonLabel', - { - defaultMessage: 'View trained models', - } - )} + + {i18n.translate('xpack.enterpriseSearch.aiSearch.vectorSearchPanel.buttonLabel', { + defaultMessage: 'Set up Vector Search', + })} - +
- ), - status: 'incomplete', - }, - { - title: i18n.translate('xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.title', { - defaultMessage: 'Create an index', - }), - children: ( - - - {i18n.translate('xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.buttonLabel', { - defaultMessage: 'Create an index', - })} - - - ), - status: 'incomplete', - }, - { - title: i18n.translate('xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.title', { - defaultMessage: 'Create a ML inference pipeline', - }), - children: ( - -

- - " - {i18n.translate( - 'xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description.pipelinesName', - { - defaultMessage: 'Pipelines', - } - )} - " - - ), - }} - /> -

-
- ), - status: 'incomplete', - }, -]; - -export const VectorSearchPanel: React.FC = () => ( - <> - - - - -

- - {i18n.translate( - 'xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText', - { - defaultMessage: "Elasticsearch's vector DB capabilities", - } - )} - - ), - }} - /> -

-
-
- - - -
- -); + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/layout/page_template.tsx new file mode 100644 index 0000000000000..72ddbffc263c7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/layout/page_template.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { SEARCH_PRODUCT_NAME } from '../../../../../common/constants'; +import { SetSemanticSearchChrome } from '../../../shared/kibana_chrome'; +import { + EnterpriseSearchPageTemplateWrapper, + PageTemplateProps, + useEnterpriseSearchNav, +} from '../../../shared/layout'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; + +export const EnterpriseSearchSemanticSearchPageTemplate: React.FC = ({ + children, + pageChrome, + pageViewTelemetry, + ...pageTemplateProps +}) => ( + } + > + {pageViewTelemetry && ( + + )} + {children} + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.scss b/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.scss new file mode 100644 index 0000000000000..15fb1b242ac76 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.scss @@ -0,0 +1,6 @@ +.chooseEmbeddingModelSelectedBorder { + border: 1px solid $euiColorPrimary; +} +.chooseEmbeddingModelBorder { + border: 1px solid $euiColorLightShade; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.tsx new file mode 100644 index 0000000000000..1b373da7a9ff2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/semantic_search/components/semantic_search_guide/semantic_search_guide.tsx @@ -0,0 +1,360 @@ +/* + * 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 { useSearchParams } from 'react-router-dom-v5-compat'; + +import { + EuiCard, + EuiCode, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { SetAISearchChromeSearchDocsSection } from '../../../ai_search/components/ai_search_guide/ai_search_docs_section'; +import { docLinks } from '../../../shared/doc_links'; +import { SetSemanticSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { DevToolsConsoleCodeBlock } from '../../../vector_search/components/dev_tools_console_code_block/dev_tools_console_code_block'; +import './semantic_search_guide.scss'; +import { EnterpriseSearchSemanticSearchPageTemplate } from '../layout/page_template'; + +const SETUP_INFERENCE_ENDPOINT_ELSER = `PUT _inference/sparse_embedding/my-inference-endpoint +{ + "service": "elser", + "service_settings": { + "num_allocations": 1, + "num_threads": 1 + } +} +`; + +const SETUP_INFERENCE_ENDPOINT_E5 = `PUT _inference/text_embedding/my-inference-endpoint +{ + "service": "elasticsearch", + "service_settings": { + "model_id": ".multilingual-e5-small", + "num_allocations": 1, + "num_threads": 1 + } +} +`; + +const SETUP_INFERENCE_ENDPOINT_OPENAI = `PUT _inference/text_embedding/my-inference-endpoint +{ + "service": "openai", + "service_settings": { + "model_id": "text-embedding-3-small", + "api_key": "", + } +} +`; + +const SETUP_INFERENCE_ENDPOINT_BEDROCK = `PUT _inference/text_embedding/my-inference-endpoint +{ + "service": "amazonbedrock", + "service_settings": { + "access_key": "", + "secret_key": "", + "region": "us-east-1", + "provider": "amazontitan", + "model": "amazon.titan-embed-text-v2:0" + } +} +`; + +const CREATE_INDEX_SNIPPET = `PUT /my-index +{ + "mappings": { + "properties": { + "text": { + "type": "semantic_text", + "inference_id": "my-inference-endpoint" + } + } + } +}`; + +const INGEST_SNIPPET = `POST /my-index/_doc +{ + "text": "There are a few foods and food groups that will help to fight inflammation and delayed onset muscle soreness (both things that are inevitable after a long, hard workout) when you incorporate them into your postworkout eats, whether immediately after your run or at a meal later in the day" +}`; + +const QUERY_SNIPPET = `POST /my-index/_search +{ + "size" : 3, + "query" : { + "semantic": { + "field": "text", + "query": "How to avoid muscle soreness while running?" + } + } +}`; + +const modelSelection: InferenceModel[] = [ + { + id: 'elser', + modelName: 'ELSER', + code: SETUP_INFERENCE_ENDPOINT_ELSER, + link: docLinks.elser, + description: "Elastic's proprietary, best-in-class sparse vector model for semantic search.", + }, + { + id: 'e5', + modelName: 'E5 Multilingual', + code: SETUP_INFERENCE_ENDPOINT_E5, + link: docLinks.e5Model, + description: 'Try an optimized third party multilingual model.', + }, + { + code: SETUP_INFERENCE_ENDPOINT_OPENAI, + id: 'openai', + modelName: 'OpenAI', + link: 'https://platform.openai.com/docs/guides/embeddings', + description: "Connect with OpenAI's embedding models.", + }, + { + id: 'bedrock', + modelName: 'Amazon Bedrock', + code: SETUP_INFERENCE_ENDPOINT_BEDROCK, + link: 'https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html', + description: "Use Amazon Bedrock's embedding models.", + }, +]; + +interface SelectModelPanelProps { + isSelectedModel: boolean; + model: InferenceModel; + setSelectedModel: (model: InferenceModel) => void; +} + +interface InferenceModel { + code: string; + id: string; + link: string; + modelName: string; + description: string; +} + +const SelectModelPanel: React.FC = ({ + model, + setSelectedModel, + isSelectedModel, +}) => { + return ( + + + +

{model.description}

+
+ + + + + + } + display={isSelectedModel ? 'primary' : 'plain'} + onClick={() => setSelectedModel(model)} + titleSize="xs" + hasBorder + textAlign="left" + /> +
+ ); +}; + +export const SemanticSearchGuide: React.FC = () => { + const [searchParams] = useSearchParams(); + const chosenUrlModel = + modelSelection.find((model) => model.id === searchParams.get('model_example')) || + modelSelection[0]; + const [selectedModel, setSelectedModel] = React.useState(chosenUrlModel); + + return ( + + {' '} + + + +

+ ), + pageTitle: ( + + ), + }} + > + + + + +

+ +

+
+ + +

+ +

+

+ + + +

+
+ + + {modelSelection.map((model) => ( + + ))} + +
+ + {selectedModel.code} + +
+ + + + +

+ +

+
+ + +

+ semantic_text }} + /> +

+
+
+ + {CREATE_INDEX_SNIPPET} + +
+ + + + +

+ +

+
+ + +

+ +

+
+
+ + {INGEST_SNIPPET} + +
+ + + + +

+ +

+
+ + +

+ +

+
+
+ + {QUERY_SNIPPET} + +
+ + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/semantic_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/semantic_search/index.tsx new file mode 100644 index 0000000000000..f33142bae940c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/semantic_search/index.tsx @@ -0,0 +1,41 @@ +/* + * 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'; +// switch is not available on shared-ux-router +// eslint-disable-next-line no-restricted-imports +import { Switch } from 'react-router-dom'; + +import { Route } from '@kbn/shared-ux-router'; + +import { isVersionMismatch } from '../../../common/is_version_mismatch'; +import { InitialAppData } from '../../../common/types'; +import { VersionMismatchPage } from '../shared/version_mismatch'; + +import { SemanticSearchGuide } from './components/semantic_search_guide/semantic_search_guide'; + +import { ROOT_PATH } from './routes'; + +export const EnterpriseSearchSemanticSearch: React.FC = (props) => { + const { enterpriseSearchVersion, kibanaVersion } = props; + const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion); + + return ( + + + {incompatibleVersions ? ( + + ) : ( + + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js new file mode 100644 index 0000000000000..7711d9b97523f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../../..', + roots: ['/x-pack/plugins/enterprise_search/public/applications/semantic_search'], + collectCoverage: true, + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}', + '!/x-pack/plugins/enterprise_search/public/*.ts', + '!/x-pack/plugins/enterprise_search/server/*.ts', + '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}', + ], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/semantic_search', + modulePathIgnorePatterns: [ + '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', + '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', + ], +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/semantic_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/semantic_search/routes.ts new file mode 100644 index 0000000000000..d6b0b0a669281 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/semantic_search/routes.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ROOT_PATH = '/'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 228321ef120c1..311043a442bc8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -103,6 +103,7 @@ class DocLinks { public crawlerOverview: string; public deployTrainedModels: string; public documentLevelSecurity: string; + public e5Model: string; public elasticsearchCreateIndex: string; public elasticsearchGettingStarted: string; public elasticsearchMapping: string; @@ -114,6 +115,7 @@ class DocLinks { public enterpriseSearchTroubleshootSetup: string; public enterpriseSearchUsersAccess: string; public indexApi: string; + public inferenceApiCreate: string; public ingestionApis: string; public ingestPipelines: string; public kibanaSecurity: string; @@ -138,6 +140,7 @@ class DocLinks { public searchTemplates: string; public searchUIAppSearch: string; public searchUIElasticsearch: string; + public semanticTextField: string; public start: string; public supportedNlpModels: string; public syncRules: string; @@ -280,6 +283,7 @@ class DocLinks { this.crawlerOverview = ''; this.deployTrainedModels = ''; this.documentLevelSecurity = ''; + this.e5Model = ''; this.elasticsearchCreateIndex = ''; this.elasticsearchGettingStarted = ''; this.elasticsearchMapping = ''; @@ -291,6 +295,7 @@ class DocLinks { this.enterpriseSearchTroubleshootSetup = ''; this.enterpriseSearchUsersAccess = ''; this.indexApi = ''; + this.inferenceApiCreate = ''; this.ingestionApis = ''; this.ingestPipelines = ''; this.kibanaSecurity = ''; @@ -315,6 +320,7 @@ class DocLinks { this.searchLabs = ''; this.searchLabsRepo = ''; this.searchTemplates = ''; + this.semanticTextField = ''; this.start = ''; this.supportedNlpModels = ''; this.syncRules = ''; @@ -459,6 +465,7 @@ class DocLinks { this.crawlerOverview = docLinks.links.enterpriseSearch.crawlerOverview; this.deployTrainedModels = docLinks.links.enterpriseSearch.deployTrainedModels; this.documentLevelSecurity = docLinks.links.enterpriseSearch.documentLevelSecurity; + this.e5Model = docLinks.links.enterpriseSearch.e5Model; this.elasticsearchCreateIndex = docLinks.links.elasticsearch.createIndex; this.elasticsearchGettingStarted = docLinks.links.elasticsearch.gettingStarted; this.elasticsearchMapping = docLinks.links.elasticsearch.mapping; @@ -470,6 +477,7 @@ class DocLinks { this.enterpriseSearchTroubleshootSetup = docLinks.links.enterpriseSearch.troubleshootSetup; this.enterpriseSearchUsersAccess = docLinks.links.enterpriseSearch.usersAccess; this.indexApi = docLinks.links.enterpriseSearch.indexApi; + this.inferenceApiCreate = docLinks.links.enterpriseSearch.inferenceApiCreate; this.ingestionApis = docLinks.links.enterpriseSearch.ingestionApis; this.ingestPipelines = docLinks.links.enterpriseSearch.ingestPipelines; this.kibanaSecurity = docLinks.links.kibana.xpackSecurity; @@ -494,6 +502,7 @@ class DocLinks { this.searchLabs = docLinks.links.enterpriseSearch.searchLabs; this.searchLabsRepo = docLinks.links.enterpriseSearch.searchLabsRepo; this.searchTemplates = docLinks.links.enterpriseSearch.searchTemplates; + this.semanticTextField = docLinks.links.enterpriseSearch.semanticTextField; this.start = docLinks.links.enterpriseSearch.start; this.supportedNlpModels = docLinks.links.enterpriseSearch.supportedNlpModels; this.syncRules = docLinks.links.enterpriseSearch.syncRules; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index 5798a48680d1f..40e2d2fb27476 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -21,6 +21,7 @@ import { SEARCH_PRODUCT_NAME, VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, + SEMANTIC_SEARCH_PLUGIN, } from '../../../../common/constants'; import { stripLeadingSlash } from '../../../../common/strip_slashes'; @@ -167,3 +168,6 @@ export const useAiSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => export const useVectorSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useSearchBreadcrumbs([{ text: VECTOR_SEARCH_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]); + +export const useSemanticSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => + useSearchBreadcrumbs([{ text: SEMANTIC_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts index c9400393b057b..eaeb30f1540d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts @@ -12,6 +12,7 @@ import { ENTERPRISE_SEARCH_CONTENT_PLUGIN, SEARCH_EXPERIENCES_PLUGIN, SEARCH_PRODUCT_NAME, + SEMANTIC_SEARCH_PLUGIN, VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, } from '../../../../common/constants'; @@ -55,5 +56,8 @@ export const aiSearchTitle = (page: Title = []) => generateTitle([...page, AI_SE export const vectorSearchTitle = (page: Title = []) => generateTitle([...page, VECTOR_SEARCH_PLUGIN.NAME]); +export const semanticSearchTitle = (page: Title = []) => + generateTitle([...page, SEMANTIC_SEARCH_PLUGIN.NAME]); + export const enterpriseSearchContentTitle = (page: Title = []) => generateTitle([...page, ENTERPRISE_SEARCH_CONTENT_PLUGIN.NAME]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts index ae2151287b1d1..f9a6564ab5f28 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts @@ -16,5 +16,6 @@ export { SetWorkplaceSearchChrome, SetSearchExperiencesChrome, SetEnterpriseSearchApplicationsChrome, + SetSemanticSearchChrome, SetVectorSearchChrome, } from './set_chrome'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index ac85bd89b6ba9..8f7c71d1309c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -27,6 +27,7 @@ import { BreadcrumbTrail, useSearchExperiencesBreadcrumbs, useVectorSearchBreadcrumbs, + useSemanticSearchBreadcrumbs, } from './generate_breadcrumbs'; import { aiSearchTitle, @@ -36,6 +37,7 @@ import { enterpriseSearchContentTitle, searchExperiencesTitle, searchTitle, + semanticSearchTitle, vectorSearchTitle, workplaceSearchTitle, } from './generate_title'; @@ -242,5 +244,21 @@ export const SetVectorSearchChrome: React.FC = ({ trail = [] }) return null; }; +export const SetSemanticSearchChrome: React.FC = ({ trail = [] }) => { + const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); + + const title = reverseArray(trail); + const docTitle = semanticSearchTitle(title); + + const breadcrumbs = useSemanticSearchBreadcrumbs(useGenerateBreadcrumbs(trail)); + + useEffect(() => { + setBreadcrumbs(breadcrumbs); + setDocTitle(docTitle); + }, [trail]); + + return null; +}; + // Small util - performantly reverses an array without mutating the original array const reverseArray = (array: string[]) => array.slice().reverse(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index f818dfb9141f3..b2c31ff4868bc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -111,6 +111,12 @@ const baseNavItems = [ items: undefined, name: 'Vector Search', }, + { + href: '/app/enterprise_search/semantic_search', + id: 'semanticSearch', + items: undefined, + name: 'Semantic Search', + }, { href: '/app/enterprise_search/ai_search', id: 'aiSearch', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index e39f0f0b71f29..d0f8a60f3472a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -24,6 +24,7 @@ import { VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, INFERENCE_ENDPOINTS_PLUGIN, + SEMANTIC_SEARCH_PLUGIN, } from '../../../../common/constants'; import { SEARCH_APPLICATIONS_PATH, @@ -204,6 +205,14 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { to: VECTOR_SEARCH_PLUGIN.URL, }), }, + { + id: 'semanticSearch', + name: SEMANTIC_SEARCH_PLUGIN.NAME, + ...generateNavLink({ + shouldNotCreateHref: true, + to: SEMANTIC_SEARCH_PLUGIN.URL, + }), + }, { id: 'aiSearch', name: i18n.translate('xpack.enterpriseSearch.nav.aiSearchTitle', { diff --git a/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx index 3ee0453067747..0cf9cb753c26a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/vector_search/components/vector_search_guide/vector_search_guide.tsx @@ -17,66 +17,56 @@ import { EuiHorizontalRule, EuiIcon, EuiLink, + EuiSpacer, EuiText, EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { AI_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { SEMANTIC_SEARCH_PLUGIN } from '../../../../../common/constants'; import elserIllustration from '../../../../assets/images/elser.svg'; import nlpIllustration from '../../../../assets/images/nlp.svg'; import { docLinks } from '../../../shared/doc_links'; +import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { SetVectorSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { DevToolsConsoleCodeBlock } from '../dev_tools_console_code_block/dev_tools_console_code_block'; import { EnterpriseSearchVectorSearchPageTemplate } from '../layout/page_template'; -const CREATE_INDEX_SNIPPET = `PUT /image-index +const CREATE_INDEX_SNIPPET = `PUT /my-index { "mappings": { "properties": { - "image-vector": { + "vector": { "type": "dense_vector", - "dims": 3, - "index": true, - "similarity": "l2_norm" + "dims": 3 }, - "title-vector": { - "type": "dense_vector", - "dims": 5, - "index": true, - "similarity": "l2_norm" - }, - "title": { + "text": { "type": "text" - }, - "file-type": { - "type": "keyword" } } } }`; -const INGEST_SNIPPET = `POST /image-index/_bulk?refresh=true -{ "index": { "_id": "1" } } -{ "image-vector": [1, 5, -20], "title-vector": [12, 50, -10, 0, 1], "title": "moose family", "file-type": "jpg" } -{ "index": { "_id": "2" } } -{ "image-vector": [42, 8, -15], "title-vector": [25, 1, 4, -12, 2], "title": "alpine lake", "file-type": "png" } -{ "index": { "_id": "3" } } -{ "image-vector": [15, 11, 23], "title-vector": [1, 5, 25, 50, 20], "title": "full moon", "file-type": "jpg" }`; +const INGEST_SNIPPET = `POST /my-index/_doc +{ + "vector": [1, 5, -20], + "text": "hello world" +}`; -const QUERY_SNIPPET = `POST /image-index/_search +const QUERY_SNIPPET = `POST /my-index/_search { - "knn": { - "field": "image-vector", - "query_vector": [-5, 9, -12], - "k": 10, - "num_candidates": 100 - }, - "fields": [ "title", "file-type" ] + "size" : 3, + "query" : { + "knn": { + "field": "vector", + "query_vector": [1, 5, -20] + } + } }`; export const VectorSearchGuide: React.FC = () => { + const { http } = useValues(HttpLogic); const { application } = useValues(KibanaLogic); return ( @@ -120,6 +110,7 @@ export const VectorSearchGuide: React.FC = () => { /> +

{

@@ -165,7 +156,7 @@ export const VectorSearchGuide: React.FC = () => {

@@ -189,7 +180,7 @@ export const VectorSearchGuide: React.FC = () => {

@@ -197,7 +188,7 @@ export const VectorSearchGuide: React.FC = () => {

@@ -205,16 +196,18 @@ export const VectorSearchGuide: React.FC = () => { - application.navigateToApp(AI_SEARCH_PLUGIN.URL.replace(/^(?:\/app\/)?(.*)$/, '$1')) - } + onClick={() => { + application.navigateToUrl( + http.basePath.prepend(`${SEMANTIC_SEARCH_PLUGIN.URL}?model_example=elser`) + ); + }} layout="horizontal" titleSize="s" icon={} title={ } description={ @@ -224,22 +217,26 @@ export const VectorSearchGuide: React.FC = () => { /> } /> + { + application.navigateToUrl( + http.basePath.prepend(`${SEMANTIC_SEARCH_PLUGIN.URL}?model_example=e5`) + ); + }} layout="horizontal" titleSize="s" icon={} title={ } description={ } /> diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 552bb43fbd073..496ce1821c0d1 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -55,6 +55,7 @@ import { VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, INFERENCE_ENDPOINTS_PLUGIN, + SEMANTIC_SEARCH_PLUGIN, } from '../common/constants'; import { CreatIndexLocatorDefinition, @@ -383,6 +384,27 @@ export class EnterpriseSearchPlugin implements Plugin { title: VECTOR_SEARCH_PLUGIN.NAV_TITLE, }); + core.application.register({ + appRoute: SEMANTIC_SEARCH_PLUGIN.URL, + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, + euiIconType: SEMANTIC_SEARCH_PLUGIN.LOGO, + id: SEMANTIC_SEARCH_PLUGIN.ID, + mount: async (params: AppMountParameters) => { + const kibanaDeps = await this.getKibanaDeps(core, params, cloud); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(SEMANTIC_SEARCH_PLUGIN.NAME); + + this.getInitialData(http); + const pluginData = this.getPluginData(); + + const { renderApp } = await import('./applications'); + const { EnterpriseSearchSemanticSearch } = await import('./applications/semantic_search'); + + return renderApp(EnterpriseSearchSemanticSearch, kibanaDeps, pluginData); + }, + title: SEMANTIC_SEARCH_PLUGIN.NAV_TITLE, + }); + core.application.register({ appRoute: AI_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index d5c917443654f..a03429729bf2f 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -229,6 +229,7 @@ export class EnterpriseSearchPlugin implements Plugin { enterpriseSearchApplications: showEnterpriseSearch, enterpriseSearchAISearch: showEnterpriseSearch, enterpriseSearchVectorSearch: showEnterpriseSearch, + enterpriseSearchSemanticSearch: showEnterpriseSearch, enterpriseSearchElasticsearch: showEnterpriseSearch, appSearch: hasAppSearchAccess && config.canDeployEntSearch, workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch, @@ -241,6 +242,7 @@ export class EnterpriseSearchPlugin implements Plugin { enterpriseSearchApplications: showEnterpriseSearch, enterpriseSearchAISearch: showEnterpriseSearch, enterpriseSearchVectorSearch: showEnterpriseSearch, + enterpriseSearchSemanticSearch: showEnterpriseSearch, enterpriseSearchElasticsearch: showEnterpriseSearch, appSearch: hasAppSearchAccess && config.canDeployEntSearch, workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 43b0fdd793da6..4b263c9f5a080 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -14151,13 +14151,6 @@ "xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText": "Elastic Learned Sparse Encoder v2", "xpack.enterpriseSearch.aiSearch.elserAccordion.description": "Fonctionnalités de recherche sémantique instantanée", "xpack.enterpriseSearch.aiSearch.elserAccordion.title": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.aiSearch.elserPanel.step1.buttonLabel": "Créer un index", - "xpack.enterpriseSearch.aiSearch.elserPanel.step1.title": "Créez un index", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description": "Après avoir créé un index, sélectionnez-le et cliquez sur l'onglet intitulé {pipelinesName}.", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description.pipelinesName": "Pipelines", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.title": "Accédez à l’onglet Pipelines d’un index", - "xpack.enterpriseSearch.aiSearch.elserPanel.step3.description": "Localisez le panneau qui vous permet de déployer ELSER en un clic et créez un pipeline d’inférence à l’aide de ce modèle.", - "xpack.enterpriseSearch.aiSearch.elserPanel.step3.title": "Suivez les instructions à l’écran pour déployer ELSER", "xpack.enterpriseSearch.aiSearch.guide.description": "Élaborez une application de recherche propulsée par l'intelligence artificielle grâce à la plateforme Elastic, y compris notre modèle ML entraîné ELSER, notre recherche vectorielle et nos capacités d'intégration, ainsi que le classement RRF pour combiner la recherche vectorielle et la recherche textuelle.", "xpack.enterpriseSearch.aiSearch.guide.pageTitle": "Améliorez vos recherches avec l'intelligence artificielle", "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.description": "Résultats pondérés de plusieurs classements", @@ -14208,14 +14201,6 @@ "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.title": "Recherche vectorielle", "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description": "Utilisez des {vectorDbCapabilities} en ajoutant des incorporations de vos modèles ML. Déployez des modèles entraînés sur des nœuds de ML Elastic et configurez des pipelines d’inférence pour ajouter automatiquement des incorporations quand vous ingérez des documents, afin de pouvoir utiliser la méthode de recherche vectorielle kNN dans _search.", "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "Fonctionnalités de bases de données vectorielles d’Elasticsearch", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.buttonLabel": "Affichez les modèles entraînés", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "Guide sur les modèles entraînés", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.title": "Apprenez à charger des modèles de ML", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.buttonLabel": "Créez un index", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.title": "Créez un index", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description": "Accédez à l'onglet {pipelinesName} de votre index pour créer un pipeline d'inférence qui utilise votre modèle déployé.", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description.pipelinesName": "Pipelines", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.title": "Créez un pipeline d’inférence de ML", "xpack.enterpriseSearch.analytics..units.quickRange.last1Year": "Dernière année", "xpack.enterpriseSearch.analytics..units.quickRange.last2Weeks": "Deux dernières semaines", "xpack.enterpriseSearch.analytics..units.quickRange.last30Days": "30 derniers jours", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c3ae051fef551..2a44c2da7e209 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -14107,13 +14107,6 @@ "xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText": "Elastic Learned Sparse Encoder v2", "xpack.enterpriseSearch.aiSearch.elserAccordion.description": "即時セマンティック検索機能", "xpack.enterpriseSearch.aiSearch.elserAccordion.title": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.aiSearch.elserPanel.step1.buttonLabel": "インデックスを作成", - "xpack.enterpriseSearch.aiSearch.elserPanel.step1.title": "インデックスを作成", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description": "インデックスを作成した後は、インデックスを選択し、{pipelinesName}タブをクリックします。", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description.pipelinesName": "パイプライン", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.title": "インデックスのパイプラインタブに移動", - "xpack.enterpriseSearch.aiSearch.elserPanel.step3.description": "ELSERをワンクリックでデプロイし、そのモデルを使った推論パイプラインを作成できるパネルを探します。", - "xpack.enterpriseSearch.aiSearch.elserPanel.step3.title": "画面の指示に従い、ELSERをデプロイ", "xpack.enterpriseSearch.aiSearch.guide.description": "当社独自の学習済みMLモデルELSER、ベクトル検索と埋め込み機能、ベクトル検索とテキスト検索を組み合わせたRRFランキングなど、Elasticプラットフォームを使用して、AI検索を活用したアプリケーションを構築できます。", "xpack.enterpriseSearch.aiSearch.guide.pageTitle": "AIで検索を強化", "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.description": "複数のランキングから重み付けがされた結果", @@ -14164,14 +14157,6 @@ "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.title": "ベクトル検索", "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description": "MLモデルから埋め込みを追加して、{vectorDbCapabilities}を使用します。Elastic MLノードに学習済みモデルをデプロイし、推論パイプラインを設定して、ドキュメントをインジェストしたときに自動的に埋め込みが追加されるようにします。これにより、_searchでkNNベクトル検索方法を使用できます。", "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "ElasticsearchのベクトルDB機能", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.buttonLabel": "学習済みモデルを表示", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "学習済みモデルのガイド", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.title": "MLモデルのアップロード方法", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.buttonLabel": "インデックスを作成", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.title": "インデックスを作成", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description": "インデックスの{pipelinesName}タブに移動し、デプロイされたモデルで使用する推論パイプラインを作成します。", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description.pipelinesName": "パイプライン", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.title": "ML推論パイプラインを作成", "xpack.enterpriseSearch.analytics..units.quickRange.last1Year": "過去1年間", "xpack.enterpriseSearch.analytics..units.quickRange.last2Weeks": "過去 2 週間", "xpack.enterpriseSearch.analytics..units.quickRange.last30Days": "過去30日間", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f96a1de934d35..25a0b95a991fd 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -14171,13 +14171,6 @@ "xpack.enterpriseSearch.aiSearch.elser.description.elserLinkText": "Elastic Learned Sparse Encoder v2", "xpack.enterpriseSearch.aiSearch.elserAccordion.description": "即时语义搜索功能", "xpack.enterpriseSearch.aiSearch.elserAccordion.title": "Elastic Learned Sparse Encoder", - "xpack.enterpriseSearch.aiSearch.elserPanel.step1.buttonLabel": "创建索引", - "xpack.enterpriseSearch.aiSearch.elserPanel.step1.title": "创建索引", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description": "创建索引后,请选中该索引,然后单击 {pipelinesName} 选项卡。", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.description.pipelinesName": "管道", - "xpack.enterpriseSearch.aiSearch.elserPanel.step2.title": "导航到索引的“管道”选项卡", - "xpack.enterpriseSearch.aiSearch.elserPanel.step3.description": "查找允许您一键部署 ELSER 并使用该模型创建推理管道的面板。", - "xpack.enterpriseSearch.aiSearch.elserPanel.step3.title": "按照屏幕上显示的说明部署 ELSER", "xpack.enterpriseSearch.aiSearch.guide.description": "使用 Elastic 平台,包括我们专有的已训练 ML 模型 ELSER、矢量搜索和嵌入功能,以及用于组合矢量和文本搜索的 RRF 排名,构建 AI 搜索驱动式应用程序。", "xpack.enterpriseSearch.aiSearch.guide.pageTitle": "利用 AI 增强您的搜索功能", "xpack.enterpriseSearch.aiSearch.linearCombinationAccordion.description": "来自多个排名的加权结果", @@ -14228,14 +14221,6 @@ "xpack.enterpriseSearch.aiSearch.vectorSearchAccordion.title": "矢量搜索", "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description": "通过添加来自 ML 模型的嵌入来使用{vectorDbCapabilities}。在 Elastic ML 节点上部署已训练模型并设置推理管道,以在采集文档时自动添加嵌入,便于您在 _search 中使用 kNN 矢量搜索方法。", "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.description.vectorDbCapabilitiesLinkText": "Elasticsearch 的矢量 DB 功能", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.buttonLabel": "查看已训练模型", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.guideToTrainedModelsLinkText": "已训练模型指南", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step1.title": "了解如何上传 ML 模型", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.buttonLabel": "创建索引", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step2.title": "创建索引", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description": "导航到您索引的 {pipelinesName} 选项卡,以创建使用已部署模型的推理管道。", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.description.pipelinesName": "管道", - "xpack.enterpriseSearch.aiSearch.vectorSearchPanel.step3.title": "创建 ML 推理管道", "xpack.enterpriseSearch.analytics..units.quickRange.last1Year": "过去 1 年", "xpack.enterpriseSearch.analytics..units.quickRange.last2Weeks": "过去 2 周", "xpack.enterpriseSearch.analytics..units.quickRange.last30Days": "过去 30 天", diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts index b1ff2187bc574..9b34220ee75f3 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts @@ -69,6 +69,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'enterpriseSearchApplications', 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', + 'enterpriseSearchSemanticSearch', 'enterpriseSearchElasticsearch', 'appSearch', 'workplaceSearch', @@ -99,6 +100,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'enterpriseSearchApplications', 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', + 'enterpriseSearchSemanticSearch', 'enterpriseSearchElasticsearch', 'appSearch', 'observabilityAIAssistant', diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts index fb7b3a112f6de..b2955ade938b1 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts @@ -56,6 +56,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'enterpriseSearchApplications', 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', + 'enterpriseSearchSemanticSearch', 'enterpriseSearchElasticsearch', 'appSearch', 'workplaceSearch' @@ -76,6 +77,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'enterpriseSearchApplications', 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', + 'enterpriseSearchSemanticSearch', 'enterpriseSearchElasticsearch', 'observabilityAIAssistant', 'appSearch', diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts b/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts index a52cb46b77c9f..18693d4699f53 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts @@ -33,6 +33,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'enterpriseSearchApplications', 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', + 'enterpriseSearchSemanticSearch', 'enterpriseSearchElasticsearch', 'appSearch', 'workplaceSearch', diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts index 2778b8e54a13a..f5559940ff812 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/nav_links.ts @@ -25,6 +25,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { 'enterpriseSearchApplications', 'enterpriseSearchAISearch', 'enterpriseSearchVectorSearch', + 'enterpriseSearchSemanticSearch', 'enterpriseSearchElasticsearch', 'appSearch', 'workplaceSearch', From eb71438b9cae8031c78eb990817754d3bfffeb1c Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 22 Jul 2024 14:21:50 +0200 Subject: [PATCH 14/14] [Synthetics] Status overview embeddable (#188807) ## Summary Added status overview embeddable !! https://github.com/user-attachments/assets/27499ecf-549f-43a6-a16b-22a44db36814 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../synthetics/kibana.jsonc | 7 +- .../public/apps/embeddables/constants.ts | 8 + .../apps/embeddables/register_embeddables.ts | 48 ++++++ .../status_overview_component.tsx | 19 +++ .../status_overview_embeddable_factory.tsx | 99 +++++++++++++ .../synthetics_embeddable_context.tsx | 34 +++++ .../create_overview_panel_action.tsx | 54 +++++++ .../alerting_callout/alerting_callout.tsx | 7 +- .../components/embeddable_panel_wrapper.tsx | 139 ++++++++++++++++++ .../date_picker/synthetics_date_picker.tsx | 10 +- .../step_field_trend/step_field_trend.tsx | 4 +- .../getting_started_page.test.tsx | 2 +- .../getting_started/use_simple_monitor.ts | 4 +- .../hooks/use_monitor_save.tsx | 4 +- .../hooks/use_overview_status.ts | 3 +- .../monitor_list_table/delete_monitor.tsx | 6 +- .../overview/overview/overview_grid.tsx | 12 +- .../overview/overview/overview_status.tsx | 30 +++- .../settings/global_params/delete_param.tsx | 6 +- .../waterfall_marker_test_helper.tsx | 26 +++- .../browser_test_results.tsx | 4 +- .../simple_test_results.tsx | 4 +- .../public/apps/synthetics/contexts/index.ts | 2 - .../synthetics_embeddable_context.tsx | 27 ++++ .../contexts/synthetics_settings_context.tsx | 4 +- .../contexts/synthetics_shared_context.tsx | 63 ++++++++ .../synthetics_startup_plugins_context.tsx | 19 --- .../contexts/synthetics_theme_context.tsx | 97 ------------ .../hooks/use_edit_monitor_locator.ts | 5 +- .../hooks/use_monitor_detail_locator.ts | 5 +- .../lazy_wrapper/monitor_status.tsx | 8 +- .../alert_types/lazy_wrapper/tls_alert.tsx | 8 +- .../lib/alert_types/monitor_status.tsx | 2 +- .../apps/synthetics/lib/alert_types/tls.tsx | 2 +- .../public/apps/synthetics/render_app.tsx | 42 +++--- .../state/monitor_list/toast_title.tsx | 2 +- .../synthetics/state/overview_status/index.ts | 12 +- .../state/overview_status/selectors.ts | 4 +- .../apps/synthetics/state/settings/effects.ts | 4 +- .../synthetics/state/utils/fetch_effect.ts | 4 +- .../public/apps/synthetics/synthetics_app.tsx | 94 ++++-------- .../synthetics/utils/testing/rtl_helpers.tsx | 20 +-- .../synthetics/public/plugin.ts | 49 ++++-- .../utils/kibana_service/kibana_service.ts | 63 ++++---- .../synthetics/tsconfig.json | 11 +- 45 files changed, 739 insertions(+), 338 deletions(-) create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/constants.ts create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/register_embeddables.ts create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_component.tsx create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_embeddable_factory.tsx create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/synthetics_embeddable_context.tsx create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/embeddable_panel_wrapper.tsx create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_embeddable_context.tsx create mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx delete mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_startup_plugins_context.tsx delete mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx diff --git a/x-pack/plugins/observability_solution/synthetics/kibana.jsonc b/x-pack/plugins/observability_solution/synthetics/kibana.jsonc index 3e0715e48abb1..c527630b85d6c 100644 --- a/x-pack/plugins/observability_solution/synthetics/kibana.jsonc +++ b/x-pack/plugins/observability_solution/synthetics/kibana.jsonc @@ -17,6 +17,7 @@ "embeddable", "discover", "dataViews", + "dashboard", "encryptedSavedObjects", "exploratoryView", "features", @@ -31,7 +32,9 @@ "triggersActionsUi", "usageCollection", "bfetch", - "unifiedSearch" + "uiActions", + "unifiedSearch", + "presentationUtil" ], "optionalPlugins": [ "cloud", @@ -53,7 +56,7 @@ "observability", "spaces", "indexLifecycleManagement", - "unifiedDocViewer" + "unifiedDocViewer", ] } } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/constants.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/constants.ts new file mode 100644 index 0000000000000..b471d46ac3832 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SYNTHETICS_OVERVIEW_EMBEDDABLE = 'SYNTHETICS_OVERVIEW_EMBEDDABLE'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/register_embeddables.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/register_embeddables.ts new file mode 100644 index 0000000000000..fbc516a6f611b --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/register_embeddables.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreSetup } from '@kbn/core-lifecycle-browser'; + +import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-browser/src'; +import { createStatusOverviewPanelAction } from './ui_actions/create_overview_panel_action'; +import { ClientPluginsSetup, ClientPluginsStart } from '../../plugin'; +import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from './constants'; + +export const registerSyntheticsEmbeddables = ( + core: CoreSetup, + pluginsSetup: ClientPluginsSetup +) => { + pluginsSetup.embeddable.registerReactEmbeddableFactory( + SYNTHETICS_OVERVIEW_EMBEDDABLE, + async () => { + const { getStatusOverviewEmbeddableFactory } = await import( + './status_overview/status_overview_embeddable_factory' + ); + return getStatusOverviewEmbeddableFactory(core.getStartServices); + } + ); + + const { uiActions, cloud, serverless } = pluginsSetup; + + // Initialize actions + const addOverviewPanelAction = createStatusOverviewPanelAction(); + + core.getStartServices().then(([_, pluginsStart]) => { + pluginsStart.dashboard.registerDashboardPanelPlacementSetting( + SYNTHETICS_OVERVIEW_EMBEDDABLE, + () => { + return { width: 10, height: 8 }; + } + ); + }); + + // Assign triggers + // Only register these actions in stateful kibana, and the serverless observability project + if (Boolean((serverless && cloud?.serverless.projectType === 'observability') || !serverless)) { + uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addOverviewPanelAction); + } +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_component.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_component.tsx new file mode 100644 index 0000000000000..1034f9ea959ec --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_component.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Subject } from 'rxjs'; +import { OverviewStatus } from '../../synthetics/components/monitors_page/overview/overview/overview_status'; +import { SyntheticsEmbeddableContext } from '../synthetics_embeddable_context'; + +export const StatusOverviewComponent = ({ reload$ }: { reload$: Subject }) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_embeddable_factory.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_embeddable_factory.tsx new file mode 100644 index 0000000000000..3f7b3fcf13699 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/status_overview/status_overview_embeddable_factory.tsx @@ -0,0 +1,99 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import React, { useEffect } from 'react'; +import { DefaultEmbeddableApi, ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { + initializeTitles, + useBatchedPublishingSubjects, + fetch$, + PublishesWritablePanelTitle, + PublishesPanelTitle, + SerializedTitles, +} from '@kbn/presentation-publishing'; +import { BehaviorSubject, Subject } from 'rxjs'; +import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser'; +import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from '../constants'; +import { ClientPluginsStart } from '../../../plugin'; +import { StatusOverviewComponent } from './status_overview_component'; + +export const getOverviewPanelTitle = () => + i18n.translate('xpack.synthetics.statusOverview.displayName', { + defaultMessage: 'Synthetics Status Overview', + }); + +export type OverviewEmbeddableState = SerializedTitles; + +export type StatusOverviewApi = DefaultEmbeddableApi & + PublishesWritablePanelTitle & + PublishesPanelTitle; + +export const getStatusOverviewEmbeddableFactory = ( + getStartServices: StartServicesAccessor +) => { + const factory: ReactEmbeddableFactory< + OverviewEmbeddableState, + OverviewEmbeddableState, + StatusOverviewApi + > = { + type: SYNTHETICS_OVERVIEW_EMBEDDABLE, + deserializeState: (state) => { + return state.rawState as OverviewEmbeddableState; + }, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); + const defaultTitle$ = new BehaviorSubject(getOverviewPanelTitle()); + const reload$ = new Subject(); + + const api = buildApi( + { + ...titlesApi, + defaultPanelTitle: defaultTitle$, + serializeState: () => { + return { + rawState: { + ...serializeTitles(), + }, + }; + }, + }, + { + ...titleComparators, + } + ); + + const fetchSubscription = fetch$(api) + .pipe() + .subscribe((next) => { + reload$.next(next.isReload); + }); + + return { + api, + Component: () => { + const [] = useBatchedPublishingSubjects(); + + useEffect(() => { + return () => { + fetchSubscription.unsubscribe(); + }; + }, []); + return ( +
+ +
+ ); + }, + }; + }, + }; + return factory; +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/synthetics_embeddable_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/synthetics_embeddable_context.tsx new file mode 100644 index 0000000000000..0953fb79961b1 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/synthetics_embeddable_context.tsx @@ -0,0 +1,34 @@ +/* + * 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 { createBrowserHistory } from 'history'; +import { EuiPanel } from '@elastic/eui'; +import { Router } from '@kbn/shared-ux-router'; +import { SyntheticsSharedContext } from '../synthetics/contexts/synthetics_shared_context'; +import { SyntheticsEmbeddableStateContextProvider } from '../synthetics/contexts/synthetics_embeddable_context'; +import { getSyntheticsAppProps } from '../synthetics/render_app'; +import { SyntheticsSettingsContextProvider } from '../synthetics/contexts'; + +export const SyntheticsEmbeddableContext: React.FC<{ search?: string }> = ({ + search, + children, +}) => { + const props = getSyntheticsAppProps(); + + return ( + + + + + {children} + + + + + ); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx new file mode 100644 index 0000000000000..202a09e1f3576 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/embeddables/ui_actions/create_overview_panel_action.tsx @@ -0,0 +1,54 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { + IncompatibleActionError, + type UiActionsActionDefinition, +} from '@kbn/ui-actions-plugin/public'; +import { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from '../constants'; + +export const COMMON_SYNTHETICS_GROUPING = [ + { + id: 'synthetics', + getDisplayName: () => + i18n.translate('xpack.synthetics.common.constants.grouping.legacy', { + defaultMessage: 'Synthetics', + }), + getIconType: () => { + return 'online'; + }, + }, +]; +export const ADD_SYNTHETICS_OVERVIEW_ACTION_ID = 'CREATE_SYNTHETICS_OVERVIEW_EMBEDDABLE'; + +export function createStatusOverviewPanelAction(): UiActionsActionDefinition { + return { + id: ADD_SYNTHETICS_OVERVIEW_ACTION_ID, + grouping: COMMON_SYNTHETICS_GROUPING, + order: 30, + getIconType: () => 'online', + isCompatible: async ({ embeddable }) => { + return apiIsPresentationContainer(embeddable); + }, + execute: async ({ embeddable }) => { + if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError(); + try { + embeddable.addNewPanel({ + panelType: SYNTHETICS_OVERVIEW_EMBEDDABLE, + }); + } catch (e) { + return Promise.reject(); + } + }, + getDisplayName: () => + i18n.translate('xpack.synthetics.syntheticsEmbeddable.ariaLabel', { + defaultMessage: 'Synthetics Overview', + }), + }; +} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx index 07c594aa56156..4b508ab701231 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx @@ -14,6 +14,8 @@ import { useFetcher } from '@kbn/observability-shared-plugin/public'; import { useSessionStorage } from 'react-use'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ClientPluginsStart } from '../../../../../plugin'; import { selectDynamicSettings } from '../../../state/settings'; import { selectSyntheticsAlerts, @@ -21,7 +23,7 @@ import { } from '../../../state/alert_rules/selectors'; import { selectMonitorListState } from '../../../state'; import { getDynamicSettingsAction } from '../../../state/settings/actions'; -import { useSyntheticsSettingsContext, useSyntheticsStartPlugins } from '../../../contexts'; +import { useSyntheticsSettingsContext } from '../../../contexts'; import { ConfigKey } from '../../../../../../common/runtime_types'; export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boolean }) => { @@ -40,7 +42,8 @@ export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boo loaded: monitorsLoaded, } = useSelector(selectMonitorListState); - const syntheticsLocators = useSyntheticsStartPlugins()?.share?.url.locators; + const syntheticsLocators = useKibana().services.share?.url.locators; + const locator = syntheticsLocators?.get(syntheticsSettingsLocatorID); const { data: url } = useFetcher(() => { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/embeddable_panel_wrapper.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/embeddable_panel_wrapper.tsx new file mode 100644 index 0000000000000..cd73097c956a6 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/embeddable_panel_wrapper.tsx @@ -0,0 +1,139 @@ +/* + * 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, { FC, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiPopover, + EuiProgress, + EuiTitle, +} from '@elastic/eui'; +import { + LazySavedObjectSaveModalDashboard, + SaveModalDashboardProps, + withSuspense, +} from '@kbn/presentation-util-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from '../../../../embeddables/constants'; +import { ClientPluginsStart } from '../../../../../plugin'; + +const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); + +export const EmbeddablePanelWrapper: FC<{ + title: string; + loading?: boolean; +}> = ({ children, title, loading }) => { + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); + + const [isDashboardAttachmentReady, setDashboardAttachmentReady] = React.useState(false); + + const closePopover = () => { + setIsPopoverOpen(false); + }; + + const { embeddable } = useKibana().services; + + const isSyntheticsApp = window.location.pathname.includes('/app/synthetics'); + + const handleAttachToDashboardSave: SaveModalDashboardProps['onSave'] = useCallback( + ({ dashboardId, newTitle, newDescription }) => { + const stateTransfer = embeddable.getStateTransfer(); + const embeddableInput = {}; + + const state = { + input: embeddableInput, + type: SYNTHETICS_OVERVIEW_EMBEDDABLE, + }; + + const path = dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`; + + stateTransfer.navigateToWithEmbeddablePackage('dashboards', { + state, + path, + }); + }, + [embeddable] + ); + + return ( + <> + + {loading && } + + + +

{title}

+
+
+ {isSyntheticsApp && ( + + setIsPopoverOpen(!isPopoverOpen)} + /> + } + isOpen={isPopoverOpen} + closePopover={closePopover} + > + { + setDashboardAttachmentReady(true); + closePopover(); + }} + > + {i18n.translate( + 'xpack.synthetics.embeddablePanelWrapper.shareContextMenuItemLabel', + { defaultMessage: 'Add to dashboard' } + )} + , + ]} + /> + + + )} +
+ + {children} +
+ {isDashboardAttachmentReady ? ( + { + setDashboardAttachmentReady(false); + }} + onSave={handleAttachToDashboardSave} + /> + ) : null} + + ); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx index a5eaeacf6c7ed..1edaf76ea95a8 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx @@ -7,20 +7,18 @@ import React, { useContext, useEffect } from 'react'; import { EuiSuperDatePicker } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ClientPluginsStart } from '../../../../../plugin'; import { useUrlParams } from '../../../hooks'; import { CLIENT_DEFAULTS } from '../../../../../../common/constants'; -import { - SyntheticsSettingsContext, - SyntheticsStartupPluginsContext, - SyntheticsRefreshContext, -} from '../../../contexts'; +import { SyntheticsSettingsContext, SyntheticsRefreshContext } from '../../../contexts'; export const SyntheticsDatePicker = ({ fullWidth }: { fullWidth?: boolean }) => { const [getUrlParams, updateUrl] = useUrlParams(); const { commonlyUsedRanges } = useContext(SyntheticsSettingsContext); const { refreshApp } = useContext(SyntheticsRefreshContext); - const { data } = useContext(SyntheticsStartupPluginsContext); + const { data } = useKibana().services; // read time from state and update the url const sharedTimeState = data?.query.timefilter.timefilter.getTime(); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/step_field_trend/step_field_trend.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/step_field_trend/step_field_trend.tsx index 7b687929b78a5..47794a31b8e1c 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/step_field_trend/step_field_trend.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/step_field_trend/step_field_trend.tsx @@ -12,9 +12,9 @@ import moment from 'moment'; import { AllSeries, createExploratoryViewUrl } from '@kbn/exploratory-view-plugin/public'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ClientPluginsStart } from '../../../../../plugin'; import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; import { JourneyStep } from '../../../../../../common/runtime_types'; -import { useSyntheticsStartPlugins } from '../../../contexts'; export const getLast48Intervals = (activeStep: JourneyStep) => { const timestamp = activeStep['@timestamp']; @@ -36,7 +36,7 @@ export function StepFieldTrend({ field: string; step: JourneyStep; }) { - const { exploratoryView } = useSyntheticsStartPlugins(); + const exploratoryView = useKibana().services.exploratoryView; const EmbeddableExpView = exploratoryView!.ExploratoryViewEmbeddable; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx index 9c51fdf6b7b6e..2587fe21fba21 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.test.tsx @@ -179,7 +179,7 @@ describe('GettingStartedPage', () => { }); // page is loaded - expect(kibanaService.core.application.navigateToApp).toHaveBeenCalledWith('synthetics', { + expect(kibanaService.coreStart.application.navigateToApp).toHaveBeenCalledWith('synthetics', { path: '/monitors', }); }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts index e4015386ff333..250b6442ce1d1 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts @@ -56,14 +56,14 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData }, [monitorData]); useEffect(() => { - const { core, toasts } = kibanaService; + const { coreStart, toasts } = kibanaService; const newMonitor = data as UpsertMonitorResponse; const hasErrors = data && 'attributes' in data && data.attributes.errors?.length > 0; if (hasErrors && !loading) { showSyncErrors( (data as { attributes: { errors: ServiceLocationErrors } })?.attributes.errors ?? [], serviceLocations, - core + coreStart ); } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx index 1ba3cec885889..44def6edee978 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_save.tsx @@ -45,7 +45,7 @@ export const useMonitorSave = ({ monitorData }: { monitorData?: SyntheticsMonito }, [monitorData]); useEffect(() => { - const { core, toasts } = kibanaService; + const { coreStart, toasts } = kibanaService; if (status === FETCH_STATUS.FAILURE && error) { toasts.addError( @@ -64,7 +64,7 @@ export const useMonitorSave = ({ monitorData }: { monitorData?: SyntheticsMonito

{monitorId ? MONITOR_UPDATED_SUCCESS_LABEL_SUBTEXT : MONITOR_SUCCESS_LABEL_SUBTEXT}

, - core + coreStart ), toastLifeTimeMs: 3000, }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts index b06b66f8a5a73..562d651cf819b 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts @@ -18,7 +18,7 @@ import { export function useOverviewStatus({ scopeStatusByLocation }: { scopeStatusByLocation: boolean }) { const pageState = useSelector(selectOverviewPageState); - const { status, error, loaded } = useSelector(selectOverviewStatus); + const { status, error, loaded, loading } = useSelector(selectOverviewStatus); const { lastRefresh } = useSyntheticsRefreshContext(); @@ -37,5 +37,6 @@ export function useOverviewStatus({ scopeStatusByLocation }: { scopeStatusByLoca return { status, error, + loading, }; } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx index b16829fa5880a..dab31650aec4d 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx @@ -42,7 +42,7 @@ export const DeleteMonitor = ({ }, [configId, isDeleting]); useEffect(() => { - const { core, toasts } = kibanaService; + const { coreStart, toasts } = kibanaService; if (!isDeleting) { return; } @@ -53,7 +53,7 @@ export const DeleteMonitor = ({

{labels.MONITOR_DELETE_FAILURE_LABEL}

, - core + coreStart ), }, { toastLifeTimeMs: 3000 } @@ -72,7 +72,7 @@ export const DeleteMonitor = ({ } )}

, - core + coreStart ), }, { toastLifeTimeMs: 3000 } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx index 53f14deb0dab5..49f21acc20f10 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState, useRef, memo, useCallback } from 'react'; +import React, { useState, useRef, memo, useCallback, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { @@ -15,11 +15,12 @@ import { EuiButtonEmpty, EuiText, } from '@elastic/eui'; -import { selectOverviewStatus } from '../../../../state/overview_status'; +import { useOverviewStatus } from '../../hooks/use_overview_status'; import { useInfiniteScroll } from './use_infinite_scroll'; import { GridItemsByGroup } from './grid_by_group/grid_items_by_group'; import { GroupFields } from './grid_by_group/group_fields'; import { + fetchMonitorOverviewAction, quietFetchOverviewAction, selectOverviewState, setFlyoutConfig, @@ -33,7 +34,7 @@ import { NoMonitorsFound } from '../../common/no_monitors_found'; import { MonitorDetailFlyout } from './monitor_detail_flyout'; export const OverviewGrid = memo(() => { - const { status } = useSelector(selectOverviewStatus); + const { status } = useOverviewStatus({ scopeStatusByLocation: true }); const { data: { monitors }, @@ -49,6 +50,11 @@ export const OverviewGrid = memo(() => { const intersectionRef = useRef(null); const { monitorsSortedByStatus } = useMonitorsSortedByStatus(); + // fetch overview for all other page state changes + useEffect(() => { + dispatch(fetchMonitorOverviewAction.get(pageState)); + }, [dispatch, pageState]); + const setFlyoutConfigCallback = useCallback( (params: FlyoutParamProps) => dispatch(setFlyoutConfig(params)), [dispatch] diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx index fd3e19c7e96f0..28eaf97a37c5f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx @@ -5,10 +5,13 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiStat, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiStat } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { Subject } from 'rxjs'; +import { useSyntheticsRefreshContext } from '../../../../contexts'; +import { EmbeddablePanelWrapper } from '../../../common/components/embeddable_panel_wrapper'; import { clearOverviewStatusErrorAction } from '../../../../state/overview_status'; import { kibanaService } from '../../../../../../utils/kibana_service'; import { useGetUrlParams } from '../../../../hooks/use_url_params'; @@ -18,10 +21,16 @@ function title(t?: number) { return t ?? '-'; } -export function OverviewStatus() { +export function OverviewStatus({ reload$ }: { reload$?: Subject }) { const { statusFilter } = useGetUrlParams(); - const { status, error: statusError } = useOverviewStatus({ scopeStatusByLocation: true }); + const { refreshApp } = useSyntheticsRefreshContext(); + + const { + status, + error: statusError, + loading, + } = useOverviewStatus({ scopeStatusByLocation: true }); const dispatch = useDispatch(); const [statusConfig, setStatusConfig] = useState({ up: status?.up, @@ -30,6 +39,14 @@ export function OverviewStatus() { disabledCount: status?.disabledCount, }); + useEffect(() => { + const sub = reload$?.subscribe(() => { + refreshApp(); + }); + + return () => sub?.unsubscribe(); + }, [refreshApp, reload$]); + useEffect(() => { if (statusError) { dispatch(clearOverviewStatusErrorAction()); @@ -87,10 +104,7 @@ export function OverviewStatus() { }, [status, statusFilter]); return ( - - -

{headingText}

-
+ @@ -136,7 +150,7 @@ export function OverviewStatus() { )} -
+ ); } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx index f2e86f077123d..814fb13a99ba9 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx @@ -48,7 +48,7 @@ export const DeleteParam = ({ if (!isDeleting) { return; } - const { core, toasts } = kibanaService; + const { coreStart, toasts } = kibanaService; if (status === FETCH_STATUS.FAILURE) { toasts.addDanger( @@ -61,7 +61,7 @@ export const DeleteParam = ({ values: { name }, })}

, - core + coreStart ), }, { toastLifeTimeMs: 3000 } @@ -76,7 +76,7 @@ export const DeleteParam = ({ values: { name }, })}

, - core + coreStart ), }, { toastLifeTimeMs: 3000 } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_test_helper.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_test_helper.tsx index b30cd29a0f065..93afcb3c55bbb 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_test_helper.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_test_helper.tsx @@ -5,8 +5,9 @@ * 2.0. */ +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import React from 'react'; -import { SyntheticsStartupPluginsContext } from '../../../../../contexts'; +import { i18n } from '@kbn/i18n'; import { JourneyStep } from '../../../../../../../../common/runtime_types'; import { WaterfallContext } from '../context/waterfall_context'; @@ -25,9 +26,21 @@ const EmbeddableMock = ({ }) => (

{title}

-
{appendTitle}
+
+ {appendTitle} +
{reportType}
-
{JSON.stringify(attributes)}
+
+ {JSON.stringify(attributes)} +
); @@ -40,9 +53,8 @@ export const TestWrapper = ({ activeStep?: JourneyStep; children: JSX.Element; }) => ( - ), }, @@ -55,5 +67,5 @@ export const TestWrapper = ({ > {children} - + ); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx index 326e981d2c4e2..7aaeb39bf46e6 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/browser_test_results.tsx @@ -38,7 +38,7 @@ export const BrowserTestRunResult = ({ }); useEffect(() => { - const { core, toasts } = kibanaService; + const { coreStart, toasts } = kibanaService; if (retriesExceeded) { toasts.addDanger( { @@ -49,7 +49,7 @@ export const BrowserTestRunResult = ({ defaultMessage="Manual test run failed for {name}" values={{ name }} />, - core + coreStart ), }, { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/simple_test_results.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/simple_test_results.tsx index f88b6b3483a43..ba9d4ff87566c 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/simple_test_results.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/test_now_mode/manual_test_run_mode/simple_test_results.tsx @@ -25,7 +25,7 @@ export function SimpleTestResults({ name, testRunId, expectPings, onDone }: Prop useEffect(() => { if (retriesExceeded) { - const { core, toasts } = kibanaService; + const { coreStart, toasts } = kibanaService; toasts.addDanger( { @@ -36,7 +36,7 @@ export function SimpleTestResults({ name, testRunId, expectPings, onDone }: Prop defaultMessage="Manual test run failed for {name}" values={{ name }} />, - core + coreStart ), }, { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/index.ts index e3171165a8639..662d6337d5a0e 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/index.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/index.ts @@ -7,5 +7,3 @@ export * from './synthetics_refresh_context'; export * from './synthetics_settings_context'; -export * from './synthetics_theme_context'; -export * from './synthetics_startup_plugins_context'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_embeddable_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_embeddable_context.tsx new file mode 100644 index 0000000000000..0ae588714bf97 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_embeddable_context.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext, useMemo } from 'react'; +import { History, createMemoryHistory } from 'history'; + +interface SyntheticsEmbeddableContext { + history: History; +} + +const defaultContext: SyntheticsEmbeddableContext = {} as SyntheticsEmbeddableContext; + +export const SyntheticsEmbeddableContext = createContext(defaultContext); + +export const SyntheticsEmbeddableStateContextProvider: React.FC = ({ children }) => { + const value = useMemo(() => { + return { history: createMemoryHistory() }; + }, []); + + return ; +}; + +export const useSyntheticsEmbeddableContext = () => useContext(SyntheticsEmbeddableContext); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx index b221997efe9c3..14799683d96d5 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx @@ -27,13 +27,13 @@ export interface CommonlyUsedDateRange { export interface SyntheticsAppProps { basePath: string; canSave: boolean; - core: CoreStart; + coreStart: CoreStart; darkMode: boolean; i18n: I18nStart; isApmAvailable: boolean; isInfraAvailable: boolean; isLogsAvailable: boolean; - plugins: ClientPluginsSetup; + setupPlugins: ClientPluginsSetup; startPlugins: ClientPluginsStart; setBadge: (badge?: ChromeBadge) => void; renderGlobalHelpControls(): void; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx new file mode 100644 index 0000000000000..948bf538c2faf --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx @@ -0,0 +1,63 @@ +/* + * 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 { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { Provider as ReduxProvider } from 'react-redux'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { SyntheticsRefreshContextProvider } from './synthetics_refresh_context'; +import { SyntheticsDataViewContextProvider } from './synthetics_data_view_context'; +import { SyntheticsAppProps } from './synthetics_settings_context'; +import { storage, store } from '../state'; + +export const SyntheticsSharedContext: React.FC = ({ + coreStart, + setupPlugins, + startPlugins, + children, + darkMode, +}) => { + return ( + + + + + + + {children} + + + + + + + ); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_startup_plugins_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_startup_plugins_context.tsx deleted file mode 100644 index cfb28cbbff553..0000000000000 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_startup_plugins_context.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { createContext, useContext, PropsWithChildren } from 'react'; -import { ClientPluginsStart } from '../../../plugin'; - -export const SyntheticsStartupPluginsContext = createContext>({}); - -export const SyntheticsStartupPluginsContextProvider: React.FC< - PropsWithChildren> -> = ({ children, ...props }) => ( - -); - -export const useSyntheticsStartPlugins = () => useContext(SyntheticsStartupPluginsContext); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx deleted file mode 100644 index b57bdaa0cd365..0000000000000 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_theme_context.tsx +++ /dev/null @@ -1,97 +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 { euiLightVars, euiDarkVars } from '@kbn/ui-theme'; -import React, { createContext, useContext, useMemo, FC, PropsWithChildren } from 'react'; -import { DARK_THEME, LIGHT_THEME, PartialTheme, Theme } from '@elastic/charts'; - -export interface SyntheticsAppColors { - danger: string; - dangerBehindText: string; - success: string; - gray: string; - range: string; - mean: string; - warning: string; - lightestShade: string; -} - -export interface SyntheticsThemeContextValues { - colors: SyntheticsAppColors; - chartTheme: { - baseTheme?: Theme; - theme?: PartialTheme; - }; -} - -/** - * These are default values for the context. These defaults are typically - * overwritten by the Synthetics App upon its invocation. - */ -const defaultContext: SyntheticsThemeContextValues = { - colors: { - danger: euiLightVars.euiColorDanger, - dangerBehindText: euiDarkVars.euiColorVis9_behindText, - mean: euiLightVars.euiColorPrimary, - range: euiLightVars.euiFocusBackgroundColor, - success: euiLightVars.euiColorSuccess, - warning: euiLightVars.euiColorWarning, - gray: euiLightVars.euiColorLightShade, - lightestShade: euiLightVars.euiColorLightestShade, - }, - chartTheme: { - baseTheme: LIGHT_THEME, - }, -}; - -export const SyntheticsThemeContext = createContext(defaultContext); - -interface ThemeContextProps { - darkMode: boolean; -} - -export const SyntheticsThemeContextProvider: FC> = ({ - darkMode, - children, -}) => { - let colors: SyntheticsAppColors; - if (darkMode) { - colors = { - danger: euiDarkVars.euiColorVis9, - dangerBehindText: euiDarkVars.euiColorVis9_behindText, - mean: euiDarkVars.euiColorPrimary, - gray: euiDarkVars.euiColorLightShade, - range: euiDarkVars.euiFocusBackgroundColor, - success: euiDarkVars.euiColorSuccess, - warning: euiDarkVars.euiColorWarning, - lightestShade: euiDarkVars.euiColorLightestShade, - }; - } else { - colors = { - danger: euiLightVars.euiColorVis9, - dangerBehindText: euiLightVars.euiColorVis9_behindText, - mean: euiLightVars.euiColorPrimary, - gray: euiLightVars.euiColorLightShade, - range: euiLightVars.euiFocusBackgroundColor, - success: euiLightVars.euiColorSuccess, - warning: euiLightVars.euiColorWarning, - lightestShade: euiLightVars.euiColorLightestShade, - }; - } - const value = useMemo(() => { - return { - colors, - chartTheme: { - baseTheme: darkMode ? DARK_THEME : LIGHT_THEME, - }, - }; - }, [colors, darkMode]); - - return ; -}; - -export const useSyntheticsThemeContext = () => useContext(SyntheticsThemeContext); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts index 034ca4ec6807a..a0ecb681e38c2 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_edit_monitor_locator.ts @@ -8,7 +8,8 @@ import { useEffect, useState } from 'react'; import { LocatorClient } from '@kbn/share-plugin/common/url_service/locators'; import { syntheticsEditMonitorLocatorID } from '@kbn/observability-plugin/common'; -import { useSyntheticsStartPlugins } from '../contexts'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ClientPluginsStart } from '../../../plugin'; export function useEditMonitorLocator({ configId, @@ -18,7 +19,7 @@ export function useEditMonitorLocator({ locators?: LocatorClient; }) { const [editUrl, setEditUrl] = useState(undefined); - const syntheticsLocators = useSyntheticsStartPlugins()?.share?.url.locators; + const syntheticsLocators = useKibana().services.share?.url.locators; const locator = (locators || syntheticsLocators)?.get(syntheticsEditMonitorLocatorID); useEffect(() => { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts index d1c181da28f17..fc346bccfa6c6 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/hooks/use_monitor_detail_locator.ts @@ -7,7 +7,8 @@ import { useEffect, useState } from 'react'; import { syntheticsMonitorDetailLocatorID } from '@kbn/observability-plugin/common'; -import { useSyntheticsStartPlugins } from '../contexts'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ClientPluginsStart } from '../../../plugin'; export function useMonitorDetailLocator({ configId, @@ -17,7 +18,7 @@ export function useMonitorDetailLocator({ locationId?: string; }) { const [monitorUrl, setMonitorUrl] = useState(undefined); - const locator = useSyntheticsStartPlugins()?.share?.url.locators.get( + const locator = useKibana().services?.share?.url.locators.get( syntheticsMonitorDetailLocatorID ); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx index 7c33dc7aba96e..e0c843eca0bd4 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx @@ -19,19 +19,19 @@ import { store } from '../../../state'; import type { StatusRuleParams } from '../../../../../../common/rules/status_rule'; interface Props { - core: CoreStart; + coreStart: CoreStart; plugins: ClientPluginsStart; params: RuleTypeParamsExpressionProps; } // eslint-disable-next-line import/no-default-export -export default function MonitorStatusAlert({ core, plugins, params }: Props) { - kibanaService.core = core; +export default function MonitorStatusAlert({ coreStart, plugins, params }: Props) { + kibanaService.coreStart = coreStart; const queryClient = new QueryClient(); return ( - + ['ruleParams']; setRuleParams: RuleTypeParamsExpressionProps['setRuleParams']; } // eslint-disable-next-line import/no-default-export -export default function TLSAlert({ core, plugins, ruleParams, setRuleParams }: Props) { - kibanaService.core = core; +export default function TLSAlert({ coreStart, plugins, ruleParams, setRuleParams }: Props) { + kibanaService.coreStart = coreStart; return ( - + diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx index 8ee01e185e8c1..ba86407859408 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx @@ -33,7 +33,7 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({ return `${docLinks.links.observability.syntheticsAlerting}`; }, ruleParamsExpression: (paramProps: RuleTypeParamsExpressionProps) => ( - + ), validate: (_ruleParams: StatusRuleParams) => { return { errors: {} }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/tls.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/tls.tsx index 2479d1f466f3e..15c0fa90ec605 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/tls.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/tls.tsx @@ -31,7 +31,7 @@ export const initTlsAlertType: AlertTypeInitializer = ({ }, ruleParamsExpression: (params: RuleTypeParamsExpressionProps) => ( { + const { isDev, isServerless, coreStart, startPlugins, setupPlugins, appMountParameters } = + kibanaService; + const { application: { capabilities }, chrome: { setBadge, setHelpExtension }, @@ -30,7 +26,7 @@ export function renderApp( http: { basePath }, i18n, theme, - } = core; + } = kibanaService.coreStart; const { apm, infrastructure, logs } = getIntegratedAppAvailability( capabilities, @@ -40,24 +36,22 @@ export function renderApp( const canSave = (capabilities.uptime.save ?? false) as boolean; // TODO: Determine for synthetics const darkMode = theme.getTheme().darkMode; - const props: SyntheticsAppProps = { + return { isDev, - plugins, + setupPlugins, canSave, - core, + coreStart, i18n, startPlugins, basePath: basePath.get(), darkMode, - commonlyUsedRanges: core.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES), + commonlyUsedRanges: coreStart.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES), isApmAvailable: apm, isInfraAvailable: infrastructure, isLogsAvailable: logs, renderGlobalHelpControls: () => setHelpExtension({ - appName: i18nFormatter.translate('xpack.synthetics.header.appName', { - defaultMessage: 'Synthetics', - }), + appName: SYNTHETICS_APP_NAME, links: [ { linkType: 'documentation', @@ -72,13 +66,21 @@ export function renderApp( setBadge, appMountParameters, isServerless, - setBreadcrumbs: startPlugins.serverless?.setBreadcrumbs ?? core.chrome.setBreadcrumbs, + setBreadcrumbs: startPlugins.serverless?.setBreadcrumbs ?? coreStart.chrome.setBreadcrumbs, }; +}; + +export function renderApp(appMountParameters: AppMountParameters) { + const props: SyntheticsAppProps = getSyntheticsAppProps(); ReactDOM.render(, appMountParameters.element); return () => { - startPlugins.data.search.session.clear(); + props.startPlugins.data.search.session.clear(); ReactDOM.unmountComponentAtNode(appMountParameters.element); }; } + +const SYNTHETICS_APP_NAME = i18nFormatter.translate('xpack.synthetics.header.appName', { + defaultMessage: 'Synthetics', +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx index 0083a5a75339f..04c5065014289 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/toast_title.tsx @@ -11,5 +11,5 @@ import { toMountPoint } from '@kbn/react-kibana-mount'; import { kibanaService } from '../../../../utils/kibana_service'; export function toastTitle({ title, testAttribute }: { title: string; testAttribute?: string }) { - return toMountPoint(

{title}

, kibanaService.core); + return toMountPoint(

{title}

, kibanaService.coreStart); } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts index 5125e723f13db..69b735302ee75 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/index.ts @@ -9,7 +9,11 @@ import { createReducer } from '@reduxjs/toolkit'; import { OverviewStatusState } from '../../../../../common/runtime_types'; import { IHttpSerializedFetchError } from '..'; -import { clearOverviewStatusErrorAction, fetchOverviewStatusAction } from './actions'; +import { + clearOverviewStatusErrorAction, + fetchOverviewStatusAction, + quietFetchOverviewStatusAction, +} from './actions'; export interface OverviewStatusStateReducer { loading: boolean; @@ -29,6 +33,10 @@ export const overviewStatusReducer = createReducer(initialState, (builder) => { builder .addCase(fetchOverviewStatusAction.get, (state) => { state.status = null; + state.loading = true; + }) + .addCase(quietFetchOverviewStatusAction.get, (state) => { + state.loading = true; }) .addCase(fetchOverviewStatusAction.success, (state, action) => { state.status = { @@ -36,9 +44,11 @@ export const overviewStatusReducer = createReducer(initialState, (builder) => { allConfigs: { ...action.payload.upConfigs, ...action.payload.downConfigs }, }; state.loaded = true; + state.loading = false; }) .addCase(fetchOverviewStatusAction.fail, (state, action) => { state.error = action.payload; + state.loading = false; }) .addCase(clearOverviewStatusErrorAction, (state) => { state.error = null; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/selectors.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/selectors.ts index 07745420b86c8..c23eed413d107 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/selectors.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview_status/selectors.ts @@ -8,5 +8,5 @@ import { SyntheticsAppState } from '../root_reducer'; export const selectOverviewStatus = ({ - overviewStatus: { status, error, loaded }, -}: SyntheticsAppState) => ({ status, error, loaded }); + overviewStatus: { status, error, loaded, loading }, +}: SyntheticsAppState) => ({ status, error, loaded, loading }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/settings/effects.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/settings/effects.ts index 551838a6f6ec4..fdc7c55a7a053 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/settings/effects.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/settings/effects.ts @@ -81,13 +81,13 @@ export function* setDynamicSettingsEffect() { yield call(setDynamicSettings, { settings: action.payload }); yield put(updateDefaultAlertingAction.get()); yield put(setDynamicSettingsAction.success(action.payload)); - kibanaService.core.notifications.toasts.addSuccess( + kibanaService.coreSetup.notifications.toasts.addSuccess( i18n.translate('xpack.synthetics.settings.saveSuccess', { defaultMessage: 'Settings saved!', }) ); } catch (err) { - kibanaService.core.notifications.toasts.addError(err, { + kibanaService.coreSetup.notifications.toasts.addError(err, { title: couldNotSaveSettingsText, }); yield put(setDynamicSettingsAction.fail(err)); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts index d9412849114ab..5883c55196ff7 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts @@ -67,7 +67,7 @@ export function fetchEffectFactory( if (typeof onFailure === 'function') { onFailure?.(error); } else if (typeof onFailure === 'string') { - kibanaService.core.notifications.toasts.addError( + kibanaService.coreSetup.notifications.toasts.addError( { ...error, message: serializedError.body?.message ?? error.message }, { title: onFailure, @@ -104,7 +104,7 @@ export function fetchEffectFactory( if (typeof onSuccess === 'function') { onSuccess(response as R); } else if (onSuccess && typeof onSuccess === 'string') { - kibanaService.core.notifications.toasts.addSuccess(onSuccess); + kibanaService.coreSetup.notifications.toasts.addSuccess(onSuccess); } } } catch (error) { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/synthetics_app.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/synthetics_app.tsx index 93c45442695ad..61cf6b69763da 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/synthetics_app.tsx @@ -6,42 +6,30 @@ */ import React, { useEffect } from 'react'; -import { Provider as ReduxProvider } from 'react-redux'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { InspectorContextProvider } from '@kbn/observability-shared-plugin/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; -import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { Router } from '@kbn/shared-ux-router'; +import { SyntheticsSharedContext } from './contexts/synthetics_shared_context'; import { kibanaService } from '../../utils/kibana_service'; import { ActionMenu } from './components/common/header/action_menu'; import { TestNowModeFlyoutContainer } from './components/test_now_mode/test_now_mode_flyout_container'; -import { - SyntheticsAppProps, - SyntheticsRefreshContextProvider, - SyntheticsSettingsContextProvider, - SyntheticsStartupPluginsContextProvider, - SyntheticsThemeContextProvider, -} from './contexts'; -import { SyntheticsDataViewContextProvider } from './contexts/synthetics_data_view_context'; +import { SyntheticsAppProps, SyntheticsSettingsContextProvider } from './contexts'; import { PageRouter } from './routes'; -import { setBasePath, storage, store } from './state'; +import { setBasePath, store } from './state'; const Application = (props: SyntheticsAppProps) => { const { basePath, canSave, - core, - darkMode, - plugins, + coreStart, + startPlugins, renderGlobalHelpControls, setBadge, - startPlugins, appMountParameters, } = props; @@ -62,16 +50,17 @@ const Application = (props: SyntheticsAppProps) => { ); }, [canSave, renderGlobalHelpControls, setBadge]); - kibanaService.core = core; - kibanaService.startPlugins = startPlugins; kibanaService.theme = props.appMountParameters.theme$; store.dispatch(setBasePath(basePath)); + const PresentationContextProvider = + startPlugins.presentationUtil?.ContextProvider ?? React.Fragment; + return ( - + { }, }} > - - - - - - - - - -
- - - - - - - -
-
-
-
-
-
-
-
-
-
+ + + + +
+ + + + + +
+
+
+
+
); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx index 552229fe47346..9d4870b8c9154 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx @@ -34,10 +34,7 @@ import { MountWithReduxProvider } from './helper_with_redux'; import { AppState } from '../../state'; import { stringifyUrlParams } from '../url_params'; import { ClientPluginsStart } from '../../../../plugin'; -import { - SyntheticsRefreshContextProvider, - SyntheticsStartupPluginsContextProvider, -} from '../../contexts'; +import { SyntheticsRefreshContextProvider } from '../../contexts'; import { kibanaService } from '../../../../utils/kibana_service'; type DeepPartial = { @@ -182,21 +179,14 @@ export function MockKibanaProvider({ }: MockKibanaProviderProps) { const coreOptions = merge({}, mockCore(), core); - kibanaService.core = coreOptions as any; + kibanaService.coreStart = coreOptions as any; return ( - - - {children} - - + + {children} + ); diff --git a/x-pack/plugins/observability_solution/synthetics/public/plugin.ts b/x-pack/plugins/observability_solution/synthetics/public/plugin.ts index 50c79681a4ebc..1192b4d991f55 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/plugin.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/plugin.ts @@ -25,7 +25,7 @@ import type { ExploratoryViewPublicSetup, ExploratoryViewPublicStart, } from '@kbn/exploratory-view-plugin/public'; -import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { EmbeddableStart, EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, @@ -50,12 +50,18 @@ import type { ObservabilitySharedPluginSetup, ObservabilitySharedPluginStart, } from '@kbn/observability-shared-plugin/public'; + import { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/public/plugin'; import { ObservabilityAIAssistantPublicSetup, ObservabilityAIAssistantPublicStart, } from '@kbn/observability-ai-assistant-plugin/public'; import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import type { UiActionsSetup } from '@kbn/ui-actions-plugin/public'; +import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; +import { DashboardStart, DashboardSetup } from '@kbn/dashboard-plugin/public'; +import { registerSyntheticsEmbeddables } from './apps/embeddables/register_embeddables'; +import { kibanaService } from './utils/kibana_service'; import { PLUGIN } from '../common/constants/plugin'; import { OVERVIEW_ROUTE } from '../common/constants/ui'; import { locators } from './apps/locators'; @@ -72,7 +78,10 @@ export interface ClientPluginsSetup { share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; cloud?: CloudSetup; + embeddable: EmbeddableSetup; serverless?: ServerlessPluginSetup; + uiActions: UiActionsSetup; + dashboard: DashboardSetup; } export interface ClientPluginsStart { @@ -102,6 +111,8 @@ export interface ClientPluginsStart { usageCollection: UsageCollectionStart; serverless: ServerlessPluginStart; licenseManagement?: LicenseManagementUIPluginSetup; + presentationUtil: PresentationUtilPluginStart; + dashboard: DashboardStart; } export interface SyntheticsPluginServices extends Partial { @@ -123,12 +134,25 @@ export class SyntheticsPlugin this._packageInfo = initContext.env.packageInfo; } - public setup(core: CoreSetup, plugins: ClientPluginsSetup): void { + public setup( + coreSetup: CoreSetup, + plugins: ClientPluginsSetup + ): void { locators.forEach((locator) => { plugins.share.url.locators.create(locator); }); - registerSyntheticsRoutesWithNavigation(core, plugins); + registerSyntheticsRoutesWithNavigation(coreSetup, plugins); + + coreSetup.getStartServices().then(([coreStart, clientPluginsStart]) => { + kibanaService.init({ + coreSetup, + coreStart, + startPlugins: clientPluginsStart, + isDev: this.initContext.env.mode.dev, + isServerless: this._isServerless, + }); + }); const appKeywords = [ 'Synthetics', @@ -149,7 +173,7 @@ export class SyntheticsPlugin ]; // Register the Synthetics UI plugin - core.application.register({ + coreSetup.application.register({ id: 'synthetics', euiIconType: 'logoObservability', order: 8400, @@ -177,23 +201,20 @@ export class SyntheticsPlugin }, ], mount: async (params: AppMountParameters) => { - const [coreStart, corePlugins] = await core.getStartServices(); - + kibanaService.appMountParameters = params; const { renderApp } = await import('./apps/synthetics/render_app'); - return renderApp( - coreStart, - plugins, - corePlugins, - params, - this.initContext.env.mode.dev, - this._isServerless - ); + await coreSetup.getStartServices(); + + return renderApp(params); }, }); + + registerSyntheticsEmbeddables(coreSetup, plugins); } public start(coreStart: CoreStart, pluginsStart: ClientPluginsStart): void { const { triggersActionsUi } = pluginsStart; + setStartServices(coreStart); setStartServices(coreStart); diff --git a/x-pack/plugins/observability_solution/synthetics/public/utils/kibana_service/kibana_service.ts b/x-pack/plugins/observability_solution/synthetics/public/utils/kibana_service/kibana_service.ts index 021d8c7ec3d7d..292a0e058737b 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/utils/kibana_service/kibana_service.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/utils/kibana_service/kibana_service.ts @@ -6,43 +6,46 @@ */ import type { Observable } from 'rxjs'; -import type { CoreStart, CoreTheme } from '@kbn/core/public'; -import { ClientPluginsStart } from '../../plugin'; +import type { CoreStart, CoreTheme, CoreSetup } from '@kbn/core/public'; +import { AppMountParameters } from '@kbn/core/public'; +import { ClientPluginsSetup, ClientPluginsStart } from '../../plugin'; import { apiService } from '../api_service/api_service'; class KibanaService { private static instance: KibanaService; - private _core!: CoreStart; - private _startPlugins!: ClientPluginsStart; - private _theme!: Observable; - - public get core() { - return this._core; - } - - public set core(coreStart: CoreStart) { - this._core = coreStart; - apiService.http = this._core.http; - } - - public get startPlugins() { - return this._startPlugins; - } - - public set startPlugins(startPlugins: ClientPluginsStart) { - this._startPlugins = startPlugins; - } - - public get theme() { - return this._theme; - } - - public set theme(coreTheme: Observable) { - this._theme = coreTheme; + public coreStart!: CoreStart; + public coreSetup!: CoreSetup; + public theme!: Observable; + public setupPlugins!: ClientPluginsSetup; + public isDev!: boolean; + public isServerless!: boolean; + public appMountParameters!: AppMountParameters; + public startPlugins!: ClientPluginsStart; + + public init({ + coreSetup, + coreStart, + startPlugins, + isDev, + isServerless, + }: { + coreSetup: CoreSetup; + coreStart: CoreStart; + startPlugins: ClientPluginsStart; + isDev: boolean; + isServerless: boolean; + }) { + this.coreSetup = coreSetup; + this.coreStart = coreStart; + this.startPlugins = startPlugins; + this.theme = coreStart.uiSettings.get$('theme:darkMode'); + apiService.http = coreStart.http; + this.isDev = isDev; + this.isServerless = isServerless; } public get toasts() { - return this._core.notifications.toasts; + return this.coreStart.notifications.toasts; } private constructor() {} diff --git a/x-pack/plugins/observability_solution/synthetics/tsconfig.json b/x-pack/plugins/observability_solution/synthetics/tsconfig.json index 95ed03ba36a11..f5df3a24c8ea1 100644 --- a/x-pack/plugins/observability_solution/synthetics/tsconfig.json +++ b/x-pack/plugins/observability_solution/synthetics/tsconfig.json @@ -40,7 +40,6 @@ "@kbn/kibana-react-plugin", "@kbn/i18n-react", "@kbn/securitysolution-io-ts-utils", - "@kbn/ui-theme", "@kbn/es-query", "@kbn/stack-connectors-plugin", "@kbn/rule-data-utils", @@ -90,7 +89,15 @@ "@kbn/react-kibana-mount", "@kbn/react-kibana-context-render", "@kbn/react-kibana-context-theme", - "@kbn/search-types" + "@kbn/search-types", + "@kbn/core-lifecycle-browser", + "@kbn/ui-actions-browser", + "@kbn/presentation-publishing", + "@kbn/presentation-containers", + "@kbn/ui-actions-plugin", + "@kbn/presentation-util-plugin", + "@kbn/core-application-browser", + "@kbn/dashboard-plugin" ], "exclude": ["target/**/*"] }