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`
### 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
`
---
.../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 '#
-
+
-
+
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/**/*"]
}