diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.executioncontext.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.executioncontext.md
new file mode 100644
index 0000000000000..be5689ad7b080
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.executioncontext.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreSetup](./kibana-plugin-core-public.coresetup.md) > [executionContext](./kibana-plugin-core-public.coresetup.executioncontext.md)
+
+## CoreSetup.executionContext property
+
+[ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md)
+
+Signature:
+
+```typescript
+executionContext: ExecutionContextSetup;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.md
index 9488b8a26b867..31793ec6f7a58 100644
--- a/docs/development/core/public/kibana-plugin-core-public.coresetup.md
+++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.md
@@ -17,6 +17,7 @@ export interface CoreSetup
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreStart](./kibana-plugin-core-public.corestart.md) > [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md)
+
+## CoreStart.executionContext property
+
+[ExecutionContextStart](./kibana-plugin-core-public.executioncontextstart.md)
+
+Signature:
+
+```typescript
+executionContext: ExecutionContextStart;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.md b/docs/development/core/public/kibana-plugin-core-public.corestart.md
index ae67696e12501..edd80e1adb9f9 100644
--- a/docs/development/core/public/kibana-plugin-core-public.corestart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.corestart.md
@@ -20,6 +20,7 @@ export interface CoreStart
| [chrome](./kibana-plugin-core-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-core-public.chromestart.md) |
| [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) | DeprecationsServiceStart | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) |
| [docLinks](./kibana-plugin-core-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) |
+| [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md) | ExecutionContextStart | [ExecutionContextStart](./kibana-plugin-core-public.executioncontextstart.md) |
| [fatalErrors](./kibana-plugin-core-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) |
| [http](./kibana-plugin-core-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-core-public.httpstart.md) |
| [i18n](./kibana-plugin-core-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-core-public.i18nstart.md) |
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.clear.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.clear.md
new file mode 100644
index 0000000000000..94936b94d0710
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.clear.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md) > [clear](./kibana-plugin-core-public.executioncontextsetup.clear.md)
+
+## ExecutionContextSetup.clear() method
+
+clears the context
+
+Signature:
+
+```typescript
+clear(): void;
+```
+Returns:
+
+void
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.context_.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.context_.md
new file mode 100644
index 0000000000000..d6c74db6d603e
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.context_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md) > [context$](./kibana-plugin-core-public.executioncontextsetup.context_.md)
+
+## ExecutionContextSetup.context$ property
+
+The current context observable
+
+Signature:
+
+```typescript
+context$: Observable;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.get.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.get.md
new file mode 100644
index 0000000000000..65e9b1218649d
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.get.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md) > [get](./kibana-plugin-core-public.executioncontextsetup.get.md)
+
+## ExecutionContextSetup.get() method
+
+Get the current top level context
+
+Signature:
+
+```typescript
+get(): KibanaExecutionContext;
+```
+Returns:
+
+KibanaExecutionContext
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.getaslabels.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.getaslabels.md
new file mode 100644
index 0000000000000..0f0bda4e2913e
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.getaslabels.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md) > [getAsLabels](./kibana-plugin-core-public.executioncontextsetup.getaslabels.md)
+
+## ExecutionContextSetup.getAsLabels() method
+
+returns apm labels
+
+Signature:
+
+```typescript
+getAsLabels(): Labels;
+```
+Returns:
+
+Labels
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.md
new file mode 100644
index 0000000000000..01581d2e80a5c
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md)
+
+## ExecutionContextSetup interface
+
+Kibana execution context. Used to provide execution context to Elasticsearch, reporting, performance monitoring, etc.
+
+Signature:
+
+```typescript
+export interface ExecutionContextSetup
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [context$](./kibana-plugin-core-public.executioncontextsetup.context_.md) | Observable<KibanaExecutionContext> | The current context observable |
+
+## Methods
+
+| Method | Description |
+| --- | --- |
+| [clear()](./kibana-plugin-core-public.executioncontextsetup.clear.md) | clears the context |
+| [get()](./kibana-plugin-core-public.executioncontextsetup.get.md) | Get the current top level context |
+| [getAsLabels()](./kibana-plugin-core-public.executioncontextsetup.getaslabels.md) | returns apm labels |
+| [set(c$)](./kibana-plugin-core-public.executioncontextsetup.set.md) | Set the current top level context |
+| [withGlobalContext(context)](./kibana-plugin-core-public.executioncontextsetup.withglobalcontext.md) | merges the current top level context with the specific event context |
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.set.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.set.md
new file mode 100644
index 0000000000000..e3dcea78c827a
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.set.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md) > [set](./kibana-plugin-core-public.executioncontextsetup.set.md)
+
+## ExecutionContextSetup.set() method
+
+Set the current top level context
+
+Signature:
+
+```typescript
+set(c$: KibanaExecutionContext): void;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| c$ | KibanaExecutionContext | |
+
+Returns:
+
+void
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.withglobalcontext.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.withglobalcontext.md
new file mode 100644
index 0000000000000..574d0fd989750
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextsetup.withglobalcontext.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md) > [withGlobalContext](./kibana-plugin-core-public.executioncontextsetup.withglobalcontext.md)
+
+## ExecutionContextSetup.withGlobalContext() method
+
+merges the current top level context with the specific event context
+
+Signature:
+
+```typescript
+withGlobalContext(context?: KibanaExecutionContext): KibanaExecutionContext;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| context | KibanaExecutionContext | |
+
+Returns:
+
+KibanaExecutionContext
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextstart.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextstart.md
new file mode 100644
index 0000000000000..0d210ba5bb1c4
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextstart.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextStart](./kibana-plugin-core-public.executioncontextstart.md)
+
+## ExecutionContextStart type
+
+See [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md).
+
+Signature:
+
+```typescript
+export declare type ExecutionContextStart = ExecutionContextSetup;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md
index 6266639b63976..d8f8a77d84b2f 100644
--- a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md
+++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md
@@ -10,9 +10,10 @@ Represents a meta-information about a Kibana entity initiating a search request.
```typescript
export declare type KibanaExecutionContext = {
- readonly type: string;
- readonly name: string;
- readonly id: string;
+ readonly type?: string;
+ readonly name?: string;
+ readonly page?: string;
+ readonly id?: string;
readonly description?: string;
readonly url?: string;
child?: KibanaExecutionContext;
diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md
index b51f5ed833fd3..fca697144a872 100644
--- a/docs/development/core/public/kibana-plugin-core-public.md
+++ b/docs/development/core/public/kibana-plugin-core-public.md
@@ -62,6 +62,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | DeprecationsService provides methods to fetch domain deprecation details from the Kibana server. |
| [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | |
| [ErrorToastOptions](./kibana-plugin-core-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) error APIs. |
+| [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md) | Kibana execution context. Used to provide execution context to Elasticsearch, reporting, performance monitoring, etc. |
| [FatalErrorInfo](./kibana-plugin-core-public.fatalerrorinfo.md) | Represents the message
and stack
of a fatal Error |
| [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
| [HttpFetchOptions](./kibana-plugin-core-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-core-public.httphandler.md). |
@@ -160,6 +161,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [ChromeBreadcrumb](./kibana-plugin-core-public.chromebreadcrumb.md) | |
| [ChromeHelpExtensionLinkBase](./kibana-plugin-core-public.chromehelpextensionlinkbase.md) | |
| [ChromeHelpExtensionMenuLink](./kibana-plugin-core-public.chromehelpextensionmenulink.md) | |
+| [ExecutionContextStart](./kibana-plugin-core-public.executioncontextstart.md) | See [ExecutionContextSetup](./kibana-plugin-core-public.executioncontextsetup.md). |
| [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
| [HttpStart](./kibana-plugin-core-public.httpstart.md) | See [HttpSetup](./kibana-plugin-core-public.httpsetup.md) |
| [IToasts](./kibana-plugin-core-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-core-public.toastsapi.md). |
diff --git a/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.getaslabels.md b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.getaslabels.md
new file mode 100644
index 0000000000000..c8816a3deee4d
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.getaslabels.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) > [getAsLabels](./kibana-plugin-core-server.executioncontextsetup.getaslabels.md)
+
+## ExecutionContextSetup.getAsLabels() method
+
+Signature:
+
+```typescript
+getAsLabels(): apm.Labels;
+```
+Returns:
+
+apm.Labels
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md
index 24591648ad953..7fdc4d1ec1d57 100644
--- a/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md
+++ b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md
@@ -15,5 +15,6 @@ export interface ExecutionContextSetup
| Method | Description |
| --- | --- |
+| [getAsLabels()](./kibana-plugin-core-server.executioncontextsetup.getaslabels.md) | |
| [withContext(context, fn)](./kibana-plugin-core-server.executioncontextsetup.withcontext.md) | Keeps track of execution context while the passed function is executed. Data are carried over all async operations spawned by the passed function. The nested calls stack the registered context on top of each other. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md
index 0d65a3662da6f..792af8f693869 100644
--- a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md
+++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md
@@ -10,9 +10,10 @@ Represents a meta-information about a Kibana entity initiating a search request.
```typescript
export declare type KibanaExecutionContext = {
- readonly type: string;
- readonly name: string;
- readonly id: string;
+ readonly type?: string;
+ readonly name?: string;
+ readonly page?: string;
+ readonly id?: string;
readonly description?: string;
readonly url?: string;
child?: KibanaExecutionContext;
diff --git a/src/core/public/apm_system.test.ts b/src/core/public/apm_system.test.ts
index 842d5de7e5afc..0a3a1dee63e57 100644
--- a/src/core/public/apm_system.test.ts
+++ b/src/core/public/apm_system.test.ts
@@ -13,6 +13,7 @@ import type { Transaction } from '@elastic/apm-rum';
import { ApmSystem } from './apm_system';
import { Subject } from 'rxjs';
import { InternalApplicationStart } from './application/types';
+import { executionContextServiceMock } from './execution_context/execution_context_service.mock';
const initMock = init as jest.Mocked;
const apmMock = apm as DeeplyMockedKeys;
@@ -96,6 +97,7 @@ describe('ApmSystem', () => {
application: {
currentAppId$,
} as any as InternalApplicationStart,
+ executionContext: executionContextServiceMock.createInternalStartContract(),
});
expect(mark).toHaveBeenCalledWith('apm-start');
@@ -118,6 +120,7 @@ describe('ApmSystem', () => {
application: {
currentAppId$,
} as any as InternalApplicationStart,
+ executionContext: executionContextServiceMock.createInternalStartContract(),
});
currentAppId$.next('myapp');
@@ -145,6 +148,7 @@ describe('ApmSystem', () => {
application: {
currentAppId$,
} as any as InternalApplicationStart,
+ executionContext: executionContextServiceMock.createInternalStartContract(),
});
currentAppId$.next('myapp');
diff --git a/src/core/public/apm_system.ts b/src/core/public/apm_system.ts
index 2231f394381f0..4e116c0a0182d 100644
--- a/src/core/public/apm_system.ts
+++ b/src/core/public/apm_system.ts
@@ -10,6 +10,7 @@ import type { ApmBase, AgentConfigOptions, Transaction } from '@elastic/apm-rum'
import { modifyUrl } from '@kbn/std';
import { CachedResourceObserver } from './apm_resource_counter';
import type { InternalApplicationStart } from './application';
+import { ExecutionContextStart } from './execution_context';
/** "GET protocol://hostname:port/pathname" */
const HTTP_REQUEST_TRANSACTION_NAME_REGEX =
@@ -27,6 +28,7 @@ interface ApmConfig extends AgentConfigOptions {
interface StartDeps {
application: InternalApplicationStart;
+ executionContext: ExecutionContextStart;
}
export class ApmSystem {
@@ -34,6 +36,7 @@ export class ApmSystem {
private pageLoadTransaction?: Transaction;
private resourceObserver: CachedResourceObserver;
private apm?: ApmBase;
+
/**
* `apmConfig` would be populated with relevant APM RUM agent
* configuration if server is started with elastic.apm.* config.
@@ -64,6 +67,15 @@ export class ApmSystem {
this.markPageLoadStart();
+ start.executionContext.context$.subscribe((c) => {
+ // We're using labels because we want the context to be indexed
+ // https://www.elastic.co/guide/en/apm/get-started/current/metadata.html
+ const apmContext = start.executionContext.getAsLabels();
+ this.apm?.addLabels(apmContext);
+ });
+
+ // TODO: Start a new transaction every page change instead of every app change.
+
/**
* Register listeners for navigation changes and capture them as
* route-change transactions after Kibana app is bootstrapped
diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts
index 3d3331d54792b..1aa01c13dd375 100644
--- a/src/core/public/core_system.ts
+++ b/src/core/public/core_system.ts
@@ -31,6 +31,7 @@ import { DeprecationsService } from './deprecations';
import { ThemeService } from './theme';
import { CoreApp } from './core_app';
import type { InternalApplicationSetup, InternalApplicationStart } from './application/types';
+import { ExecutionContextService } from './execution_context';
interface Params {
rootDomElement: HTMLElement;
@@ -87,6 +88,7 @@ export class CoreSystem {
private readonly theme: ThemeService;
private readonly rootDomElement: HTMLElement;
private readonly coreContext: CoreContext;
+ private readonly executionContext: ExecutionContextService;
private fatalErrorsSetup: FatalErrorsSetup | null = null;
constructor(params: Params) {
@@ -121,6 +123,7 @@ export class CoreSystem {
this.application = new ApplicationService();
this.integrations = new IntegrationsService();
this.deprecations = new DeprecationsService();
+ this.executionContext = new ExecutionContextService();
this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins);
this.coreApp = new CoreApp(this.coreContext);
@@ -137,7 +140,13 @@ export class CoreSystem {
});
await this.integrations.setup();
this.docLinks.setup();
- const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup });
+
+ const executionContext = this.executionContext.setup();
+ const http = this.http.setup({
+ injectedMetadata,
+ fatalErrors: this.fatalErrorsSetup,
+ executionContext,
+ });
const uiSettings = this.uiSettings.setup({ http, injectedMetadata });
const notifications = this.notifications.setup({ uiSettings });
const theme = this.theme.setup({ injectedMetadata });
@@ -153,6 +162,7 @@ export class CoreSystem {
notifications,
theme,
uiSettings,
+ executionContext,
};
// Services that do not expose contracts at setup
@@ -201,6 +211,11 @@ export class CoreSystem {
targetDomElement: notificationsTargetDomElement,
});
const application = await this.application.start({ http, theme, overlays });
+
+ const executionContext = this.executionContext.start({
+ curApp$: application.currentAppId$,
+ });
+
const chrome = await this.chrome.start({
application,
docLinks,
@@ -216,6 +231,7 @@ export class CoreSystem {
application,
chrome,
docLinks,
+ executionContext,
http,
theme,
savedObjects,
@@ -248,6 +264,7 @@ export class CoreSystem {
return {
application,
+ executionContext,
};
} catch (error) {
if (this.fatalErrorsSetup) {
diff --git a/src/core/public/execution_context/execution_context_service.mock.ts b/src/core/public/execution_context/execution_context_service.mock.ts
new file mode 100644
index 0000000000000..3941aa333cfa2
--- /dev/null
+++ b/src/core/public/execution_context/execution_context_service.mock.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { PublicMethodsOf } from '@kbn/utility-types';
+import { BehaviorSubject } from 'rxjs';
+
+import { ExecutionContextService, ExecutionContextSetup } from './execution_context_service';
+
+const createContractMock = (): jest.Mocked => ({
+ context$: new BehaviorSubject({}),
+ clear: jest.fn(),
+ set: jest.fn(),
+ get: jest.fn(),
+ getAsLabels: jest.fn(),
+ withGlobalContext: jest.fn(),
+});
+
+const createMock = (): jest.Mocked> => ({
+ setup: jest.fn().mockReturnValue(createContractMock()),
+ start: jest.fn().mockReturnValue(createContractMock()),
+ stop: jest.fn(),
+});
+
+export const executionContextServiceMock = {
+ create: createMock,
+ createSetupContract: createContractMock,
+ createStartContract: createContractMock,
+ createInternalSetupContract: createContractMock,
+ createInternalStartContract: createContractMock,
+};
diff --git a/src/core/public/execution_context/execution_context_service.ts b/src/core/public/execution_context/execution_context_service.ts
new file mode 100644
index 0000000000000..bf13a7351f9b5
--- /dev/null
+++ b/src/core/public/execution_context/execution_context_service.ts
@@ -0,0 +1,134 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { isEqual, isUndefined, omitBy } from 'lodash';
+import { BehaviorSubject, Observable, Subscription } from 'rxjs';
+import { CoreService, KibanaExecutionContext } from '../../types';
+
+// Should be exported from elastic/apm-rum
+export type LabelValue = string | number | boolean;
+
+export interface Labels {
+ [key: string]: LabelValue;
+}
+
+/**
+ * Kibana execution context.
+ * Used to provide execution context to Elasticsearch, reporting, performance monitoring, etc.
+ * @public
+ **/
+export interface ExecutionContextSetup {
+ /**
+ * The current context observable
+ **/
+ context$: Observable;
+ /**
+ * Set the current top level context
+ **/
+ set(c$: KibanaExecutionContext): void;
+ /**
+ * Get the current top level context
+ **/
+ get(): KibanaExecutionContext;
+ /**
+ * clears the context
+ **/
+ clear(): void;
+ /**
+ * returns apm labels
+ **/
+ getAsLabels(): Labels;
+ /**
+ * merges the current top level context with the specific event context
+ **/
+ withGlobalContext(context?: KibanaExecutionContext): KibanaExecutionContext;
+}
+
+/**
+ * See {@link ExecutionContextSetup}.
+ * @public
+ */
+export type ExecutionContextStart = ExecutionContextSetup;
+
+export interface StartDeps {
+ curApp$: Observable;
+}
+
+/** @internal */
+export class ExecutionContextService
+ implements CoreService
+{
+ private context$: BehaviorSubject = new BehaviorSubject({});
+ private appId?: string;
+ private subscription: Subscription = new Subscription();
+ private contract?: ExecutionContextSetup;
+
+ public setup() {
+ this.contract = {
+ context$: this.context$.asObservable(),
+ clear: () => {
+ this.context$.next({});
+ },
+ set: (c: KibanaExecutionContext) => {
+ const newVal = {
+ ...this.context$.value,
+ ...c,
+ };
+ if (!isEqual(newVal, this.context$.value)) {
+ this.context$.next(newVal);
+ }
+ },
+ get: () => {
+ return this.mergeContext();
+ },
+ getAsLabels: () => {
+ return this.removeUndefined({
+ name: this.appId,
+ id: this.context$.value?.id,
+ page: this.context$.value?.page,
+ }) as Labels;
+ },
+ withGlobalContext: (context: KibanaExecutionContext) => {
+ return this.mergeContext(context);
+ },
+ };
+
+ return this.contract;
+ }
+
+ public start({ curApp$ }: StartDeps) {
+ const start = this.contract!;
+
+ // Track app id changes and clear context on app change
+ this.subscription.add(
+ curApp$.subscribe((appId) => {
+ this.appId = appId;
+ start.clear();
+ })
+ );
+
+ return start;
+ }
+
+ public stop() {
+ this.subscription.unsubscribe();
+ }
+
+ private removeUndefined(context: KibanaExecutionContext = {}) {
+ return omitBy(context, isUndefined);
+ }
+
+ private mergeContext(context: KibanaExecutionContext = {}): KibanaExecutionContext {
+ return {
+ name: this.appId,
+ url: window.location.pathname,
+ ...this.context$.value,
+ ...context,
+ };
+ }
+}
diff --git a/src/core/public/execution_context/index.ts b/src/core/public/execution_context/index.ts
index b15a967ac714a..f160b0ecea67b 100644
--- a/src/core/public/execution_context/index.ts
+++ b/src/core/public/execution_context/index.ts
@@ -8,3 +8,5 @@
export type { KibanaExecutionContext } from '../../types';
export { ExecutionContextContainer } from './execution_context_container';
+export { ExecutionContextService } from './execution_context_service';
+export type { ExecutionContextSetup, ExecutionContextStart } from './execution_context_service';
diff --git a/src/core/public/http/fetch.test.ts b/src/core/public/http/fetch.test.ts
index e897d69057e02..0691e2443c17f 100644
--- a/src/core/public/http/fetch.test.ts
+++ b/src/core/public/http/fetch.test.ts
@@ -15,6 +15,7 @@ import { first } from 'rxjs/operators';
import { Fetch } from './fetch';
import { BasePath } from './base_path';
import { HttpResponse, HttpFetchOptionsWithPath } from './types';
+import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
function delay(duration: number) {
return new Promise((r) => setTimeout(r, duration));
@@ -23,9 +24,11 @@ function delay(duration: number) {
const BASE_PATH = 'http://localhost/myBase';
describe('Fetch', () => {
+ const executionContextMock = executionContextServiceMock.createSetupContract();
const fetchInstance = new Fetch({
basePath: new BasePath(BASE_PATH),
kibanaVersion: 'VERSION',
+ executionContext: executionContextMock,
});
afterEach(() => {
fetchMock.restore();
@@ -230,13 +233,15 @@ describe('Fetch', () => {
it('should inject context headers if provided', async () => {
fetchMock.get('*', {});
+ const context = {
+ type: 'test-type',
+ name: 'test-name',
+ description: 'test-description',
+ id: '42',
+ };
+ executionContextMock.withGlobalContext.mockReturnValue(context);
await fetchInstance.fetch('/my/path', {
- context: {
- type: 'test-type',
- name: 'test-name',
- description: 'test-description',
- id: '42',
- },
+ context,
});
expect(fetchMock.lastOptions()!.headers).toMatchObject({
@@ -245,6 +250,29 @@ describe('Fetch', () => {
});
});
+ it('should include top level context context headers if provided', async () => {
+ fetchMock.get('*', {});
+
+ const context = {
+ type: 'test-type',
+ name: 'test-name',
+ description: 'test-description',
+ id: '42',
+ };
+ executionContextMock.withGlobalContext.mockReturnValue({
+ ...context,
+ name: 'banana',
+ });
+ await fetchInstance.fetch('/my/path', {
+ context,
+ });
+
+ expect(fetchMock.lastOptions()!.headers).toMatchObject({
+ 'x-kbn-context':
+ '%7B%22type%22%3A%22test-type%22%2C%22name%22%3A%22banana%22%2C%22description%22%3A%22test-description%22%2C%22id%22%3A%2242%22%7D',
+ });
+ });
+
it('should return response', async () => {
fetchMock.get('*', { foo: 'bar' });
const json = await fetchInstance.fetch('/my/path');
diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts
index 4ee81f4b47aa0..9a333161e1b7a 100644
--- a/src/core/public/http/fetch.ts
+++ b/src/core/public/http/fetch.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { omitBy } from 'lodash';
+import { isEmpty, omitBy } from 'lodash';
import { format } from 'url';
import { BehaviorSubject } from 'rxjs';
@@ -22,11 +22,12 @@ import { HttpFetchError } from './http_fetch_error';
import { HttpInterceptController } from './http_intercept_controller';
import { interceptRequest, interceptResponse } from './intercept';
import { HttpInterceptHaltError } from './http_intercept_halt_error';
-import { ExecutionContextContainer } from '../execution_context';
+import { ExecutionContextContainer, ExecutionContextSetup } from '../execution_context';
interface Params {
basePath: IBasePath;
kibanaVersion: string;
+ executionContext: ExecutionContextSetup;
}
const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/;
@@ -107,6 +108,7 @@ export class Fetch {
};
private createRequest(options: HttpFetchOptionsWithPath): Request {
+ const context = this.params.executionContext.withGlobalContext(options.context);
// Merge and destructure options out that are not applicable to the Fetch API.
const {
query,
@@ -125,7 +127,7 @@ export class Fetch {
'Content-Type': 'application/json',
...options.headers,
'kbn-version': this.params.kibanaVersion,
- ...(options.context ? new ExecutionContextContainer(options.context).toHeader() : {}),
+ ...(!isEmpty(context) ? new ExecutionContextContainer(context).toHeader() : {}),
}),
};
diff --git a/src/core/public/http/http_service.test.ts b/src/core/public/http/http_service.test.ts
index 2b41991904d97..698fa876433d4 100644
--- a/src/core/public/http/http_service.test.ts
+++ b/src/core/public/http/http_service.test.ts
@@ -14,6 +14,7 @@ import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.moc
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { HttpService } from './http_service';
import { Observable } from 'rxjs';
+import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
describe('interceptors', () => {
afterEach(() => fetchMock.restore());
@@ -22,9 +23,10 @@ describe('interceptors', () => {
fetchMock.get('*', {});
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
const fatalErrors = fatalErrorsServiceMock.createSetupContract();
+ const executionContext = executionContextServiceMock.createSetupContract();
const httpService = new HttpService();
- const setup = httpService.setup({ fatalErrors, injectedMetadata });
+ const setup = httpService.setup({ fatalErrors, injectedMetadata, executionContext });
const setupInterceptor = jest.fn();
setup.intercept({ request: setupInterceptor });
@@ -47,7 +49,8 @@ describe('#setup()', () => {
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
const fatalErrors = fatalErrorsServiceMock.createSetupContract();
const httpService = new HttpService();
- httpService.setup({ fatalErrors, injectedMetadata });
+ const executionContext = executionContextServiceMock.createSetupContract();
+ httpService.setup({ fatalErrors, injectedMetadata, executionContext });
const loadingServiceSetup = loadingServiceMock.setup.mock.results[0].value;
// We don't verify that this Observable comes from Fetch#getLoadingCount$() to avoid complex mocking
expect(loadingServiceSetup.addLoadingCountSource).toHaveBeenCalledWith(expect.any(Observable));
@@ -59,7 +62,8 @@ describe('#stop()', () => {
const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
const fatalErrors = fatalErrorsServiceMock.createSetupContract();
const httpService = new HttpService();
- httpService.setup({ fatalErrors, injectedMetadata });
+ const executionContext = executionContextServiceMock.createSetupContract();
+ httpService.setup({ fatalErrors, injectedMetadata, executionContext });
httpService.start();
httpService.stop();
expect(loadingServiceMock.stop).toHaveBeenCalled();
diff --git a/src/core/public/http/http_service.ts b/src/core/public/http/http_service.ts
index a9719cfce67af..390130da4e07d 100644
--- a/src/core/public/http/http_service.ts
+++ b/src/core/public/http/http_service.ts
@@ -15,10 +15,12 @@ import { LoadingCountService } from './loading_count_service';
import { Fetch } from './fetch';
import { CoreService } from '../../types';
import { ExternalUrlService } from './external_url_service';
+import { ExecutionContextSetup } from '../execution_context';
interface HttpDeps {
injectedMetadata: InjectedMetadataSetup;
fatalErrors: FatalErrorsSetup;
+ executionContext: ExecutionContextSetup;
}
/** @internal */
@@ -27,14 +29,15 @@ export class HttpService implements CoreService {
private readonly loadingCount = new LoadingCountService();
private service?: HttpSetup;
- public setup({ injectedMetadata, fatalErrors }: HttpDeps): HttpSetup {
+ public setup({ injectedMetadata, fatalErrors, executionContext }: HttpDeps): HttpSetup {
const kibanaVersion = injectedMetadata.getKibanaVersion();
const basePath = new BasePath(
injectedMetadata.getBasePath(),
injectedMetadata.getServerBasePath(),
injectedMetadata.getPublicBaseUrl()
);
- const fetchService = new Fetch({ basePath, kibanaVersion });
+
+ const fetchService = new Fetch({ basePath, kibanaVersion, executionContext });
const loadingCount = this.loadingCount.setup({ fatalErrors });
loadingCount.addLoadingCountSource(fetchService.getRequestCount$());
diff --git a/src/core/public/index.ts b/src/core/public/index.ts
index ded7db9c6f892..50d8bf304ddf8 100644
--- a/src/core/public/index.ts
+++ b/src/core/public/index.ts
@@ -65,6 +65,7 @@ import { DocLinksStart } from './doc_links';
import { SavedObjectsStart } from './saved_objects';
import { DeprecationsServiceStart } from './deprecations';
import type { ThemeServiceSetup, ThemeServiceStart } from './theme';
+import { ExecutionContextSetup, ExecutionContextStart } from './execution_context';
export type {
PackageInfo,
@@ -194,7 +195,11 @@ export type { MountPoint, UnmountCallback, PublicUiSettingsParams } from './type
export { URL_MAX_LENGTH } from './core_app';
-export type { KibanaExecutionContext } from './execution_context';
+export type {
+ KibanaExecutionContext,
+ ExecutionContextSetup,
+ ExecutionContextStart,
+} from './execution_context';
/**
* Core services exposed to the `Plugin` setup lifecycle
@@ -221,6 +226,8 @@ export interface CoreSetup, any, any]>, []>(() =>
Promise.resolve([createCoreStartMock({ basePath }), pluginStartDeps, pluginStartContract])
@@ -76,6 +78,7 @@ function createCoreStartMock({ basePath = '' } = {}) {
application: applicationServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
docLinks: docLinksServiceMock.createStartContract(),
+ executionContext: executionContextServiceMock.createStartContract(),
http: httpServiceMock.createStartContract({ basePath }),
i18n: i18nServiceMock.createStartContract(),
notifications: notificationServiceMock.createStartContract(),
diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts
index 345aea4b6cac8..8c085d3de2369 100644
--- a/src/core/public/plugins/plugin_context.ts
+++ b/src/core/public/plugins/plugin_context.ts
@@ -88,6 +88,7 @@ export function createPluginSetupContext<
registerAppUpdater: (statusUpdater$) => deps.application.registerAppUpdater(statusUpdater$),
},
fatalErrors: deps.fatalErrors,
+ executionContext: deps.executionContext,
http: deps.http,
notifications: deps.notifications,
uiSettings: deps.uiSettings,
@@ -129,6 +130,7 @@ export function createPluginStartContext<
getUrlForApp: deps.application.getUrlForApp,
},
docLinks: deps.docLinks,
+ executionContext: deps.executionContext,
http: deps.http,
chrome: omit(deps.chrome, 'getComponent'),
i18n: deps.i18n,
diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts
index c4e3b7990ba32..40976424b7edd 100644
--- a/src/core/public/plugins/plugins_service.test.ts
+++ b/src/core/public/plugins/plugins_service.test.ts
@@ -36,6 +36,7 @@ import { docLinksServiceMock } from '../doc_links/doc_links_service.mock';
import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock';
import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock';
import { themeServiceMock } from '../theme/theme_service.mock';
+import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
export let mockPluginInitializers: Map;
@@ -85,6 +86,7 @@ describe('PluginsService', () => {
mockSetupDeps = {
application: applicationServiceMock.createInternalSetupContract(),
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
+ executionContext: executionContextServiceMock.createSetupContract(),
http: httpServiceMock.createSetupContract(),
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
notifications: notificationServiceMock.createSetupContract(),
@@ -100,6 +102,7 @@ describe('PluginsService', () => {
mockStartDeps = {
application: applicationServiceMock.createInternalStartContract(),
docLinks: docLinksServiceMock.createStartContract(),
+ executionContext: executionContextServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
i18n: i18nServiceMock.createStartContract(),
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 4cf845de4617d..e3f2822b5a7c8 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -401,6 +401,8 @@ export interface CoreSetup;
@@ -429,6 +431,8 @@ export interface CoreStart {
// (undocumented)
docLinks: DocLinksStart;
// (undocumented)
+ executionContext: ExecutionContextStart;
+ // (undocumented)
fatalErrors: FatalErrorsStart;
// (undocumented)
http: HttpStart;
@@ -461,6 +465,7 @@ export class CoreSystem {
// (undocumented)
start(): Promise<{
application: InternalApplicationStart;
+ executionContext: ExecutionContextSetup;
} | undefined>;
// (undocumented)
stop(): void;
@@ -511,6 +516,20 @@ export interface ErrorToastOptions extends ToastOptions {
toastMessage?: string;
}
+// @public
+export interface ExecutionContextSetup {
+ clear(): void;
+ context$: Observable;
+ get(): KibanaExecutionContext;
+ // Warning: (ae-forgotten-export) The symbol "Labels" needs to be exported by the entry point index.d.ts
+ getAsLabels(): Labels_2;
+ set(c$: KibanaExecutionContext): void;
+ withGlobalContext(context?: KibanaExecutionContext): KibanaExecutionContext;
+}
+
+// @public
+export type ExecutionContextStart = ExecutionContextSetup;
+
// @public
export interface FatalErrorInfo {
// (undocumented)
@@ -751,9 +770,10 @@ export interface IUiSettingsClient {
// @public
export type KibanaExecutionContext = {
- readonly type: string;
- readonly name: string;
- readonly id: string;
+ readonly type?: string;
+ readonly name?: string;
+ readonly page?: string;
+ readonly id?: string;
readonly description?: string;
readonly url?: string;
child?: KibanaExecutionContext;
@@ -1522,6 +1542,6 @@ export interface UserProvidedValues {
// Warnings were encountered during analysis:
//
-// src/core/public/core_system.ts:173:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts
+// src/core/public/core_system.ts:183:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts
```
diff --git a/src/core/server/execution_context/execution_context_container.ts b/src/core/server/execution_context/execution_context_container.ts
index de895bcff5ecb..1528df6c23140 100644
--- a/src/core/server/execution_context/execution_context_container.ts
+++ b/src/core/server/execution_context/execution_context_container.ts
@@ -50,9 +50,10 @@ export interface IExecutionContextContainer {
}
function stringify(ctx: KibanaExecutionContext): string {
- const stringifiedCtx = `${encodeURIComponent(ctx.type)}:${encodeURIComponent(
+ const encodeURIComponentIfNotEmpty = (val?: string) => encodeURIComponent(val || '');
+ const stringifiedCtx = `${encodeURIComponentIfNotEmpty(ctx.type)}:${encodeURIComponentIfNotEmpty(
ctx.name
- )}:${encodeURIComponent(ctx.id!)}`;
+ )}:${encodeURIComponentIfNotEmpty(ctx.id)}`;
return ctx.child ? `${stringifiedCtx};${stringify(ctx.child)}` : stringifiedCtx;
}
diff --git a/src/core/server/execution_context/execution_context_service.mock.ts b/src/core/server/execution_context/execution_context_service.mock.ts
index 68aab7a5b84f8..85768eb423f26 100644
--- a/src/core/server/execution_context/execution_context_service.mock.ts
+++ b/src/core/server/execution_context/execution_context_service.mock.ts
@@ -26,6 +26,7 @@ const createExecutionContextMock = () => {
get: jest.fn(),
getParentContextFrom: jest.fn(),
getAsHeader: jest.fn(),
+ getAsLabels: jest.fn(),
};
mock.withContext.mockImplementation(withContextMock);
return mock;
@@ -38,6 +39,7 @@ const createInternalSetupContractMock = () => {
const createSetupContractMock = () => {
const mock: jest.Mocked = {
withContext: jest.fn(),
+ getAsLabels: jest.fn(),
};
mock.withContext.mockImplementation(withContextMock);
return mock;
diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts
index 6e2b809e23043..03ae2cb36c9ec 100644
--- a/src/core/server/execution_context/execution_context_service.ts
+++ b/src/core/server/execution_context/execution_context_service.ts
@@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
import { AsyncLocalStorage } from 'async_hooks';
+import apm from 'elastic-apm-node';
+import { isUndefined, omitBy } from 'lodash';
import type { Subscription } from 'rxjs';
import type { CoreService, KibanaExecutionContext } from '../../types';
@@ -39,6 +41,10 @@ export interface IExecutionContext {
* returns serialized representation to send as a header
**/
getAsHeader(): string | undefined;
+ /**
+ * returns apm labels
+ **/
+ getAsLabels(): apm.Labels;
}
/**
@@ -61,6 +67,7 @@ export interface ExecutionContextSetup {
* The nested calls stack the registered context on top of each other.
**/
withContext(context: KibanaExecutionContext | undefined, fn: (...args: any[]) => R): R;
+ getAsLabels(): apm.Labels;
}
/**
@@ -97,6 +104,7 @@ export class ExecutionContextService
setRequestId: this.setRequestId.bind(this),
get: this.get.bind(this),
getAsHeader: this.getAsHeader.bind(this),
+ getAsLabels: this.getAsLabels.bind(this),
};
}
@@ -108,6 +116,7 @@ export class ExecutionContextService
withContext: this.withContext.bind(this),
get: this.get.bind(this),
getAsHeader: this.getAsHeader.bind(this),
+ getAsLabels: this.getAsLabels.bind(this),
};
}
@@ -161,4 +170,18 @@ export class ExecutionContextService
return `${requestId}${executionContextStr}`;
}
+
+ private getAsLabels() {
+ if (!this.enabled) return {};
+ const executionContext = this.contextStore.getStore()?.toJSON();
+
+ return omitBy(
+ {
+ name: executionContext?.name,
+ id: executionContext?.id,
+ page: executionContext?.page,
+ },
+ isUndefined
+ );
+ }
}
diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts
index 4623b09b19e29..813f8e9784332 100644
--- a/src/core/server/http/http_server.ts
+++ b/src/core/server/http/http_server.ts
@@ -21,6 +21,7 @@ import agent from 'elastic-apm-node';
import type { Duration } from 'moment';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
+import apm from 'elastic-apm-node';
import { Logger, LoggerFactory } from '../logging';
import { HttpConfig } from './http_config';
import type { InternalExecutionContextSetup } from '../execution_context';
@@ -338,7 +339,11 @@ export class HttpServer {
const requestId = getRequestId(request, config.requestId);
const parentContext = executionContext?.getParentContextFrom(request.headers);
- if (parentContext) executionContext?.set(parentContext);
+
+ if (executionContext && parentContext) {
+ executionContext.set(parentContext);
+ apm.addLabels(executionContext.getAsLabels());
+ }
executionContext?.setRequestId(requestId);
diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts
index 18abbe88c4913..983a12627b7f3 100644
--- a/src/core/server/plugins/plugin_context.ts
+++ b/src/core/server/plugins/plugin_context.ts
@@ -161,6 +161,7 @@ export function createPluginSetupContext(
},
executionContext: {
withContext: deps.executionContext.withContext,
+ getAsLabels: deps.executionContext.getAsLabels,
},
http: {
createCookieSessionStorageFactory: deps.http.createCookieSessionStorageFactory,
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index d7ed4928e1cf5..5fe1942ed8453 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -7,6 +7,7 @@
///
import { AddConfigDeprecation } from '@kbn/config';
+import apm from 'elastic-apm-node';
import Boom from '@hapi/boom';
import { ByteSizeValue } from '@kbn/config-schema';
import { CliArgs } from '@kbn/config';
@@ -994,6 +995,8 @@ export class EventLoopDelaysMonitor {
// @public (undocumented)
export interface ExecutionContextSetup {
+ // (undocumented)
+ getAsLabels(): apm.Labels;
withContext(context: KibanaExecutionContext | undefined, fn: (...args: any[]) => R): R;
}
@@ -1319,9 +1322,10 @@ export interface IUiSettingsClient {
// @public
export type KibanaExecutionContext = {
- readonly type: string;
- readonly name: string;
- readonly id: string;
+ readonly type?: string;
+ readonly name?: string;
+ readonly page?: string;
+ readonly id?: string;
readonly description?: string;
readonly url?: string;
child?: KibanaExecutionContext;
diff --git a/src/core/test_helpers/http_test_setup.ts b/src/core/test_helpers/http_test_setup.ts
index 468034dffceb9..67b340898aab4 100644
--- a/src/core/test_helpers/http_test_setup.ts
+++ b/src/core/test_helpers/http_test_setup.ts
@@ -9,6 +9,7 @@
import { HttpService } from '../public/http';
import { fatalErrorsServiceMock } from '../public/fatal_errors/fatal_errors_service.mock';
import { injectedMetadataServiceMock } from '../public/injected_metadata/injected_metadata_service.mock';
+import { executionContextServiceMock } from '../public/execution_context/execution_context_service.mock';
export type SetupTap = (
injectedMetadata: ReturnType,
@@ -28,7 +29,8 @@ export function setup(tap: SetupTap = defaultTap) {
tap(injectedMetadata, fatalErrors);
const httpService = new HttpService();
- const http = httpService.setup({ fatalErrors, injectedMetadata });
+ const executionContext = executionContextServiceMock.createSetupContract();
+ const http = httpService.setup({ fatalErrors, injectedMetadata, executionContext });
return { httpService, injectedMetadata, fatalErrors, http };
}
diff --git a/src/core/types/execution_context.ts b/src/core/types/execution_context.ts
index 1b985a73f410b..d790b8d855fd4 100644
--- a/src/core/types/execution_context.ts
+++ b/src/core/types/execution_context.ts
@@ -16,11 +16,13 @@ export type KibanaExecutionContext = {
/**
* Kibana application initated an operation.
* */
- readonly type: string; // 'visualization' | 'actions' | 'server' | ..;
- /** public name of a user-facing feature */
- readonly name: string; // 'TSVB' | 'Lens' | 'action_execution' | ..;
+ readonly type?: string; // 'visualization' | 'actions' | 'server' | ..;
+ /** public name of an application or a user-facing feature */
+ readonly name?: string; // 'TSVB' | 'Lens' | 'action_execution' | ..;
+ /** a stand alone, logical unit such as an application page or tab */
+ readonly page?: string;
/** unique value to identify the source */
- readonly id: string;
+ readonly id?: string;
/** human readable description. For example, a vis title, action name */
readonly description?: string;
/** in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url */
diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx
index 7aedbe9e11001..a32e6643a4e3a 100644
--- a/src/plugins/dashboard/public/application/dashboard_app.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_app.tsx
@@ -11,7 +11,7 @@ import React, { useEffect, useMemo } from 'react';
import { useDashboardSelector } from './state';
import { useDashboardAppState } from './hooks';
-import { useKibana } from '../../../kibana_react/public';
+import { useKibana, useExecutionContext } from '../../../kibana_react/public';
import {
getDashboardBreadcrumb,
getDashboardTitle,
@@ -48,6 +48,12 @@ export function DashboardApp({
[core.notifications.toasts, history, uiSettings]
);
+ useExecutionContext(core.executionContext, {
+ type: 'application',
+ page: 'app',
+ id: savedDashboardId || 'new',
+ });
+
const dashboardState = useDashboardSelector((state) => state.dashboardStateReducer);
const dashboardAppState = useDashboardAppState({
history,
diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts
index 98b3d761f350e..26641dc52e3d5 100644
--- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts
+++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts
@@ -220,11 +220,7 @@ export const useDashboardAppState = ({
savedDashboard,
data,
executionContext: {
- type: 'application',
- name: 'dashboard',
- id: savedDashboard.id ?? 'unsaved_dashboard',
description: savedDashboard.title,
- url: history.location.pathname,
},
});
if (canceled || !dashboardContainer) {
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
index 5b53fc47e06a4..65374ad723f23 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
@@ -35,6 +35,7 @@ import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
import { confirmCreateWithUnsaved, confirmDiscardUnsavedChanges } from './confirm_overlays';
import { getDashboardListItemLink } from './get_dashboard_list_item_link';
import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_session_storage';
+import { useExecutionContext } from '../../../../kibana_react/public';
export interface DashboardListingProps {
kbnUrlStateStorage: IKbnUrlStateStorage;
@@ -67,6 +68,11 @@ export const DashboardListing = ({
dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()
);
+ useExecutionContext(core.executionContext, {
+ type: 'application',
+ page: 'list',
+ });
+
// Set breadcrumbs useEffect
useEffect(() => {
setBreadcrumbs([
diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts
index 968dd870489fe..f1e2e903cadde 100644
--- a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts
+++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts
@@ -119,6 +119,7 @@ describe('SearchInterceptor', () => {
}),
uiSettings: mockCoreSetup.uiSettings,
http: mockCoreSetup.http,
+ executionContext: mockCoreSetup.executionContext,
session: sessionService,
theme: themeServiceMock.createSetupContract(),
});
@@ -543,7 +544,12 @@ describe('SearchInterceptor', () => {
.catch(() => {});
expect(fetchMock.mock.calls[0][0]).toEqual(
expect.objectContaining({
- options: { sessionId, isStored: true, isRestore: true, strategy: 'ese' },
+ options: {
+ sessionId,
+ isStored: true,
+ isRestore: true,
+ strategy: 'ese',
+ },
})
);
diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts
index 7dc1ce6dee078..251e191d589e3 100644
--- a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts
+++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts
@@ -61,6 +61,7 @@ import { SearchAbortController } from './search_abort_controller';
export interface SearchInterceptorDeps {
bfetch: BfetchPublicSetup;
http: CoreSetup['http'];
+ executionContext: CoreSetup['executionContext'];
uiSettings: CoreSetup['uiSettings'];
startServices: Promise<[CoreStart, any, unknown]>;
toasts: ToastsSetup;
@@ -297,10 +298,14 @@ export class SearchInterceptor {
}
}) as Promise;
} else {
+ const { executionContext, ...rest } = options || {};
return this.batchedFetch(
{
request,
- options: this.getSerializableOptions(options),
+ options: this.getSerializableOptions({
+ ...rest,
+ executionContext: this.deps.executionContext.withGlobalContext(executionContext),
+ }),
},
abortSignal
);
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index 961599de713df..b21ad44c7bd6d 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -89,7 +89,7 @@ export class SearchService implements Plugin {
constructor(private initializerContext: PluginInitializerContext) {}
public setup(
- { http, getStartServices, notifications, uiSettings, theme }: CoreSetup,
+ { http, getStartServices, notifications, uiSettings, executionContext, theme }: CoreSetup,
{ bfetch, expressions, usageCollection, nowProvider }: SearchServiceSetupDependencies
): ISearchSetup {
this.usageCollector = createUsageCollector(getStartServices, usageCollection);
@@ -108,6 +108,7 @@ export class SearchService implements Plugin {
this.searchInterceptor = new SearchInterceptor({
bfetch,
toasts: notifications.toasts,
+ executionContext,
http,
uiSettings,
startServices: getStartServices(),
diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts
index 314de4254851f..25b1bd0d7009d 100644
--- a/src/plugins/data/server/search/routes/bsearch.ts
+++ b/src/plugins/data/server/search/routes/bsearch.ts
@@ -9,6 +9,7 @@
import { catchError, first } from 'rxjs/operators';
import { BfetchServerSetup } from 'src/plugins/bfetch/server';
import type { ExecutionContextSetup } from 'src/core/server';
+import apm from 'elastic-apm-node';
import {
IKibanaSearchRequest,
IKibanaSearchResponse,
@@ -33,9 +34,10 @@ export function registerBsearchRoute(
*/
onBatchItem: async ({ request: requestData, options }) => {
const { executionContext, ...restOptions } = options || {};
+ return executionContextService.withContext(executionContext, () => {
+ apm.addLabels(executionContextService.getAsLabels());
- return executionContextService.withContext(executionContext, () =>
- search
+ return search
.search(requestData, restOptions)
.pipe(
first(),
@@ -49,8 +51,8 @@ export function registerBsearchRoute(
};
})
)
- .toPromise()
- );
+ .toPromise();
+ });
},
};
});
diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx
index a3ec8fc0a9af2..bcfde68abd99c 100644
--- a/src/plugins/dev_tools/public/application.tsx
+++ b/src/plugins/dev_tools/public/application.tsx
@@ -15,8 +15,14 @@ import { I18nProvider } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { euiThemeVars } from '@kbn/ui-theme';
-import { ApplicationStart, ChromeStart, ScopedHistory, CoreTheme } from 'src/core/public';
-import { KibanaThemeProvider } from '../../kibana_react/public';
+import type {
+ ApplicationStart,
+ ChromeStart,
+ ScopedHistory,
+ CoreTheme,
+ ExecutionContextStart,
+} from 'src/core/public';
+import { KibanaThemeProvider, useExecutionContext } from '../../kibana_react/public';
import type { DocTitleService, BreadcrumbService } from './services';
import { DevToolApp } from './dev_tool';
@@ -24,6 +30,7 @@ import { DevToolApp } from './dev_tool';
export interface AppServices {
docTitleService: DocTitleService;
breadcrumbService: BreadcrumbService;
+ executionContext: ExecutionContextStart;
}
interface DevToolsWrapperProps {
@@ -64,6 +71,11 @@ function DevToolsWrapper({
breadcrumbService.setBreadcrumbs(activeDevTool.title);
}, [activeDevTool, docTitleService, breadcrumbService]);
+ useExecutionContext(appServices.executionContext, {
+ type: 'application',
+ page: activeDevTool.id,
+ });
+
return (
diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts
index 1876bf278513e..ee729c8f4400c 100644
--- a/src/plugins/dev_tools/public/plugin.ts
+++ b/src/plugins/dev_tools/public/plugin.ts
@@ -61,7 +61,7 @@ export class DevToolsPlugin implements Plugin {
element.classList.add('devAppWrapper');
const [core] = await getStartServices();
- const { application, chrome } = core;
+ const { application, chrome, executionContext } = core;
this.docTitleService.setup(chrome.docTitle.change);
this.breadcrumbService.setup(chrome.setBreadcrumbs);
@@ -69,6 +69,7 @@ export class DevToolsPlugin implements Plugin {
const appServices = {
breadcrumbService: this.breadcrumbService,
docTitleService: this.docTitleService,
+ executionContext,
};
const { renderApp } = await import('./application');
diff --git a/src/plugins/discover/public/application/context/context_app.test.tsx b/src/plugins/discover/public/application/context/context_app.test.tsx
index c9089a6c1111c..cf1e7a98e01a3 100644
--- a/src/plugins/discover/public/application/context/context_app.test.tsx
+++ b/src/plugins/discover/public/application/context/context_app.test.tsx
@@ -46,6 +46,9 @@ describe('ContextApp test', () => {
toastNotifications: { addDanger: () => {} },
navigation: mockNavigationPlugin,
core: {
+ executionContext: {
+ set: jest.fn(),
+ },
notifications: { toasts: [] },
theme: { theme$: themeServiceMock.createStartContract().theme$ },
},
diff --git a/src/plugins/discover/public/application/context/context_app.tsx b/src/plugins/discover/public/application/context/context_app.tsx
index 8d2a6b2c04815..dcf1c6b11e68e 100644
--- a/src/plugins/discover/public/application/context/context_app.tsx
+++ b/src/plugins/discover/public/application/context/context_app.tsx
@@ -25,6 +25,7 @@ import { ContextAppContent } from './context_app_content';
import { SurrDocType } from './services/context';
import { DocViewFilterFn } from '../../services/doc_views/doc_views_types';
import { useDiscoverServices } from '../../utils/use_discover_services';
+import { useExecutionContext } from '../../../../kibana_react/public';
import { generateFilters } from '../../../../data/public';
const ContextAppContentMemoized = memo(ContextAppContent);
@@ -36,11 +37,17 @@ export interface ContextAppProps {
export const ContextApp = ({ indexPattern, anchorId }: ContextAppProps) => {
const services = useDiscoverServices();
- const { uiSettings, capabilities, indexPatterns, navigation, filterManager } = services;
+ const { uiSettings, capabilities, indexPatterns, navigation, filterManager, core } = services;
const isLegacy = useMemo(() => uiSettings.get(DOC_TABLE_LEGACY), [uiSettings]);
const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]);
+ useExecutionContext(core.executionContext, {
+ type: 'application',
+ page: 'context',
+ id: indexPattern.id || '',
+ });
+
/**
* Context app state
*/
diff --git a/src/plugins/discover/public/application/doc/single_doc_route.tsx b/src/plugins/discover/public/application/doc/single_doc_route.tsx
index d11c6bdca76a0..e2bdc6dc799e6 100644
--- a/src/plugins/discover/public/application/doc/single_doc_route.tsx
+++ b/src/plugins/discover/public/application/doc/single_doc_route.tsx
@@ -16,6 +16,7 @@ import { withQueryParams } from '../../utils/with_query_params';
import { useMainRouteBreadcrumb } from '../../utils/use_navigation_props';
import { Doc } from './components/doc';
import { useDiscoverServices } from '../../utils/use_discover_services';
+import { useExecutionContext } from '../../../../kibana_react/public';
export interface SingleDocRouteProps {
/**
@@ -31,11 +32,17 @@ export interface DocUrlParams {
const SingleDoc = ({ id }: SingleDocRouteProps) => {
const services = useDiscoverServices();
- const { chrome, timefilter } = services;
+ const { chrome, timefilter, core } = services;
const { indexPatternId, index } = useParams();
const breadcrumb = useMainRouteBreadcrumb();
+ useExecutionContext(core.executionContext, {
+ type: 'application',
+ page: 'single-doc',
+ id: indexPatternId,
+ });
+
useEffect(() => {
chrome.setBreadcrumbs([
...getRootBreadcrumbs(breadcrumb),
diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx
index d5950085b94c7..dcf229d36b1e0 100644
--- a/src/plugins/discover/public/application/main/discover_main_route.tsx
+++ b/src/plugins/discover/public/application/main/discover_main_route.tsx
@@ -24,6 +24,7 @@ import { LoadingIndicator } from '../../components/common/loading_indicator';
import { DiscoverError } from '../../components/common/error_alert';
import { useDiscoverServices } from '../../utils/use_discover_services';
import { getUrlTracker } from '../../kibana_services';
+import { useExecutionContext } from '../../../../kibana_react/public';
const DiscoverMainAppMemoized = memo(DiscoverMainApp);
@@ -50,6 +51,12 @@ export function DiscoverMainRoute() {
>([]);
const { id } = useParams();
+ useExecutionContext(core.executionContext, {
+ type: 'application',
+ page: 'app',
+ id: id || 'new',
+ });
+
const navigateToOverview = useCallback(() => {
core.application.navigateToApp('kibanaOverview', { path: '#' });
}, [core.application]);
diff --git a/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts b/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts
index 4c68eff54f579..b1f736fa4b224 100644
--- a/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts
+++ b/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts
@@ -117,7 +117,7 @@ describe('test fetchCharts', () => {
});
});
- test('fetch$ is called with execution context containing savedSearch id', async () => {
+ test('fetch$ is called with request specific execution context', async () => {
const fetch$Mock = jest.fn().mockReturnValue(of(requestResult));
savedSearchMockWithTimeField.searchSource.fetch$ = fetch$Mock;
@@ -126,10 +126,6 @@ describe('test fetchCharts', () => {
expect(fetch$Mock.mock.calls[0][0].executionContext).toMatchInlineSnapshot(`
Object {
"description": "fetch chart data and total hits",
- "id": "the-saved-search-id-with-timefield",
- "name": "discover",
- "type": "application",
- "url": "/",
}
`);
});
diff --git a/src/plugins/discover/public/application/main/utils/fetch_chart.ts b/src/plugins/discover/public/application/main/utils/fetch_chart.ts
index 00cb9c43caccf..1ea2594a89d97 100644
--- a/src/plugins/discover/public/application/main/utils/fetch_chart.ts
+++ b/src/plugins/discover/public/application/main/utils/fetch_chart.ts
@@ -40,11 +40,7 @@ export function fetchChart(
const chartAggConfigs = updateSearchSource(searchSource, interval, data);
const executionContext = {
- type: 'application',
- name: 'discover',
description: 'fetch chart data and total hits',
- url: window.location.pathname,
- id: savedSearch.id ?? '',
};
const fetch$ = searchSource
diff --git a/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts b/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts
index 000d3282c38b3..1e73f5de3a3f6 100644
--- a/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts
+++ b/src/plugins/discover/public/application/main/utils/fetch_documents.test.ts
@@ -57,10 +57,6 @@ describe('test fetchDocuments', () => {
expect(fetch$Mock.mock.calls[0][0].executionContext).toMatchInlineSnapshot(`
Object {
"description": "fetch total hits",
- "id": "the-saved-search-id",
- "name": "discover",
- "type": "application",
- "url": "/",
}
`);
});
diff --git a/src/plugins/discover/public/application/main/utils/fetch_documents.ts b/src/plugins/discover/public/application/main/utils/fetch_documents.ts
index dbf972265547e..8338839e8b0ac 100644
--- a/src/plugins/discover/public/application/main/utils/fetch_documents.ts
+++ b/src/plugins/discover/public/application/main/utils/fetch_documents.ts
@@ -32,11 +32,7 @@ export const fetchDocuments = (
}
const executionContext = {
- type: 'application',
- name: 'discover',
description: 'fetch documents',
- url: window.location.pathname,
- id: savedSearch.id ?? '',
};
const fetch$ = searchSource
diff --git a/src/plugins/discover/public/application/main/utils/fetch_total_hits.test.ts b/src/plugins/discover/public/application/main/utils/fetch_total_hits.test.ts
index ba7b6a765aa2e..a5485c1a2e2e9 100644
--- a/src/plugins/discover/public/application/main/utils/fetch_total_hits.test.ts
+++ b/src/plugins/discover/public/application/main/utils/fetch_total_hits.test.ts
@@ -51,10 +51,6 @@ describe('test fetchTotalHits', () => {
expect(fetch$Mock.mock.calls[0][0].executionContext).toMatchInlineSnapshot(`
Object {
"description": "fetch total hits",
- "id": "the-saved-search-id",
- "name": "discover",
- "type": "application",
- "url": "/",
}
`);
});
diff --git a/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts b/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts
index af2d55e23cf32..e696570f05cf0 100644
--- a/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts
+++ b/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts
@@ -30,11 +30,7 @@ export function fetchTotalHits(
}
const executionContext = {
- type: 'application',
- name: 'discover',
description: 'fetch total hits',
- url: window.location.pathname,
- id: savedSearch.id ?? '',
};
const fetch$ = searchSource
diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts
index baed839d41123..94e8b505f1dd2 100644
--- a/src/plugins/kibana_react/public/index.ts
+++ b/src/plugins/kibana_react/public/index.ts
@@ -39,6 +39,8 @@ export { createReactOverlays } from './overlays';
export { useUiSetting, useUiSetting$ } from './ui_settings';
+export { useExecutionContext } from './use_execution_context';
+
export type { TableListViewProps, TableListViewState } from './table_list_view';
export { TableListView } from './table_list_view';
diff --git a/src/plugins/kibana_react/public/use_execution_context/index.ts b/src/plugins/kibana_react/public/use_execution_context/index.ts
new file mode 100644
index 0000000000000..f36d094eb86d4
--- /dev/null
+++ b/src/plugins/kibana_react/public/use_execution_context/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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 { useExecutionContext } from './use_execution_context';
diff --git a/src/plugins/kibana_react/public/use_execution_context/use_execution_context.ts b/src/plugins/kibana_react/public/use_execution_context/use_execution_context.ts
new file mode 100644
index 0000000000000..e2c538056153c
--- /dev/null
+++ b/src/plugins/kibana_react/public/use_execution_context/use_execution_context.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { KibanaExecutionContext, CoreStart } from 'kibana/public';
+import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect';
+
+/**
+ * Set and clean up application level execution context
+ * @param executionContext
+ * @param context
+ */
+export function useExecutionContext(
+ executionContext: CoreStart['executionContext'],
+ context: KibanaExecutionContext
+) {
+ useDeepCompareEffect(() => {
+ executionContext.set(context);
+
+ return () => {
+ executionContext.clear();
+ };
+ }, [context]);
+}
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx
index efc3bbf8314f8..3d3c98ce4aaea 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx
@@ -39,7 +39,7 @@ import {
ExpressionAstExpression,
} from '../../../../plugins/expressions/public';
import { Vis, SerializedVis } from '../vis';
-import { getExpressions, getTheme, getUiActions } from '../services';
+import { getExecutionContext, getExpressions, getTheme, getUiActions } from '../services';
import { VIS_EVENT_TO_TRIGGER } from './events';
import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory';
import { getSavedVisualization } from '../utils/saved_visualize_utils';
@@ -398,20 +398,18 @@ export class VisualizeEmbeddable
};
private async updateHandler() {
- const parentContext = this.parent?.getInput().executionContext;
+ const parentContext = this.parent?.getInput().executionContext || getExecutionContext().get();
const child: KibanaExecutionContext = {
type: 'visualization',
name: this.vis.type.name,
- id: this.vis.id ?? 'an_unsaved_vis',
+ id: this.vis.id ?? 'new',
description: this.vis.title || this.input.title || this.vis.type.name,
url: this.output.editUrl,
};
- const context = parentContext
- ? {
- ...parentContext,
- child,
- }
- : child;
+ const context = {
+ ...parentContext,
+ child,
+ };
const expressionParams: IExpressionLoaderParams = {
searchContext: {
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index 92bcf1dfe6a96..88b9d35d5255f 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -36,6 +36,7 @@ import {
setDocLinks,
setSpaces,
setTheme,
+ setExecutionContext,
} from './services';
import {
createVisEmbeddableFromObject,
@@ -372,6 +373,7 @@ export class VisualizationsPlugin
setTimeFilter(data.query.timefilter.timefilter);
setAggs(data.search.aggs);
setOverlays(core.overlays);
+ setExecutionContext(core.executionContext);
setChrome(core.chrome);
if (spaces) {
diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts
index 37aea45fa3f58..8564c8225f1a7 100644
--- a/src/plugins/visualizations/public/services.ts
+++ b/src/plugins/visualizations/public/services.ts
@@ -16,6 +16,7 @@ import type {
SavedObjectsStart,
DocLinksStart,
ThemeServiceStart,
+ ExecutionContextSetup,
} from '../../../core/public';
import type { TypesStart } from './vis_types';
import { createGetterSetter } from '../../../plugins/kibana_utils/public';
@@ -65,4 +66,7 @@ export const [getOverlays, setOverlays] = createGetterSetter('Over
export const [getChrome, setChrome] = createGetterSetter('Chrome');
+export const [getExecutionContext, setExecutionContext] =
+ createGetterSetter('ExecutionContext');
+
export const [getSpaces, setSpaces] = createGetterSetter('Spaces', false);
diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx
index 45241ec501084..c281b211768a1 100644
--- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx
+++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx
@@ -11,7 +11,7 @@ import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { EventEmitter } from 'events';
-import { useKibana } from '../../../../kibana_react/public';
+import { useExecutionContext, useKibana } from '../../../../kibana_react/public';
import {
useChromeVisibility,
useSavedVisInstance,
@@ -41,6 +41,14 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
originatingApp,
visualizationIdFromUrl
);
+
+ const editorName = savedVisInstance?.vis.type.title.toLowerCase().replace(' ', '_') || '';
+ useExecutionContext(services.executionContext, {
+ type: 'application',
+ page: `editor${editorName ? `:${editorName}` : ''}`,
+ id: visualizationIdFromUrl || 'new',
+ });
+
const { appState, hasUnappliedChanges } = useVisualizeAppState(
services,
eventEmitter,
diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx
index cf219b1cda117..a180cf78feeb2 100644
--- a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx
+++ b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx
@@ -21,7 +21,7 @@ import { findListItems } from '../../utils/saved_visualize_utils';
import { showNewVisModal } from '../../wizard';
import { getTypes } from '../../services';
import { SavedObjectsFindOptionsReference } from '../../../../../core/public';
-import { useKibana, TableListView } from '../../../../kibana_react/public';
+import { useKibana, TableListView, useExecutionContext } from '../../../../kibana_react/public';
import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../visualizations/public';
import { VisualizeServices } from '../types';
import { VisualizeConstants } from '../../../common/constants';
@@ -31,6 +31,7 @@ export const VisualizeListing = () => {
const {
services: {
application,
+ executionContext,
chrome,
history,
toastNotifications,
@@ -49,6 +50,11 @@ export const VisualizeListing = () => {
const closeNewVisModal = useRef(() => {});
const listingLimit = savedObjectsPublic.settings.getListingLimit();
+ useExecutionContext(executionContext, {
+ type: 'application',
+ page: 'list',
+ });
+
useEffect(() => {
if (pathname === '/new') {
// In case the user navigated to the page via the /visualize/new URL we start the dialog immediately
diff --git a/x-pack/plugins/fleet/.storybook/context/execution_context.ts b/x-pack/plugins/fleet/.storybook/context/execution_context.ts
new file mode 100644
index 0000000000000..d3a15e200129b
--- /dev/null
+++ b/x-pack/plugins/fleet/.storybook/context/execution_context.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import type { ExecutionContextSetup } from 'kibana/public';
+import { of } from 'rxjs';
+
+export const getExecutionContext = () => {
+ const exec: ExecutionContextSetup = {
+ context$: of({}),
+ get: () => {
+ return {};
+ },
+ clear: () => {},
+ set: (context: Record) => {},
+ getAsLabels: () => {
+ return {};
+ },
+ withGlobalContext: () => {
+ return {};
+ },
+ };
+
+ return exec;
+};
diff --git a/x-pack/plugins/fleet/.storybook/context/index.tsx b/x-pack/plugins/fleet/.storybook/context/index.tsx
index eb19a1145ba75..fbcbd4fd3a081 100644
--- a/x-pack/plugins/fleet/.storybook/context/index.tsx
+++ b/x-pack/plugins/fleet/.storybook/context/index.tsx
@@ -31,6 +31,7 @@ import { stubbedStartServices } from './stubs';
import { getDocLinks } from './doc_links';
import { getCloud } from './cloud';
import { getShare } from './share';
+import { getExecutionContext } from './execution_context';
// TODO: clintandrewhall - this is not ideal, or complete. The root context of Fleet applications
// requires full start contracts of its dependencies. As a result, we have to mock all of those contracts
@@ -52,6 +53,7 @@ export const StorybookContext: React.FC<{ storyContext?: StoryContext }> = ({
() => ({
...stubbedStartServices,
application: getApplication(),
+ executionContext: getExecutionContext(),
chrome: getChrome(),
cloud: {
...getCloud({ isCloudEnabled }),
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx
index 3a1c2dd36812f..89f7d4795429a 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx
@@ -25,11 +25,13 @@ import { init as initHttp } from '../public/application/services/http';
import { init as initUiMetric } from '../public/application/services/ui_metric';
import { KibanaContextProvider } from '../public/shared_imports';
import { PolicyListContextProvider } from '../public/application/sections/policy_list/policy_list_context';
+import { executionContextServiceMock } from 'src/core/public/execution_context/execution_context_service.mock';
initHttp(
new HttpService().setup({
injectedMetadata: injectedMetadataServiceMock.createSetupContract(),
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
+ executionContext: executionContextServiceMock.createSetupContract(),
})
);
initUiMetric(usageCollectionPluginMock.createSetupContract());
diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx
index 3660c3d3db0cb..5ef9e05cf590b 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.tsx
@@ -13,7 +13,7 @@ import {
createKbnUrlStateStorage,
withNotifyOnErrors,
} from '../../../../../src/plugins/kibana_utils/public';
-import { useKibana } from '../../../../../src/plugins/kibana_react/public';
+import { useExecutionContext, useKibana } from '../../../../../src/plugins/kibana_react/public';
import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public';
import { syncQueryStateWithUrl } from '../../../../../src/plugins/data/public';
import { LensAppProps, LensAppServices } from './types';
@@ -71,6 +71,7 @@ export function App({
getOriginatingAppName,
spaces,
http,
+ executionContext,
// Temporarily required until the 'by value' paradigm is default.
dashboardFeatureFlag,
} = lensAppServices;
@@ -111,6 +112,7 @@ export function App({
undefined
);
const [isGoBackToVizEditorModalVisible, setIsGoBackToVizEditorModalVisible] = useState(false);
+ const savedObjectId = (initialInput as LensByReferenceInput)?.savedObjectId;
useEffect(() => {
if (currentDoc) {
@@ -122,6 +124,12 @@ export function App({
setIndicateNoData(true);
}, [setIndicateNoData]);
+ useExecutionContext(executionContext, {
+ type: 'application',
+ id: savedObjectId || 'new',
+ page: 'editor',
+ });
+
useEffect(() => {
if (indicateNoData) {
setIndicateNoData(false);
@@ -132,11 +140,9 @@ export function App({
() =>
Boolean(
// Temporarily required until the 'by value' paradigm is default.
- dashboardFeatureFlag.allowByValueEmbeddables &&
- isLinkedToOriginatingApp &&
- !(initialInput as LensByReferenceInput)?.savedObjectId
+ dashboardFeatureFlag.allowByValueEmbeddables && isLinkedToOriginatingApp && !savedObjectId
),
- [dashboardFeatureFlag.allowByValueEmbeddables, isLinkedToOriginatingApp, initialInput]
+ [dashboardFeatureFlag.allowByValueEmbeddables, isLinkedToOriginatingApp, savedObjectId]
);
useEffect(() => {
diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
index 28db5e9f4c43a..6f2fd4e8026ad 100644
--- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
@@ -77,6 +77,7 @@ export async function getLensServices(
usageCollection,
savedObjectsTagging,
attributeService,
+ executionContext: coreStart.executionContext,
http: coreStart.http,
chrome: coreStart.chrome,
overlays: coreStart.overlays,
diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts
index bdd7bebd991e7..25fff038c4814 100644
--- a/x-pack/plugins/lens/public/app_plugin/types.ts
+++ b/x-pack/plugins/lens/public/app_plugin/types.ts
@@ -12,6 +12,7 @@ import type {
ApplicationStart,
AppMountParameters,
ChromeStart,
+ ExecutionContextStart,
HttpStart,
IUiSettingsClient,
NotificationsStart,
@@ -114,6 +115,7 @@ export interface HistoryLocationState {
export interface LensAppServices {
http: HttpStart;
+ executionContext: ExecutionContextStart;
chrome: ChromeStart;
overlays: OverlayStart;
storage: IStorageWrapper;
diff --git a/x-pack/plugins/lens/public/mocks/services_mock.tsx b/x-pack/plugins/lens/public/mocks/services_mock.tsx
index 9fa6d61370a17..6681811744da4 100644
--- a/x-pack/plugins/lens/public/mocks/services_mock.tsx
+++ b/x-pack/plugins/lens/public/mocks/services_mock.tsx
@@ -112,6 +112,7 @@ export function makeDefaultServices(
chrome: core.chrome,
overlays: core.overlays,
uiSettings: core.uiSettings,
+ executionContext: core.executionContext,
navigation: navigationStartMock,
notifications: core.notifications,
attributeService: makeAttributeService(),
diff --git a/x-pack/plugins/maps/common/execution_context.test.ts b/x-pack/plugins/maps/common/execution_context.test.ts
new file mode 100644
index 0000000000000..25e3813a7e8f0
--- /dev/null
+++ b/x-pack/plugins/maps/common/execution_context.test.ts
@@ -0,0 +1,36 @@
+/*
+ * 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 { makeExecutionContext } from './execution_context';
+
+describe('makeExecutionContext', () => {
+ test('returns basic fields if nothing is provided', () => {
+ const context = makeExecutionContext({});
+ expect(context).toStrictEqual({
+ name: 'maps',
+ type: 'application',
+ });
+ });
+
+ test('merges in context', () => {
+ const context = makeExecutionContext({ id: '123' });
+ expect(context).toStrictEqual({
+ name: 'maps',
+ type: 'application',
+ id: '123',
+ });
+ });
+
+ test('omits undefined values', () => {
+ const context = makeExecutionContext({ id: '123', description: undefined });
+ expect(context).toStrictEqual({
+ name: 'maps',
+ type: 'application',
+ id: '123',
+ });
+ });
+});
diff --git a/x-pack/plugins/maps/common/execution_context.ts b/x-pack/plugins/maps/common/execution_context.ts
index 23de29cfa8cd7..4a11eb5d89029 100644
--- a/x-pack/plugins/maps/common/execution_context.ts
+++ b/x-pack/plugins/maps/common/execution_context.ts
@@ -5,14 +5,16 @@
* 2.0.
*/
+import { isUndefined, omitBy } from 'lodash';
import { APP_ID } from './constants';
-export function makeExecutionContext(id: string, url: string, description?: string) {
- return {
- name: APP_ID,
- type: 'application',
- id,
- description: description || '',
- url,
- };
+export function makeExecutionContext(context: { id?: string; url?: string; description?: string }) {
+ return omitBy(
+ {
+ name: APP_ID,
+ type: 'application',
+ ...context,
+ },
+ isUndefined
+ );
}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
index e2f9959b25d31..a26bd341613b2 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
@@ -5,8 +5,14 @@
* 2.0.
*/
+import { coreMock } from '../../../../../../../src/core/public/mocks';
import { MapExtent, VectorSourceRequestMeta } from '../../../../common/descriptor_types';
-import { getHttp, getIndexPatternService, getSearchService } from '../../../kibana_services';
+import {
+ getExecutionContext,
+ getHttp,
+ getIndexPatternService,
+ getSearchService,
+} from '../../../kibana_services';
import { ESGeoGridSource } from './es_geo_grid_source';
import {
ES_GEO_FIELD_TYPE,
@@ -129,6 +135,13 @@ describe('ESGeoGridSource', () => {
},
},
});
+
+ const coreStartMock = coreMock.createStart();
+ coreStartMock.executionContext.get.mockReturnValue({
+ name: 'some-app',
+ });
+ // @ts-expect-error
+ getExecutionContext.mockReturnValue(coreStartMock.executionContext);
});
afterEach(() => {
diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts
index 54e4964b35f03..d8197902c73ac 100644
--- a/x-pack/plugins/maps/public/kibana_services.ts
+++ b/x-pack/plugins/maps/public/kibana_services.ts
@@ -33,6 +33,7 @@ export const getUiSettings = () => coreStart.uiSettings;
export const getIsDarkMode = () => getUiSettings().get('theme:darkMode', false);
export const getIndexPatternSelectComponent = () => pluginsStart.data.ui.IndexPatternSelect;
export const getHttp = () => coreStart.http;
+export const getExecutionContext = () => coreStart.executionContext;
export const getTimeFilter = () => pluginsStart.data.query.timefilter.timefilter;
export const getToasts = () => coreStart.notifications.toasts;
export const getSavedObjectsClient = () => coreStart.savedObjects.client;
diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx
index 571cba64a06c4..dab284b0b71e4 100644
--- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx
+++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx
@@ -17,6 +17,7 @@ import {
getMapsCapabilities,
getToasts,
getCoreChrome,
+ getExecutionContext,
getNavigateToApp,
getSavedObjectsClient,
getSavedObjectsTagging,
@@ -121,6 +122,12 @@ async function deleteMaps(items: object[]) {
}
export function MapsListView() {
+ getExecutionContext().set({
+ type: 'application',
+ page: 'list',
+ id: '',
+ });
+
const isReadOnly = !getMapsCapabilities().save;
getCoreChrome().docTitle.change(getAppTitle());
diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
index 9aede248e1877..a341246f748f3 100644
--- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
+++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
@@ -16,6 +16,7 @@ import { type Filter, FilterStateStore } from '@kbn/es-query';
import type { Query, TimeRange, DataView } from 'src/plugins/data/common';
import {
getData,
+ getExecutionContext,
getCoreChrome,
getMapsCapabilities,
getNavigation,
@@ -115,6 +116,12 @@ export class MapApp extends React.Component {
componentDidMount() {
this._isMounted = true;
+ getExecutionContext().set({
+ type: 'application',
+ page: 'editor',
+ id: this.props.savedMap.getSavedObjectId() || 'new',
+ });
+
this._autoRefreshSubscription = getTimeFilter()
.getAutoRefreshFetch$()
.pipe(
diff --git a/x-pack/plugins/maps/public/util.test.js b/x-pack/plugins/maps/public/util.test.js
index d8861063fc637..7fc88578b378a 100644
--- a/x-pack/plugins/maps/public/util.test.js
+++ b/x-pack/plugins/maps/public/util.test.js
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { getGlyphUrl } from './util';
+import { getGlyphUrl, makePublicExecutionContext } from './util';
const MOCK_EMS_SETTINGS = {
isEMSEnabled: () => true,
@@ -62,3 +62,55 @@ describe('getGlyphUrl', () => {
});
});
});
+
+describe('makePublicExecutionContext', () => {
+ let injectedContext = {};
+ beforeAll(() => {
+ require('./kibana_services').getExecutionContext = () => ({
+ get: () => injectedContext,
+ });
+ });
+
+ test('creates basic context when no top level context is provided', () => {
+ const context = makePublicExecutionContext('test');
+ expect(context).toStrictEqual({
+ description: 'test',
+ name: 'maps',
+ type: 'application',
+ url: '/',
+ });
+ });
+
+ test('merges with top level context if its from the same app', () => {
+ injectedContext = {
+ name: 'maps',
+ id: '1234',
+ };
+ const context = makePublicExecutionContext('test');
+ expect(context).toStrictEqual({
+ description: 'test',
+ name: 'maps',
+ type: 'application',
+ url: '/',
+ id: '1234',
+ });
+ });
+
+ test('nests inside top level context if its from a different app', () => {
+ injectedContext = {
+ name: 'other-app',
+ id: '1234',
+ };
+ const context = makePublicExecutionContext('test');
+ expect(context).toStrictEqual({
+ name: 'other-app',
+ id: '1234',
+ child: {
+ description: 'test',
+ type: 'application',
+ name: 'maps',
+ url: '/',
+ },
+ });
+ });
+});
diff --git a/x-pack/plugins/maps/public/util.ts b/x-pack/plugins/maps/public/util.ts
index 4adb8b35bfcea..66244ea5f6768 100644
--- a/x-pack/plugins/maps/public/util.ts
+++ b/x-pack/plugins/maps/public/util.ts
@@ -8,7 +8,13 @@
import { EMSClient, FileLayer, TMSService } from '@elastic/ems-client';
import type { KibanaExecutionContext } from 'kibana/public';
import { FONTS_API_PATH } from '../common/constants';
-import { getHttp, getTilemap, getEMSSettings, getMapsEmsStart } from './kibana_services';
+import {
+ getHttp,
+ getTilemap,
+ getEMSSettings,
+ getMapsEmsStart,
+ getExecutionContext,
+} from './kibana_services';
import { getLicenseId } from './licensed_features';
import { makeExecutionContext } from '../common/execution_context';
@@ -67,9 +73,21 @@ export function isRetina(): boolean {
return window.devicePixelRatio === 2;
}
-export function makePublicExecutionContext(
- id: string,
- description?: string
-): KibanaExecutionContext {
- return makeExecutionContext(id, window.location.pathname, description);
+export function makePublicExecutionContext(description: string): KibanaExecutionContext {
+ const topLevelContext = getExecutionContext().get();
+ const context = makeExecutionContext({
+ url: window.location.pathname,
+ description,
+ });
+
+ // Distinguish between running in maps app vs. embedded
+ return topLevelContext.name !== undefined && topLevelContext.name !== context.name
+ ? {
+ ...topLevelContext,
+ child: context,
+ }
+ : {
+ ...topLevelContext,
+ ...context,
+ };
}
diff --git a/x-pack/plugins/maps/server/mvt/get_grid_tile.ts b/x-pack/plugins/maps/server/mvt/get_grid_tile.ts
index 193a3d74e2dca..28effa5eabfba 100644
--- a/x-pack/plugins/maps/server/mvt/get_grid_tile.ts
+++ b/x-pack/plugins/maps/server/mvt/get_grid_tile.ts
@@ -56,7 +56,10 @@ export async function getEsGridTile({
};
const tile = await core.executionContext.withContext(
- makeExecutionContext('mvt:get_grid_tile', url),
+ makeExecutionContext({
+ description: 'mvt:get_grid_tile',
+ url,
+ }),
async () => {
return await context.core.elasticsearch.client.asCurrentUser.transport.request(
{
diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts
index 2c8b6dd4b113d..7e9bc01c5c317 100644
--- a/x-pack/plugins/maps/server/mvt/get_tile.ts
+++ b/x-pack/plugins/maps/server/mvt/get_tile.ts
@@ -57,7 +57,10 @@ export async function getEsTile({
};
const tile = await core.executionContext.withContext(
- makeExecutionContext('mvt:get_tile', url),
+ makeExecutionContext({
+ description: 'mvt:get_tile',
+ url,
+ }),
async () => {
return await context.core.elasticsearch.client.asCurrentUser.transport.request(
{