Instrument vis_type_vislib, lens and vis_type_timeseries with execution context service (#105206) (#106117)

Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com>

Co-authored-by: Mikhail Shustov <restrry@gmail.com>
Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2021-07-19 15:19:19 -04:00 committed by GitHub
parent dde965559d
commit 50d6106e56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 357 additions and 42 deletions

View file

@ -16,4 +16,5 @@ export interface IExecutionContextContainer
| Property | Type | Description |
| --- | --- | --- |
| [toHeader](./kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md) | <code>() =&gt; Record&lt;string, string&gt;</code> | |
| [toJSON](./kibana-plugin-core-public.iexecutioncontextcontainer.tojson.md) | <code>() =&gt; Readonly&lt;KibanaExecutionContext&gt;</code> | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md) &gt; [toJSON](./kibana-plugin-core-public.iexecutioncontextcontainer.tojson.md)
## IExecutionContextContainer.toJSON property
<b>Signature:</b>
```typescript
toJSON: () => Readonly<KibanaExecutionContext>;
```

View file

@ -4,7 +4,7 @@
## KibanaExecutionContext.id property
unique value to indentify find the source
unique value to identify the source
<b>Signature:</b>

View file

@ -16,7 +16,7 @@ export interface KibanaExecutionContext
| Property | Type | Description |
| --- | --- | --- |
| [description](./kibana-plugin-core-public.kibanaexecutioncontext.description.md) | <code>string</code> | human readable description. For example, a vis title, action name |
| [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md) | <code>string</code> | unique value to indentify find the source |
| [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md) | <code>string</code> | unique value to identify the source |
| [name](./kibana-plugin-core-public.kibanaexecutioncontext.name.md) | <code>string</code> | public name of a user-facing feature |
| [type](./kibana-plugin-core-public.kibanaexecutioncontext.type.md) | <code>string</code> | Kibana application initated an operation. Can be narrowed to an enum later. |
| [url](./kibana-plugin-core-public.kibanaexecutioncontext.url.md) | <code>string</code> | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url |

View file

@ -4,7 +4,7 @@
## KibanaExecutionContext.id property
unique value to indentify find the source
unique value to identify the source
<b>Signature:</b>

View file

@ -16,7 +16,7 @@ export interface KibanaExecutionContext
| Property | Type | Description |
| --- | --- | --- |
| [description](./kibana-plugin-core-server.kibanaexecutioncontext.description.md) | <code>string</code> | human readable description. For example, a vis title, action name |
| [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) | <code>string</code> | unique value to indentify find the source |
| [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) | <code>string</code> | unique value to identify the source |
| [name](./kibana-plugin-core-server.kibanaexecutioncontext.name.md) | <code>string</code> | public name of a user-facing feature |
| [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md) | <code>string</code> | Kibana application initated an operation. Can be narrowed to an enum later. |
| [url](./kibana-plugin-core-server.kibanaexecutioncontext.url.md) | <code>string</code> | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) &gt; [executionContext](./kibana-plugin-plugins-data-public.isearchoptions.executioncontext.md)
## ISearchOptions.executionContext property
<b>Signature:</b>
```typescript
executionContext?: KibanaExecutionContext;
```

View file

@ -15,6 +15,7 @@ export interface ISearchOptions
| Property | Type | Description |
| --- | --- | --- |
| [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | <code>AbortSignal</code> | An <code>AbortSignal</code> that allows the caller of <code>search</code> to abort a search request. |
| [executionContext](./kibana-plugin-plugins-data-public.isearchoptions.executioncontext.md) | <code>KibanaExecutionContext</code> | |
| [indexPattern](./kibana-plugin-plugins-data-public.isearchoptions.indexpattern.md) | <code>IndexPattern</code> | Index pattern reference is used for better error messages |
| [inspector](./kibana-plugin-plugins-data-public.isearchoptions.inspector.md) | <code>IInspectorInfo</code> | Inspector integration options |
| [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) | <code>boolean</code> | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) &gt; [executionContext](./kibana-plugin-plugins-data-server.isearchoptions.executioncontext.md)
## ISearchOptions.executionContext property
<b>Signature:</b>
```typescript
executionContext?: KibanaExecutionContext;
```

View file

@ -15,6 +15,7 @@ export interface ISearchOptions
| Property | Type | Description |
| --- | --- | --- |
| [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | <code>AbortSignal</code> | An <code>AbortSignal</code> that allows the caller of <code>search</code> to abort a search request. |
| [executionContext](./kibana-plugin-plugins-data-server.isearchoptions.executioncontext.md) | <code>KibanaExecutionContext</code> | |
| [indexPattern](./kibana-plugin-plugins-data-server.isearchoptions.indexpattern.md) | <code>IndexPattern</code> | Index pattern reference is used for better error messages |
| [inspector](./kibana-plugin-plugins-data-server.isearchoptions.inspector.md) | <code>IInspectorInfo</code> | Inspector integration options |
| [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) | <code>boolean</code> | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) &gt; [ExecutionContext](./kibana-plugin-plugins-expressions-public.executioncontext.md) &gt; [getExecutionContext](./kibana-plugin-plugins-expressions-public.executioncontext.getexecutioncontext.md)
## ExecutionContext.getExecutionContext property
Contains the meta-data about the source of the expression.
<b>Signature:</b>
```typescript
getExecutionContext: () => IExecutionContextContainer | undefined;
```

View file

@ -17,6 +17,7 @@ export interface ExecutionContext<InspectorAdapters extends Adapters = Adapters,
| Property | Type | Description |
| --- | --- | --- |
| [abortSignal](./kibana-plugin-plugins-expressions-public.executioncontext.abortsignal.md) | <code>AbortSignal</code> | Adds ability to abort current execution. |
| [getExecutionContext](./kibana-plugin-plugins-expressions-public.executioncontext.getexecutioncontext.md) | <code>() =&gt; IExecutionContextContainer &#124; undefined</code> | Contains the meta-data about the source of the expression. |
| [getKibanaRequest](./kibana-plugin-plugins-expressions-public.executioncontext.getkibanarequest.md) | <code>() =&gt; KibanaRequest</code> | Getter to retrieve the <code>KibanaRequest</code> object inside an expression function. Useful for functions which are running on the server and need to perform operations that are scoped to a specific user. |
| [getSearchContext](./kibana-plugin-plugins-expressions-public.executioncontext.getsearchcontext.md) | <code>() =&gt; ExecutionContextSearch</code> | Get search context of the expression. |
| [getSearchSessionId](./kibana-plugin-plugins-expressions-public.executioncontext.getsearchsessionid.md) | <code>() =&gt; string &#124; undefined</code> | Search context in which expression should operate. |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) &gt; [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) &gt; [executionContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.executioncontext.md)
## IExpressionLoaderParams.executionContext property
<b>Signature:</b>
```typescript
executionContext?: IExecutionContextContainer;
```

View file

@ -19,6 +19,7 @@ export interface IExpressionLoaderParams
| [customRenderers](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.customrenderers.md) | <code>[]</code> | |
| [debug](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.debug.md) | <code>boolean</code> | |
| [disableCaching](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.disablecaching.md) | <code>boolean</code> | |
| [executionContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.executioncontext.md) | <code>IExecutionContextContainer</code> | |
| [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md) | <code>ExpressionRenderHandlerParams['hasCompatibleActions']</code> | |
| [inspectorAdapters](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.inspectoradapters.md) | <code>Adapters</code> | |
| [onRenderError](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.onrendererror.md) | <code>RenderErrorHandlerFnType</code> | |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) &gt; [ExecutionContext](./kibana-plugin-plugins-expressions-server.executioncontext.md) &gt; [getExecutionContext](./kibana-plugin-plugins-expressions-server.executioncontext.getexecutioncontext.md)
## ExecutionContext.getExecutionContext property
Contains the meta-data about the source of the expression.
<b>Signature:</b>
```typescript
getExecutionContext: () => IExecutionContextContainer | undefined;
```

View file

@ -17,6 +17,7 @@ export interface ExecutionContext<InspectorAdapters extends Adapters = Adapters,
| Property | Type | Description |
| --- | --- | --- |
| [abortSignal](./kibana-plugin-plugins-expressions-server.executioncontext.abortsignal.md) | <code>AbortSignal</code> | Adds ability to abort current execution. |
| [getExecutionContext](./kibana-plugin-plugins-expressions-server.executioncontext.getexecutioncontext.md) | <code>() =&gt; IExecutionContextContainer &#124; undefined</code> | Contains the meta-data about the source of the expression. |
| [getKibanaRequest](./kibana-plugin-plugins-expressions-server.executioncontext.getkibanarequest.md) | <code>() =&gt; KibanaRequest</code> | Getter to retrieve the <code>KibanaRequest</code> object inside an expression function. Useful for functions which are running on the server and need to perform operations that are scoped to a specific user. |
| [getSearchContext](./kibana-plugin-plugins-expressions-server.executioncontext.getsearchcontext.md) | <code>() =&gt; ExecutionContextSearch</code> | Get search context of the expression. |
| [getSearchSessionId](./kibana-plugin-plugins-expressions-server.executioncontext.getsearchsessionid.md) | <code>() =&gt; string &#124; undefined</code> | Search context in which expression should operate. |

View file

@ -30,6 +30,7 @@ function enforceMaxLength(header: string): string {
*/
export interface IExecutionContextContainer {
toHeader: () => Record<string, string>;
toJSON: () => Readonly<KibanaExecutionContext>;
}
export class ExecutionContextContainer implements IExecutionContextContainer {
@ -42,7 +43,12 @@ export class ExecutionContextContainer implements IExecutionContextContainer {
// escape content as the description property might contain non-ASCII symbols
return enforceMaxLength(encodeURIComponent(value));
}
toHeader() {
return { [BAGGAGE_HEADER]: this.toString() };
}
toJSON() {
return this.#context;
}
}

View file

@ -13,6 +13,7 @@ import type { ExecutionContextContainer } from './execution_context_container';
const createContainerMock = () => {
const mock: jest.Mocked<PublicMethodsOf<ExecutionContextContainer>> = {
toHeader: jest.fn(),
toJSON: jest.fn(),
};
return mock;
};

View file

@ -186,16 +186,14 @@ export type {
export type { DeprecationsServiceStart, ResolveDeprecationResponse } from './deprecations';
export type {
IExecutionContextContainer,
ExecutionContextServiceStart,
KibanaExecutionContext,
} from './execution_context';
export type { IExecutionContextContainer, ExecutionContextServiceStart } from './execution_context';
export type { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types';
export { URL_MAX_LENGTH } from './core_app';
export type { KibanaExecutionContext } from './execution_context';
/**
* Core services exposed to the `Plugin` setup lifecycle
*

View file

@ -900,6 +900,8 @@ export interface IBasePath {
export interface IExecutionContextContainer {
// (undocumented)
toHeader: () => Record<string, string>;
// (undocumented)
toJSON: () => Readonly<KibanaExecutionContext>;
}
// @public

View file

@ -10,7 +10,11 @@ import { Buffer } from 'buffer';
import { Readable } from 'stream';
import { RequestEvent, errors } from '@elastic/elasticsearch';
import { TransportRequestParams, RequestBody } from '@elastic/elasticsearch/lib/Transport';
import type {
TransportRequestOptions,
TransportRequestParams,
RequestBody,
} from '@elastic/elasticsearch/lib/Transport';
import { parseClientOptionsMock, ClientMock } from './configure_client.test.mocks';
import { loggingSystemMock } from '../../logging/logging_system.mock';
@ -39,12 +43,14 @@ const createApiResponse = <T>({
headers = {},
warnings = [],
params,
requestOptions = {},
}: {
body: T;
statusCode?: number;
headers?: Record<string, string>;
warnings?: string[];
params?: TransportRequestParams;
requestOptions?: TransportRequestOptions;
}): RequestEvent<T> => {
return {
body,
@ -54,6 +60,7 @@ const createApiResponse = <T>({
meta: {
request: {
params: params!,
options: requestOptions,
} as any,
} as any,
};
@ -146,6 +153,7 @@ describe('configureClient', () => {
"200
GET /foo?hello=dolly
{\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}",
undefined,
],
]
`);
@ -170,6 +178,7 @@ describe('configureClient', () => {
"200
GET /foo?hello=dolly
{\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}",
undefined,
],
]
`);
@ -196,6 +205,7 @@ describe('configureClient', () => {
"200
GET /foo?hello=dolly
[buffer]",
undefined,
],
]
`);
@ -222,6 +232,7 @@ describe('configureClient', () => {
"200
GET /foo?hello=dolly
[stream]",
undefined,
],
]
`);
@ -238,6 +249,7 @@ describe('configureClient', () => {
Array [
"200
GET /foo?hello=dolly",
undefined,
],
]
`);
@ -263,6 +275,7 @@ describe('configureClient', () => {
Array [
"200
GET /foo?city=M%C3%BCnich",
undefined,
],
]
`);
@ -298,6 +311,7 @@ describe('configureClient', () => {
"500
GET /foo?hello=dolly
{\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}} [internal server error]: internal server error",
undefined,
],
]
`);
@ -313,6 +327,7 @@ describe('configureClient', () => {
Array [
Array [
"[TimeoutError]: message",
undefined,
],
]
`);
@ -343,6 +358,7 @@ describe('configureClient', () => {
Array [
"400
GET /_path?hello=dolly [illegal_argument_exception]: request [/_path] contains unrecognized parameter: [name]",
undefined,
],
]
`);
@ -369,6 +385,7 @@ describe('configureClient', () => {
Array [
"400
GET /_path [undefined]: Response Error",
undefined,
],
]
`);
@ -391,10 +408,67 @@ describe('configureClient', () => {
Array [
"400
GET /_path [undefined]: Response Error",
undefined,
],
]
`);
});
it('adds meta information to logs', () => {
const client = configureClient(createFakeConfig(), { logger, type: 'test', scoped: false });
let response = createApiResponse({
statusCode: 400,
headers: {},
params: {
method: 'GET',
path: '/_path',
},
requestOptions: {
opaqueId: 'opaque-id',
},
body: {
error: {},
},
});
client.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug[0][1]).toMatchInlineSnapshot(`
Object {
"http": Object {
"request": Object {
"id": "opaque-id",
},
},
}
`);
logger.debug.mockClear();
response = createApiResponse({
statusCode: 400,
headers: {},
params: {
method: 'GET',
path: '/_path',
},
requestOptions: {
opaqueId: 'opaque-id',
},
body: {} as any,
});
client.emit('response', new errors.ResponseError(response), response);
expect(loggingSystemMock.collect(logger).debug[0][1]).toMatchInlineSnapshot(`
Object {
"http": Object {
"request": Object {
"id": "opaque-id",
},
},
}
`);
});
});
});
});

View file

@ -98,10 +98,16 @@ function getResponseMessage(event: RequestEvent): string {
const addLogging = (client: Client, logger: Logger) => {
client.on('response', (error, event) => {
if (event) {
const opaqueId = event.meta.request.options.opaqueId;
const meta = opaqueId
? {
http: { request: { id: event.meta.request.options.opaqueId } },
}
: undefined; // do not clutter logs if opaqueId is not present
if (error) {
logger.debug(getErrorMessage(error, event));
logger.debug(getErrorMessage(error, event), meta);
} else {
logger.debug(getResponseMessage(event));
logger.debug(getResponseMessage(event), meta);
}
}
});

View file

@ -25,7 +25,7 @@ describe('KibanaExecutionContext', () => {
};
const value = new ExecutionContextContainer(context).toString();
expect(value).toMatchInlineSnapshot(`"1234-5678;kibana:test-type:42"`);
expect(value).toMatchInlineSnapshot(`"1234-5678;kibana:test-type:test-name:42"`);
});
it('returns a limited representation if optional properties are omitted', () => {
@ -37,6 +37,20 @@ describe('KibanaExecutionContext', () => {
expect(value).toMatchInlineSnapshot(`"1234-5678"`);
});
it('returns an escaped string representation of provided execution contextStringified', () => {
const context: KibanaServerExecutionContext = {
id: 'Visualization☺漢字',
type: 'test-type',
name: 'test-name',
requestId: '1234-5678',
};
const value = new ExecutionContextContainer(context).toString();
expect(value).toMatchInlineSnapshot(
`"1234-5678;kibana:test-type:test-name:Visualization%E2%98%BA%E6%BC%A2%E5%AD%97"`
);
});
it('trims a string representation of provided execution context if it is bigger max allowed size', () => {
expect(
new Blob([

View file

@ -57,7 +57,13 @@ export class ExecutionContextContainer implements IExecutionContextContainer {
}
toString(): string {
const ctx = this.#context;
const contextStringified = ctx.type && ctx.id ? `kibana:${ctx.type}:${ctx.id}` : '';
const contextStringified =
ctx.type && ctx.id && ctx.name
? // id may contain non-ASCII symbols
`kibana:${encodeURIComponent(ctx.type)}:${encodeURIComponent(
ctx.name
)}:${encodeURIComponent(ctx.id)}`
: '';
const result = contextStringified ? `${ctx.requestId};${contextStringified}` : ctx.requestId;
return enforceMaxLength(result);
}

View file

@ -11,14 +11,17 @@ import {
InternalExecutionContextSetup,
} from './execution_context_service';
import { mockCoreContext } from '../core_context.mock';
import { loggingSystemMock } from '../logging/logging_system.mock';
const delay = (ms: number = 100) => new Promise((resolve) => setTimeout(resolve, ms));
describe('ExecutionContextService', () => {
describe('setup', () => {
let service: InternalExecutionContextSetup;
const core = mockCoreContext.create();
core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: true }));
let core: ReturnType<typeof mockCoreContext.create>;
beforeEach(() => {
core = mockCoreContext.create();
core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: true }));
service = new ExecutionContextService(core).setup();
});
@ -82,6 +85,19 @@ describe('ExecutionContextService', () => {
},
]);
});
it('emits context to the logs when "set" is called', async () => {
service.set({
requestId: '0000',
});
expect(loggingSystemMock.collect(core.logger).debug).toMatchInlineSnapshot(`
Array [
Array [
"stored the execution context: {\\"requestId\\":\\"0000\\"}",
],
]
`);
});
});
describe('config', () => {

View file

@ -119,7 +119,7 @@ export class ExecutionContextService
// we have to use enterWith since Hapi lifecycle model is built on event emitters.
// therefore if we wrapped request handler in asyncLocalStorage.run(), we would lose context in other lifecycles.
this.asyncLocalStorage.enterWith(contextContainer);
this.log.trace(`stored the execution context: ${contextContainer.toJSON()}`);
this.log.debug(`stored the execution context: ${JSON.stringify(contextContainer)}`);
}
private reset() {

View file

@ -435,7 +435,7 @@ describe('trace', () => {
.expect(200);
const header = response.body['x-opaque-id'];
expect(header).toContain('kibana:test-type:42');
expect(header).toContain('kibana:test-type:test-name:42');
});
it('propagates context to Elasticsearch unscoped client', async () => {
@ -456,7 +456,7 @@ describe('trace', () => {
.expect(200);
const header = response.body['x-opaque-id'];
expect(header).toContain('kibana:test-type:42');
expect(header).toContain('kibana:test-type:test-name:42');
});
it('a repeat call overwrites the old context', async () => {
@ -484,7 +484,7 @@ describe('trace', () => {
.expect(200);
const header = response.body['x-opaque-id'];
expect(header).toContain('kibana:new-type:41');
expect(header).toContain('kibana:new-type:new-name:41');
});
it('does not affect "x-opaque-id" set by user', async () => {
@ -507,7 +507,7 @@ describe('trace', () => {
.expect(200);
const header = response.body['x-opaque-id'];
expect(header).toBe('my-opaque-id;kibana:test-type:42');
expect(header).toBe('my-opaque-id;kibana:test-type:test-name:42');
});
it('does not break on non-ASCII characters within execution context', async () => {
@ -536,7 +536,7 @@ describe('trace', () => {
.expect(200);
const header = response.body['x-opaque-id'];
expect(header).toBe('my-opaque-id;kibana:test-type:42');
expect(header).toBe('my-opaque-id;kibana:test-type:test-name:42');
});
});
});

View file

@ -81,8 +81,6 @@ export type {
import type { ExecutionContextSetup, ExecutionContextStart } from './execution_context';
export type {
ExecutionContextSetup,
ExecutionContextStart,
IExecutionContextContainer,
KibanaServerExecutionContext,
KibanaExecutionContext,
@ -551,6 +549,8 @@ export type {
CapabilitiesSetup,
CapabilitiesStart,
ContextSetup,
ExecutionContextSetup,
ExecutionContextStart,
HttpResources,
PluginsServiceSetup,
PluginsServiceStart,

View file

@ -7,10 +7,10 @@
*/
/** @public */
export interface KibanaExecutionContext {
/**
* Kibana application initated an operation.
* Can be narrowed to an enum later.
* */
readonly type: string; // 'visualization' | 'actions' | 'server' | ..;
/** public name of a user-facing feature */

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { KibanaExecutionContext } from 'src/core/public';
import { i18n } from '@kbn/i18n';
import { defer } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
@ -32,6 +32,7 @@ export interface RequestHandlerParams {
timeFields?: string[];
timeRange?: TimeRange;
getNow?: () => Date;
executionContext?: KibanaExecutionContext;
}
export const handleRequest = ({
@ -47,6 +48,7 @@ export const handleRequest = ({
timeFields,
timeRange,
getNow,
executionContext,
}: RequestHandlerParams) => {
return defer(async () => {
const forceNow = getNow?.();
@ -122,6 +124,7 @@ export const handleRequest = ({
'This request queries Elasticsearch to fetch the data for the visualization.',
}),
},
executionContext,
})
.pipe(
map(({ rawResponse: response }) => {

View file

@ -18,6 +18,7 @@ describe('interpreter/functions#field', () => {
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,

View file

@ -37,6 +37,7 @@ describe('interpreter/functions#kibana', () => {
context = {
getSearchContext: () => search,
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,

View file

@ -5,7 +5,6 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExpressionValueBoxed } from 'src/plugins/expressions/common';
import { Filter } from '../../es_query';
import { Query, TimeRange } from '../../query';

View file

@ -21,6 +21,7 @@ describe('interpreter/functions#kql', () => {
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,

View file

@ -21,6 +21,7 @@ describe('interpreter/functions#lucene', () => {
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,

View file

@ -18,6 +18,7 @@ describe('interpreter/functions#range', () => {
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,

View file

@ -21,6 +21,7 @@ describe('interpreter/functions#timerange', () => {
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { KibanaExecutionContext } from 'src/core/public';
import { Observable } from 'rxjs';
import { IEsSearchRequest, IEsSearchResponse, IndexPattern } from '..';
import type { RequestAdapter } from '../../../inspector/common';
@ -134,6 +134,8 @@ export interface ISearchOptions {
* Inspector integration options
*/
inspector?: IInspectorInfo;
executionContext?: KibanaExecutionContext;
}
/**
@ -142,5 +144,5 @@ export interface ISearchOptions {
*/
export type ISearchOptionsSerializable = Pick<
ISearchOptions,
'strategy' | 'legacyHitsTotal' | 'sessionId' | 'isStored' | 'isRestore'
'strategy' | 'legacyHitsTotal' | 'sessionId' | 'isStored' | 'isRestore' | 'executionContext'
>;

View file

@ -54,6 +54,7 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { IUiSettingsClient } from 'src/core/public';
import { JsonValue } from '@kbn/common-utils';
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { KibanaExecutionContext } from 'src/core/public';
import { Location } from 'history';
import { LocationDescriptorObject } from 'history';
import { Logger } from '@kbn/logging';
@ -1759,6 +1760,8 @@ export type ISearchGeneric = <SearchStrategyRequest extends IKibanaSearchRequest
// @public (undocumented)
export interface ISearchOptions {
abortSignal?: AbortSignal;
// (undocumented)
executionContext?: KibanaExecutionContext;
indexPattern?: IndexPattern;
// Warning: (ae-forgotten-export) The symbol "IInspectorInfo" needs to be exported by the entry point index.d.ts
inspector?: IInspectorInfo;

View file

@ -57,6 +57,7 @@ describe('esaggs expression function - public', () => {
abortSignal: (jest.fn() as unknown) as jest.Mocked<AbortSignal>,
getSearchContext: jest.fn(),
getSearchSessionId: jest.fn().mockReturnValue('abc123'),
getExecutionContext: jest.fn(),
inspectorAdapters: jest.fn(),
variables: {},
types: {},

View file

@ -37,7 +37,7 @@ export function getFunctionDefinition({
}) {
return (): EsaggsExpressionFunctionDefinition => ({
...getEsaggsMeta(),
fn(input, args, { inspectorAdapters, abortSignal, getSearchSessionId }) {
fn(input, args, { inspectorAdapters, abortSignal, getSearchSessionId, getExecutionContext }) {
return defer(async () => {
const { aggs, indexPatterns, searchSource, getNow } = await getStartDependencies();
@ -64,6 +64,7 @@ export function getFunctionDefinition({
timeFields: args.timeFields,
timeRange: get(input, 'timeRange', undefined),
getNow,
executionContext: getExecutionContext()?.toJSON(),
})
)
);

View file

@ -188,6 +188,9 @@ export class SearchInterceptor {
serializableOptions.legacyHitsTotal = combined.legacyHitsTotal;
if (combined.strategy !== undefined) serializableOptions.strategy = combined.strategy;
if (combined.isStored !== undefined) serializableOptions.isStored = combined.isStored;
if (combined.executionContext !== undefined) {
serializableOptions.executionContext = combined.executionContext;
}
return serializableOptions;
}

View file

@ -59,6 +59,7 @@ describe('esaggs expression function - server', () => {
getKibanaRequest: jest.fn().mockReturnValue({ id: 'hi' } as KibanaRequest),
getSearchContext: jest.fn(),
getSearchSessionId: jest.fn().mockReturnValue('abc123'),
getExecutionContext: jest.fn(),
inspectorAdapters: jest.fn(),
variables: {},
types: {},

View file

@ -8,6 +8,7 @@
import { catchError, first } from 'rxjs/operators';
import { BfetchServerSetup } from 'src/plugins/bfetch/server';
import type { ExecutionContextSetup } from 'src/core/server';
import {
IKibanaSearchRequest,
IKibanaSearchResponse,
@ -17,7 +18,8 @@ import { ISearchStart } from '../types';
export function registerBsearchRoute(
bfetch: BfetchServerSetup,
getScoped: ISearchStart['asScoped']
getScoped: ISearchStart['asScoped'],
executionContextService: ExecutionContextSetup
): void {
bfetch.addBatchProcessingRoute<
{ request: IKibanaSearchRequest; options?: ISearchOptionsSerializable },
@ -30,8 +32,11 @@ export function registerBsearchRoute(
*/
onBatchItem: async ({ request: requestData, options }) => {
const search = getScoped(request);
const { executionContext, ...restOptions } = options || {};
if (executionContext) executionContextService.set(executionContext);
return search
.search(requestData, options)
.search(requestData, restOptions)
.pipe(
first(),
catchError((err) => {

View file

@ -158,7 +158,11 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
this.registerSearchStrategy(EQL_SEARCH_STRATEGY, eqlSearchStrategyProvider(this.logger));
registerBsearchRoute(bfetch, (request: KibanaRequest) => this.asScoped(request));
registerBsearchRoute(
bfetch,
(request: KibanaRequest) => this.asScoped(request),
core.executionContext
);
core.savedObjects.registerType(searchTelemetry);
if (usageCollection) {

View file

@ -39,6 +39,7 @@ import { ISearchSource } from 'src/plugins/data/public';
import { IUiSettingsClient } from 'src/core/server';
import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/server';
import { JsonValue } from '@kbn/common-utils';
import { KibanaExecutionContext } from 'src/core/public';
import { KibanaRequest } from 'src/core/server';
import { KibanaRequest as KibanaRequest_2 } from 'kibana/server';
import { Logger } from 'src/core/server';
@ -1020,6 +1021,8 @@ export interface IScopedSearchClient extends ISearchClient {
// @public (undocumented)
export interface ISearchOptions {
abortSignal?: AbortSignal;
// (undocumented)
executionContext?: KibanaExecutionContext;
indexPattern?: IndexPattern;
// Warning: (ae-forgotten-export) The symbol "IInspectorInfo" needs to be exported by the entry point index.d.ts
inspector?: IInspectorInfo;

View file

@ -213,6 +213,7 @@ export class Execution<
},
isSyncColorsEnabled: () => execution.params.syncColors,
...(execution.params as any).extraContext,
getExecutionContext: () => execution.params.executionContext,
};
this.result = this.input$.pipe(

View file

@ -8,6 +8,7 @@
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { KibanaRequest } from 'src/core/server';
import type { IExecutionContextContainer } from 'src/core/public';
import { ExpressionType, SerializableState } from '../expression_types';
import { Adapters, RequestAdapter } from '../../../inspector/common';
@ -62,6 +63,11 @@ export interface ExecutionContext<
* Returns the state (true|false) of the sync colors across panels switch.
*/
isSyncColorsEnabled?: () => boolean;
/**
* Contains the meta-data about the source of the expression.
*/
getExecutionContext: () => IExecutionContextContainer | undefined;
}
/**

View file

@ -28,6 +28,7 @@ describe('expression_functions', () => {
context = {
getSearchContext: () => ({} as any),
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: { theme: themeProps },
abortSignal: {} as any,

View file

@ -21,6 +21,7 @@ describe('expression_functions', () => {
context = {
getSearchContext: () => input,
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: { test: 1 },
abortSignal: {} as any,

View file

@ -24,6 +24,7 @@ describe('expression_functions', () => {
context = {
getSearchContext: () => input,
getSearchSessionId: () => undefined,
getExecutionContext: () => undefined,
types: {},
variables: { test: 1 },
abortSignal: {} as any,

View file

@ -14,6 +14,7 @@ export const createMockExecutionContext = <ExtraContext extends object = object>
const executionContext: ExecutionContext = {
getSearchContext: jest.fn(),
getSearchSessionId: jest.fn(),
getExecutionContext: jest.fn(),
variables: {},
types: {},
abortSignal: {

View file

@ -9,6 +9,7 @@
import { Observable } from 'rxjs';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { KibanaRequest } from 'src/core/server';
import type { IExecutionContextContainer } from 'src/core/public';
import { Executor } from '../executor';
import { AnyExpressionRenderDefinition, ExpressionRendererRegistry } from '../expression_renderers';
@ -78,6 +79,8 @@ export interface ExpressionExecutionParams {
syncColors?: boolean;
inspectorAdapters?: Adapters;
executionContext?: IExecutionContextContainer;
}
/**

View file

@ -140,6 +140,7 @@ export class ExpressionLoader {
searchSessionId: params.searchSessionId,
debug: params.debug,
syncColors: params.syncColors,
executionContext: params.executionContext,
});
this.subscription = this.execution
.getData()
@ -185,6 +186,8 @@ export class ExpressionLoader {
this.params.inspectorAdapters = (params.inspectorAdapters ||
this.execution?.inspect()) as Adapters;
this.params.executionContext = params.executionContext;
}
}

View file

@ -9,6 +9,7 @@ import { CoreStart } from 'src/core/public';
import { Ensure } from '@kbn/utility-types';
import { EnvironmentMode } from '@kbn/config';
import { EventEmitter } from 'events';
import { IExecutionContextContainer } from 'src/core/public';
import { KibanaRequest } from 'src/core/server';
import { Observable } from 'rxjs';
import { ObservableLike } from '@kbn/utility-types';
@ -138,6 +139,7 @@ export type ExecutionContainer<Output = ExpressionValue> = StateContainer<Execut
// @public
export interface ExecutionContext<InspectorAdapters extends Adapters = Adapters, ExecutionContextSearch extends SerializableState_2 = SerializableState_2> {
abortSignal: AbortSignal;
getExecutionContext: () => IExecutionContextContainer | undefined;
getKibanaRequest?: () => KibanaRequest;
getSearchContext: () => ExecutionContextSearch;
getSearchSessionId: () => string | undefined;
@ -901,6 +903,8 @@ export interface IExpressionLoaderParams {
// (undocumented)
disableCaching?: boolean;
// (undocumented)
executionContext?: IExecutionContextContainer;
// (undocumented)
hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions'];
// (undocumented)
inspectorAdapters?: Adapters;

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { IExecutionContextContainer } from 'src/core/public';
import { Adapters } from '../../../inspector/public';
import {
IInterpreterRenderHandlers,
@ -48,6 +48,7 @@ export interface IExpressionLoaderParams {
renderMode?: RenderMode;
syncColors?: boolean;
hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions'];
executionContext?: IExecutionContextContainer;
/**
* The flag to toggle on emitting partial results.

View file

@ -8,6 +8,7 @@ import { CoreSetup } from 'src/core/server';
import { CoreStart } from 'src/core/server';
import { Ensure } from '@kbn/utility-types';
import { EventEmitter } from 'events';
import { IExecutionContextContainer } from 'src/core/public';
import { KibanaRequest } from 'src/core/server';
import { Observable } from 'rxjs';
import { ObservableLike } from '@kbn/utility-types';
@ -136,6 +137,7 @@ export type ExecutionContainer<Output = ExpressionValue> = StateContainer<Execut
// @public
export interface ExecutionContext<InspectorAdapters extends Adapters = Adapters, ExecutionContextSearch extends SerializableState_2 = SerializableState_2> {
abortSignal: AbortSignal;
getExecutionContext: () => IExecutionContextContainer | undefined;
getKibanaRequest?: () => KibanaRequest;
getSearchContext: () => ExecutionContextSearch;
getSearchSessionId: () => string | undefined;

View file

@ -54,7 +54,7 @@ export const createMetricsFn = (): TimeseriesExpressionFunctionDefinition => ({
help: '',
},
},
async fn(input, args, { getSearchSessionId, isSyncColorsEnabled }) {
async fn(input, args, { getSearchSessionId, isSyncColorsEnabled, getExecutionContext }) {
const visParams: TimeseriesVisParams = JSON.parse(args.params);
const uiState = JSON.parse(args.uiState);
const syncColors = isSyncColorsEnabled?.() ?? false;
@ -64,6 +64,7 @@ export const createMetricsFn = (): TimeseriesExpressionFunctionDefinition => ({
visParams,
uiState,
searchSessionId: getSearchSessionId(),
executionContext: getExecutionContext(),
});
return {

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { IExecutionContextContainer } from 'src/core/public';
import { getTimezone } from './application/lib/get_timezone';
import { getUISettings, getDataStart, getCoreStart } from './services';
import { ROUTES } from '../common/constants';
@ -19,6 +19,7 @@ interface MetricsRequestHandlerParams {
uiState: Record<string, any>;
visParams: TimeseriesVisParams;
searchSessionId?: string;
executionContext?: IExecutionContextContainer;
}
export const metricsRequestHandler = async ({
@ -26,6 +27,7 @@ export const metricsRequestHandler = async ({
uiState,
visParams,
searchSessionId,
executionContext,
}: MetricsRequestHandlerParams): Promise<TimeseriesVisData | {}> => {
const config = getUISettings();
const data = getDataStart();
@ -60,6 +62,7 @@ export const metricsRequestHandler = async ({
searchSession: searchSessionOptions,
}),
}),
context: executionContext,
});
} finally {
if (untrackSearch && dataSearch.session.isCurrentSession(searchSessionId)) {

View file

@ -391,6 +391,13 @@ export class VisualizeEmbeddable
syncColors: this.input.syncColors,
uiState: this.vis.uiState,
inspectorAdapters: this.inspectorAdapters,
executionContext: this.deps.start().core.executionContext.create({
type: 'visualization',
name: this.vis.type.name,
id: this.vis.id ?? 'an_unsaved_vis',
description: this.vis.title ?? this.vis.type.title,
url: this.output.editUrl,
}),
};
if (this.abortController) {
this.abortController.abort();

View file

@ -59,7 +59,10 @@ interface VisualizationAttributes extends SavedObjectAttributes {
export interface VisualizeEmbeddableFactoryDeps {
start: StartServicesGetter<
Pick<VisualizationsStartDeps, 'inspector' | 'embeddable' | 'savedObjectsClient'>
Pick<
VisualizationsStartDeps,
'inspector' | 'embeddable' | 'savedObjectsClient' | 'executionContext'
>
>;
}

View file

@ -64,6 +64,7 @@ const createInstance = async () => {
getAttributeService: jest.fn(),
savedObjectsClient: coreMock.createStart().savedObjects.client,
savedObjects: savedObjectsPluginMock.createStartContract(),
executionContext: coreMock.createStart().executionContext,
});
return {

View file

@ -53,6 +53,7 @@ import type {
Plugin,
ApplicationStart,
SavedObjectsClientContract,
ExecutionContextServiceStart,
} from '../../../core/public';
import type { UsageCollectionSetup } from '../../usage_collection/public';
import type { UiActionsStart } from '../../ui_actions/public';
@ -102,6 +103,7 @@ export interface VisualizationsStartDeps {
getAttributeService: EmbeddableStart['getAttributeService'];
savedObjects: SavedObjectsStart;
savedObjectsClient: SavedObjectsClientContract;
executionContext: ExecutionContextServiceStart;
}
/**

View file

@ -26,10 +26,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
const coreStart = window._coreProvider.start.core;
const context = coreStart.executionContext.create({
type: 'execution_context_app',
name: 'Execution context app',
id: '42',
type: 'visualization',
name: 'execution_context_app',
// add a non-ASCII symbols to make sure it doesn't break the context propagation mechanism
id: 'Visualization☺漢字',
description: 'какое-то странное описание',
});
@ -39,7 +39,9 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
return result['x-opaque-id'];
})
).to.contain('kibana:execution_context_app:42');
).to.contain(
'kibana:visualization:execution_context_app:Visualization%E2%98%BA%E6%BC%A2%E5%AD%97'
);
});
});
});

View file

@ -128,6 +128,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{
timeRange: {
@ -167,6 +168,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{
timeRange: {
@ -210,6 +212,7 @@ describe('embeddable', () => {
},
errors: [{ shortMessage: '', longMessage: 'my validation error' }],
}),
executionContext: coreMock.createStart().executionContext,
},
{} as LensEmbeddableInput
);
@ -253,6 +256,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{} as LensEmbeddableInput
);
@ -291,6 +295,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{ id: '123' } as LensEmbeddableInput
);
@ -332,6 +337,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{ id: '123' } as LensEmbeddableInput
);
@ -380,6 +386,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{ id: '123' } as LensEmbeddableInput
);
@ -426,6 +433,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{ id: '123' } as LensEmbeddableInput
);
@ -479,6 +487,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
input
);
@ -532,6 +541,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
input
);
@ -584,6 +594,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
input
);
@ -625,6 +636,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{ id: '123' } as LensEmbeddableInput
);
@ -666,6 +678,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{ id: '123' } as LensEmbeddableInput
);
@ -707,6 +720,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
{ id: '123', timeRange, query, filters } as LensEmbeddableInput
);
@ -763,6 +777,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
({ id: '123', onLoad } as unknown) as LensEmbeddableInput
);
@ -835,6 +850,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
({ id: '123', onFilter } as unknown) as LensEmbeddableInput
);
@ -882,6 +898,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
({ id: '123', onBrushEnd } as unknown) as LensEmbeddableInput
);
@ -929,6 +946,7 @@ describe('embeddable', () => {
},
errors: undefined,
}),
executionContext: coreMock.createStart().executionContext,
},
({ id: '123', onTableRowClick } as unknown) as LensEmbeddableInput
);

View file

@ -8,6 +8,7 @@
import { isEqual, uniqBy } from 'lodash';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import type { ExecutionContextServiceStart } from 'src/core/public';
import {
ExecutionContextSearch,
Filter,
@ -98,6 +99,7 @@ export interface LensEmbeddableDeps {
getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions'];
capabilities: { canSaveVisualizations: boolean; canSaveDashboards: boolean };
usageCollection?: UsageCollectionSetup;
executionContext: ExecutionContextServiceStart;
}
export class Embeddable
@ -323,7 +325,15 @@ export class Embeddable
if (this.input.onLoad) {
this.input.onLoad(true);
}
const executionContext = this.deps.executionContext.create({
type: 'lens',
name: this.savedVis.visualizationType ?? '',
description: this.savedVis.title ?? this.savedVis.description ?? '',
id: this.id,
url: this.output.editUrl,
});
const input = this.getInput();
render(
<ExpressionWrapper
ExpressionRenderer={this.expressionRenderer}
@ -339,6 +349,7 @@ export class Embeddable
hasCompatibleActions={this.hasCompatibleActions}
className={input.className}
style={input.style}
executionContext={executionContext}
canEdit={this.getIsEditable() && input.viewMode === 'edit'}
onRuntimeError={() => {
this.logError('runtime');

View file

@ -5,7 +5,12 @@
* 2.0.
*/
import { Capabilities, HttpSetup, SavedObjectReference } from 'kibana/public';
import type {
Capabilities,
HttpSetup,
SavedObjectReference,
ExecutionContextServiceStart,
} from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { RecursiveReadonly } from '@kbn/utility-types';
import { Ast } from '@kbn/interpreter/target/common';
@ -33,6 +38,7 @@ export interface LensEmbeddableStartServices {
indexPatternService: IndexPatternsContract;
uiActions?: UiActionsStart;
usageCollection?: UsageCollectionSetup;
executionContext: ExecutionContextServiceStart;
documentToExpression: (
doc: Document
) => Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }>;
@ -87,6 +93,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
indexPatternService,
capabilities,
usageCollection,
executionContext,
} = await this.getStartServices();
const { Embeddable } = await import('../async_services');
@ -106,6 +113,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
canSaveVisualizations: Boolean(capabilities.visualize.save),
},
usageCollection,
executionContext,
},
input,
parent

View file

@ -14,6 +14,7 @@ import {
ReactExpressionRendererType,
ReactExpressionRendererProps,
} from 'src/plugins/expressions/public';
import type { IExecutionContextContainer } from 'src/core/public';
import { ExecutionContextSearch } from 'src/plugins/data/public';
import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions';
import classNames from 'classnames';
@ -39,6 +40,7 @@ export interface ExpressionWrapperProps {
className?: string;
canEdit: boolean;
onRuntimeError: () => void;
executionContext?: IExecutionContextContainer;
}
interface VisualizationErrorProps {
@ -108,6 +110,7 @@ export function ExpressionWrapper({
errors,
canEdit,
onRuntimeError,
executionContext,
}: ExpressionWrapperProps) {
return (
<I18nProvider>
@ -125,6 +128,7 @@ export function ExpressionWrapper({
onData$={onData$}
renderMode={renderMode}
syncColors={syncColors}
executionContext={executionContext}
renderError={(errorMessage, error) => {
onRuntimeError();
return (

View file

@ -177,6 +177,7 @@ export class LensPlugin {
attributeService: await this.attributeService!(),
capabilities: coreStart.application.capabilities,
coreHttp: coreStart.http,
executionContext: coreStart.executionContext,
timefilter: deps.data.query.timefilter.timefilter,
expressionRenderer: deps.expressions.ReactExpressionRenderer,
documentToExpression: this.editorFrameService!.documentToExpression,