+
+
+
+
+
+
+
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-recentlyViewed"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
+
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
-
-
+ Undock navigation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- kibana
-
-
-
-
-
-
-
-
-
-
,
}
- >
-
-
-
-
-
-
-
-
- Undock navigation
-
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lock"
- label="Undock navigation"
- onClick={[Function]}
- size="xs"
- >
-
-
-
-
-
-
- Undock navigation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
- close
-
-
+ Undock navigation
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+ }
+ >
+
+
+
+
+
+
+ close
+
+
+
+
+
+
+
+
+
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index bd279baa78d98..fc753517fd940 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -41,8 +41,8 @@ export class DocLinksService {
},
filebeat: {
base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`,
- installation: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-installation.html`,
- configuration: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-configuration.html`,
+ installation: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-installation-configuration.html`,
+ configuration: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/configuring-howto-filebeat.html`,
elasticsearchOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/elasticsearch-output.html`,
startup: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-starting.html`,
exportedFields: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/exported-fields.html`,
diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
index fa83b34e06b81..a5c1d46f74709 100644
--- a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
+++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
@@ -26,7 +26,7 @@ Array [
]
`;
-exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `""`;
+exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `""`;
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
Array [
@@ -59,4 +59,4 @@ Array [
]
`;
-exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `""`;
+exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `""`;
diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
index fb00ddc38c6dc..aea52eb8e7ab7 100644
--- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
+++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
@@ -31,7 +31,7 @@ Array [
]
`;
-exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `"
"`;
+exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `""`;
exports[`ModalService openConfirm() renders a string confirm message 1`] = `
Array [
@@ -53,7 +53,7 @@ Array [
]
`;
-exports[`ModalService openConfirm() renders a string confirm message 2`] = `"
"`;
+exports[`ModalService openConfirm() renders a string confirm message 2`] = `""`;
exports[`ModalService openConfirm() with a currently active confirm replaces the current confirm with the new one 1`] = `
Array [
@@ -145,7 +145,7 @@ Array [
]
`;
-exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
"`;
+exports[`ModalService openModal() renders a modal to the DOM 2`] = `""`;
exports[`ModalService openModal() with a currently active confirm replaces the current confirm with the new one 1`] = `
Array [
diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts
index 85517b80745f1..121ef3aa42d51 100644
--- a/src/core/server/elasticsearch/client/cluster_client.test.ts
+++ b/src/core/server/elasticsearch/client/cluster_client.test.ts
@@ -96,7 +96,7 @@ describe('ClusterClient', () => {
expect(scopedClusterClient.asCurrentUser).toBe(scopedClient.child.mock.results[0].value);
});
- it('returns a distinct scoped cluster client on each call', () => {
+ it('returns a distinct scoped cluster client on each call', () => {
const clusterClient = new ClusterClient(createConfig(), logger, getAuthHeaders);
const request = httpServerMock.createKibanaRequest();
@@ -127,7 +127,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledTimes(1);
expect(scopedClient.child).toHaveBeenCalledWith({
- headers: { foo: 'bar' },
+ headers: { foo: 'bar', 'x-opaque-id': expect.any(String) },
});
});
@@ -147,7 +147,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledTimes(1);
expect(scopedClient.child).toHaveBeenCalledWith({
- headers: { authorization: 'auth' },
+ headers: { authorization: 'auth', 'x-opaque-id': expect.any(String) },
});
});
@@ -171,7 +171,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledTimes(1);
expect(scopedClient.child).toHaveBeenCalledWith({
- headers: { authorization: 'auth' },
+ headers: { authorization: 'auth', 'x-opaque-id': expect.any(String) },
});
});
@@ -195,6 +195,26 @@ describe('ClusterClient', () => {
headers: {
foo: 'bar',
hello: 'dolly',
+ 'x-opaque-id': expect.any(String),
+ },
+ });
+ });
+
+ it('adds the x-opaque-id header based on the request id', () => {
+ const config = createConfig();
+ getAuthHeaders.mockReturnValue({});
+
+ const clusterClient = new ClusterClient(config, logger, getAuthHeaders);
+ const request = httpServerMock.createKibanaRequest({
+ kibanaRequestState: { requestId: 'my-fake-id' },
+ });
+
+ clusterClient.asScoped(request);
+
+ expect(scopedClient.child).toHaveBeenCalledTimes(1);
+ expect(scopedClient.child).toHaveBeenCalledWith({
+ headers: {
+ 'x-opaque-id': 'my-fake-id',
},
});
});
@@ -221,6 +241,7 @@ describe('ClusterClient', () => {
headers: {
foo: 'auth',
hello: 'dolly',
+ 'x-opaque-id': expect.any(String),
},
});
});
@@ -247,6 +268,31 @@ describe('ClusterClient', () => {
headers: {
foo: 'request',
hello: 'dolly',
+ 'x-opaque-id': expect.any(String),
+ },
+ });
+ });
+
+ it('respect the precedence of x-opaque-id header over config headers', () => {
+ const config = createConfig({
+ customHeaders: {
+ 'x-opaque-id': 'from config',
+ },
+ });
+ getAuthHeaders.mockReturnValue({});
+
+ const clusterClient = new ClusterClient(config, logger, getAuthHeaders);
+ const request = httpServerMock.createKibanaRequest({
+ headers: { foo: 'request' },
+ kibanaRequestState: { requestId: 'from request' },
+ });
+
+ clusterClient.asScoped(request);
+
+ expect(scopedClient.child).toHaveBeenCalledTimes(1);
+ expect(scopedClient.child).toHaveBeenCalledWith({
+ headers: {
+ 'x-opaque-id': 'from request',
},
});
});
diff --git a/src/core/server/elasticsearch/client/cluster_client.ts b/src/core/server/elasticsearch/client/cluster_client.ts
index d9a0e6fe3f238..ffe0c10321fff 100644
--- a/src/core/server/elasticsearch/client/cluster_client.ts
+++ b/src/core/server/elasticsearch/client/cluster_client.ts
@@ -19,7 +19,7 @@
import { Client } from '@elastic/elasticsearch';
import { Logger } from '../../logging';
-import { GetAuthHeaders, isRealRequest, Headers } from '../../http';
+import { GetAuthHeaders, Headers, isKibanaRequest, isRealRequest } from '../../http';
import { ensureRawRequest, filterHeaders } from '../../http/router';
import { ScopeableRequest } from '../types';
import { ElasticsearchClient } from './types';
@@ -95,12 +95,14 @@ export class ClusterClient implements ICustomClusterClient {
private getScopedHeaders(request: ScopeableRequest): Headers {
let scopedHeaders: Headers;
if (isRealRequest(request)) {
- const authHeaders = this.getAuthHeaders(request);
const requestHeaders = ensureRawRequest(request).headers;
- scopedHeaders = filterHeaders(
- { ...requestHeaders, ...authHeaders },
- this.config.requestHeadersWhitelist
- );
+ const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {};
+ const authHeaders = this.getAuthHeaders(request);
+
+ scopedHeaders = filterHeaders({ ...requestHeaders, ...requestIdHeaders, ...authHeaders }, [
+ 'x-opaque-id',
+ ...this.config.requestHeadersWhitelist,
+ ]);
} else {
scopedHeaders = filterHeaders(request?.headers ?? {}, this.config.requestHeadersWhitelist);
}
diff --git a/src/core/server/elasticsearch/legacy/cluster_client.test.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.ts
index fd57d06e61eee..73d941053e84b 100644
--- a/src/core/server/elasticsearch/legacy/cluster_client.test.ts
+++ b/src/core/server/elasticsearch/legacy/cluster_client.test.ts
@@ -349,6 +349,20 @@ describe('#asScoped', () => {
);
});
+ test('passes x-opaque-id header with request id', () => {
+ clusterClient.asScoped(
+ httpServerMock.createKibanaRequest({ kibanaRequestState: { requestId: 'alpha' } })
+ );
+
+ expect(MockScopedClusterClient).toHaveBeenCalledTimes(1);
+ expect(MockScopedClusterClient).toHaveBeenCalledWith(
+ expect.any(Function),
+ expect.any(Function),
+ { 'x-opaque-id': 'alpha' },
+ expect.any(Object)
+ );
+ });
+
test('both scoped and internal API caller fail if cluster client is closed', async () => {
clusterClient.asScoped(
httpServerMock.createRawRequest({ headers: { zero: '0', one: '1', two: '2', three: '3' } })
@@ -482,7 +496,7 @@ describe('#asScoped', () => {
expect(MockScopedClusterClient).toHaveBeenCalledWith(
expect.any(Function),
expect.any(Function),
- {},
+ expect.objectContaining({ 'x-opaque-id': expect.any(String) }),
auditor
);
});
diff --git a/src/core/server/elasticsearch/legacy/cluster_client.ts b/src/core/server/elasticsearch/legacy/cluster_client.ts
index f8b2d39a4251c..81cbb5a10d7c6 100644
--- a/src/core/server/elasticsearch/legacy/cluster_client.ts
+++ b/src/core/server/elasticsearch/legacy/cluster_client.ts
@@ -20,7 +20,7 @@ import { Client } from 'elasticsearch';
import { get } from 'lodash';
import { LegacyElasticsearchErrorHelpers } from './errors';
-import { GetAuthHeaders, isRealRequest, KibanaRequest } from '../../http';
+import { GetAuthHeaders, KibanaRequest, isKibanaRequest, isRealRequest } from '../../http';
import { AuditorFactory } from '../../audit_trail';
import { filterHeaders, ensureRawRequest } from '../../http/router';
import { Logger } from '../../logging';
@@ -207,7 +207,10 @@ export class LegacyClusterClient implements ILegacyClusterClient {
return new LegacyScopedClusterClient(
this.callAsInternalUser,
this.callAsCurrentUser,
- filterHeaders(this.getHeaders(request), this.config.requestHeadersWhitelist),
+ filterHeaders(this.getHeaders(request), [
+ 'x-opaque-id',
+ ...this.config.requestHeadersWhitelist,
+ ]),
this.getScopedAuditor(request)
);
}
@@ -215,8 +218,7 @@ export class LegacyClusterClient implements ILegacyClusterClient {
private getScopedAuditor(request?: ScopeableRequest) {
// TODO: support alternative credential owners from outside of Request context in #39430
if (request && isRealRequest(request)) {
- const kibanaRequest =
- request instanceof KibanaRequest ? request : KibanaRequest.from(request);
+ const kibanaRequest = isKibanaRequest(request) ? request : KibanaRequest.from(request);
const auditorFactory = this.getAuditorFactory();
return auditorFactory.asScoped(kibanaRequest);
}
@@ -256,8 +258,9 @@ export class LegacyClusterClient implements ILegacyClusterClient {
return request && request.headers ? request.headers : {};
}
const authHeaders = this.getAuthHeaders(request);
- const headers = ensureRawRequest(request).headers;
+ const requestHeaders = ensureRawRequest(request).headers;
+ const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {};
- return { ...headers, ...authHeaders };
+ return { ...requestHeaders, ...requestIdHeaders, ...authHeaders };
}
}
diff --git a/src/core/server/http/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap
index d48ead3cec8e1..e9b818fe859ec 100644
--- a/src/core/server/http/__snapshots__/http_config.test.ts.snap
+++ b/src/core/server/http/__snapshots__/http_config.test.ts.snap
@@ -39,6 +39,10 @@ Object {
},
"name": "kibana-hostname",
"port": 5601,
+ "requestId": Object {
+ "allowFromAnyIp": false,
+ "ipAllowlist": Array [],
+ },
"rewriteBasePath": false,
"socketTimeout": 120000,
"ssl": Object {
diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts
index 1fb2b5693bb61..8e5dec7d4eadd 100644
--- a/src/core/server/http/cookie_session_storage.test.ts
+++ b/src/core/server/http/cookie_session_storage.test.ts
@@ -63,6 +63,10 @@ configService.atPath.mockReturnValue(
whitelist: [],
},
customResponseHeaders: {},
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: [],
+ },
} as any)
);
diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts
index 0698f118be03f..58e6699582e13 100644
--- a/src/core/server/http/http_config.test.ts
+++ b/src/core/server/http/http_config.test.ts
@@ -54,6 +54,63 @@ test('throws if invalid hostname', () => {
expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot();
});
+describe('requestId', () => {
+ test('accepts valid ip addresses', () => {
+ const {
+ requestId: { ipAllowlist },
+ } = config.schema.validate({
+ requestId: {
+ allowFromAnyIp: false,
+ ipAllowlist: ['0.0.0.0', '123.123.123.123', '1200:0000:AB00:1234:0000:2552:7777:1313'],
+ },
+ });
+ expect(ipAllowlist).toMatchInlineSnapshot(`
+ Array [
+ "0.0.0.0",
+ "123.123.123.123",
+ "1200:0000:AB00:1234:0000:2552:7777:1313",
+ ]
+ `);
+ });
+
+ test('rejects invalid ip addresses', () => {
+ expect(() => {
+ config.schema.validate({
+ requestId: {
+ allowFromAnyIp: false,
+ ipAllowlist: ['1200:0000:AB00:1234:O000:2552:7777:1313', '[2001:db8:0:1]:80'],
+ },
+ });
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"[requestId.ipAllowlist.0]: value must be a valid ipv4 or ipv6 address"`
+ );
+ });
+
+ test('rejects if allowFromAnyIp is `true` and `ipAllowlist` is non-empty', () => {
+ expect(() => {
+ config.schema.validate({
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: ['0.0.0.0', '123.123.123.123', '1200:0000:AB00:1234:0000:2552:7777:1313'],
+ },
+ });
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"[requestId]: allowFromAnyIp must be set to 'false' if any values are specified in ipAllowlist"`
+ );
+
+ expect(() => {
+ config.schema.validate({
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: ['0.0.0.0', '123.123.123.123', '1200:0000:AB00:1234:0000:2552:7777:1313'],
+ },
+ });
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"[requestId]: allowFromAnyIp must be set to 'false' if any values are specified in ipAllowlist"`
+ );
+ });
+});
+
test('can specify max payload as string', () => {
const obj = {
maxPayload: '2mb',
diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts
index e74f6d32e92b0..7d41b4ea9e915 100644
--- a/src/core/server/http/http_config.ts
+++ b/src/core/server/http/http_config.ts
@@ -87,6 +87,19 @@ export const config = {
{ defaultValue: [] }
),
}),
+ requestId: schema.object(
+ {
+ allowFromAnyIp: schema.boolean({ defaultValue: false }),
+ ipAllowlist: schema.arrayOf(schema.ip(), { defaultValue: [] }),
+ },
+ {
+ validate(value) {
+ if (value.allowFromAnyIp === true && value.ipAllowlist?.length > 0) {
+ return `allowFromAnyIp must be set to 'false' if any values are specified in ipAllowlist`;
+ }
+ },
+ }
+ ),
},
{
validate: (rawConfig) => {
@@ -130,6 +143,7 @@ export class HttpConfig {
public compression: { enabled: boolean; referrerWhitelist?: string[] };
public csp: ICspConfig;
public xsrf: { disableProtection: boolean; whitelist: string[] };
+ public requestId: { allowFromAnyIp: boolean; ipAllowlist: string[] };
/**
* @internal
@@ -158,6 +172,7 @@ export class HttpConfig {
this.compression = rawHttpConfig.compression;
this.csp = new CspConfig(rawCspConfig);
this.xsrf = rawHttpConfig.xsrf;
+ this.requestId = rawHttpConfig.requestId;
}
}
diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts
index ba6662db3655e..6d096b76263b5 100644
--- a/src/core/server/http/http_server.mocks.ts
+++ b/src/core/server/http/http_server.mocks.ts
@@ -29,7 +29,8 @@ import {
RouteMethod,
KibanaResponseFactory,
RouteValidationSpec,
- KibanaRouteState,
+ KibanaRouteOptions,
+ KibanaRequestState,
} from './router';
import { OnPreResponseToolkit } from './lifecycle/on_pre_response';
import { OnPostAuthToolkit } from './lifecycle/on_post_auth';
@@ -45,7 +46,8 @@ interface RequestFixtureOptions {
method?: RouteMethod;
socket?: Socket;
routeTags?: string[];
- kibanaRouteState?: KibanaRouteState;
+ kibanaRouteOptions?: KibanaRouteOptions;
+ kibanaRequestState?: KibanaRequestState;
routeAuthRequired?: false;
validation?: {
params?: RouteValidationSpec
;
@@ -65,13 +67,15 @@ function createKibanaRequestMock
({
routeTags,
routeAuthRequired,
validation = {},
- kibanaRouteState = { xsrfRequired: true },
+ kibanaRouteOptions = { xsrfRequired: true },
+ kibanaRequestState = { requestId: '123' },
auth = { isAuthenticated: true },
}: RequestFixtureOptions
= {}) {
const queryString = stringify(query, { sort: false });
return KibanaRequest.from
(
createRawRequestMock({
+ app: kibanaRequestState,
auth,
headers,
params,
@@ -86,7 +90,7 @@ function createKibanaRequestMock
({
search: queryString ? `?${queryString}` : queryString,
},
route: {
- settings: { tags: routeTags, auth: routeAuthRequired, app: kibanaRouteState },
+ settings: { tags: routeTags, auth: routeAuthRequired, app: kibanaRouteOptions },
},
raw: {
req: {
@@ -133,6 +137,7 @@ function createRawRequestMock(customization: DeepPartial = {}) {
raw: {
req: {
url: '/',
+ socket: {},
},
},
},
diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts
index abe70e003732b..7507a08dd150a 100644
--- a/src/core/server/http/http_server.test.ts
+++ b/src/core/server/http/http_server.test.ts
@@ -68,7 +68,11 @@ beforeEach(() => {
port: 10002,
ssl: { enabled: false },
compression: { enabled: true },
- } as HttpConfig;
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: [],
+ },
+ } as any;
configWithSSL = {
...config,
diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts
index 99ab0ef16c2f9..7609f23fe0c51 100644
--- a/src/core/server/http/http_server.ts
+++ b/src/core/server/http/http_server.ts
@@ -22,13 +22,19 @@ import url from 'url';
import { Logger, LoggerFactory } from '../logging';
import { HttpConfig } from './http_config';
-import { createServer, getListenerOptions, getServerOptions } from './http_tools';
+import { createServer, getListenerOptions, getServerOptions, getRequestId } from './http_tools';
import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth';
import { adoptToHapiOnPreAuth, OnPreAuthHandler } from './lifecycle/on_pre_auth';
import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth';
import { adoptToHapiOnRequest, OnPreRoutingHandler } from './lifecycle/on_pre_routing';
import { adoptToHapiOnPreResponseFormat, OnPreResponseHandler } from './lifecycle/on_pre_response';
-import { IRouter, RouteConfigOptions, KibanaRouteState, isSafeMethod } from './router';
+import {
+ IRouter,
+ RouteConfigOptions,
+ KibanaRouteOptions,
+ KibanaRequestState,
+ isSafeMethod,
+} from './router';
import {
SessionStorageCookieOptions,
createCookieSessionStorageFactory,
@@ -115,6 +121,7 @@ export class HttpServer {
const basePathService = new BasePath(config.basePath);
this.setupBasePathRewrite(config, basePathService);
this.setupConditionalCompression(config);
+ this.setupRequestStateAssignment(config);
return {
registerRouter: this.registerRouter.bind(this),
@@ -164,7 +171,7 @@ export class HttpServer {
const { authRequired, tags, body = {}, timeout } = route.options;
const { accepts: allow, maxBytes, output, parse } = body;
- const kibanaRouteState: KibanaRouteState = {
+ const kibanaRouteOptions: KibanaRouteOptions = {
xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method),
};
@@ -180,7 +187,7 @@ export class HttpServer {
path: route.path,
options: {
auth: this.getAuthOption(authRequired),
- app: kibanaRouteState,
+ app: kibanaRouteOptions,
ext: {
onPreAuth: {
method: (request, h) => {
@@ -303,6 +310,16 @@ export class HttpServer {
}
}
+ private setupRequestStateAssignment(config: HttpConfig) {
+ this.server!.ext('onRequest', (request, responseToolkit) => {
+ request.app = {
+ ...(request.app ?? {}),
+ requestId: getRequestId(request, config.requestId),
+ } as KibanaRequestState;
+ return responseToolkit.continue;
+ });
+ }
+
private registerOnPreAuth(fn: OnPreAuthHandler) {
if (this.server === undefined) {
throw new Error('Server is not created yet');
diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts
index f09d862f9edac..bdeca3a87799a 100644
--- a/src/core/server/http/http_tools.test.ts
+++ b/src/core/server/http/http_tools.test.ts
@@ -26,11 +26,20 @@ jest.mock('fs', () => {
};
});
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'),
+}));
+
import supertest from 'supertest';
import { Request, ResponseToolkit } from 'hapi';
import Joi from 'joi';
-import { defaultValidationErrorHandler, HapiValidationError, getServerOptions } from './http_tools';
+import {
+ defaultValidationErrorHandler,
+ HapiValidationError,
+ getServerOptions,
+ getRequestId,
+} from './http_tools';
import { HttpServer } from './http_server';
import { HttpConfig, config } from './http_config';
import { Router } from './router';
@@ -94,7 +103,11 @@ describe('timeouts', () => {
maxPayload: new ByteSizeValue(1024),
ssl: {},
compression: { enabled: true },
- } as HttpConfig);
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: [],
+ },
+ } as any);
registerRouter(router);
await server.start();
@@ -173,3 +186,75 @@ describe('getServerOptions', () => {
`);
});
});
+
+describe('getRequestId', () => {
+ describe('when allowFromAnyIp is true', () => {
+ it('generates a UUID if no x-opaque-id header is present', () => {
+ const request = {
+ headers: {},
+ raw: { req: { socket: { remoteAddress: '1.1.1.1' } } },
+ } as any;
+ expect(getRequestId(request, { allowFromAnyIp: true, ipAllowlist: [] })).toEqual(
+ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ );
+ });
+
+ it('uses x-opaque-id header value if present', () => {
+ const request = {
+ headers: {
+ 'x-opaque-id': 'id from header',
+ raw: { req: { socket: { remoteAddress: '1.1.1.1' } } },
+ },
+ } as any;
+ expect(getRequestId(request, { allowFromAnyIp: true, ipAllowlist: [] })).toEqual(
+ 'id from header'
+ );
+ });
+ });
+
+ describe('when allowFromAnyIp is false', () => {
+ describe('and ipAllowlist is empty', () => {
+ it('generates a UUID even if x-opaque-id header is present', () => {
+ const request = {
+ headers: { 'x-opaque-id': 'id from header' },
+ raw: { req: { socket: { remoteAddress: '1.1.1.1' } } },
+ } as any;
+ expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: [] })).toEqual(
+ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ );
+ });
+ });
+
+ describe('and ipAllowlist is not empty', () => {
+ it('uses x-opaque-id header if request comes from trusted IP address', () => {
+ const request = {
+ headers: { 'x-opaque-id': 'id from header' },
+ raw: { req: { socket: { remoteAddress: '1.1.1.1' } } },
+ } as any;
+ expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual(
+ 'id from header'
+ );
+ });
+
+ it('generates a UUID if request comes from untrusted IP address', () => {
+ const request = {
+ headers: { 'x-opaque-id': 'id from header' },
+ raw: { req: { socket: { remoteAddress: '5.5.5.5' } } },
+ } as any;
+ expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual(
+ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ );
+ });
+
+ it('generates UUID if request comes from trusted IP address but no x-opaque-id header is present', () => {
+ const request = {
+ headers: {},
+ raw: { req: { socket: { remoteAddress: '1.1.1.1' } } },
+ } as any;
+ expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual(
+ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ );
+ });
+ });
+ });
+});
diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts
index 4e47cf492e287..71900ab982f3d 100644
--- a/src/core/server/http/http_tools.ts
+++ b/src/core/server/http/http_tools.ts
@@ -21,6 +21,7 @@ import { Lifecycle, Request, ResponseToolkit, Server, ServerOptions, Util } from
import Hoek from 'hoek';
import { ServerOptions as TLSOptions } from 'https';
import { ValidationError } from 'joi';
+import uuid from 'uuid';
import { HttpConfig } from './http_config';
import { validateObject } from './prototype_pollution';
@@ -169,3 +170,12 @@ export function defaultValidationErrorHandler(
throw err;
}
+
+export function getRequestId(request: Request, options: HttpConfig['requestId']): string {
+ return options.allowFromAnyIp ||
+ // socket may be undefined in integration tests that connect via the http listener directly
+ (request.raw.req.socket?.remoteAddress &&
+ options.ipAllowlist.includes(request.raw.req.socket.remoteAddress))
+ ? request.headers['x-opaque-id'] ?? uuid.v4()
+ : uuid.v4();
+}
diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts
index e91f7d9375842..7513e60966085 100644
--- a/src/core/server/http/index.ts
+++ b/src/core/server/http/index.ts
@@ -24,6 +24,7 @@ export { AuthStatus, GetAuthState, IsAuthenticated } from './auth_state_storage'
export {
CustomHttpResponseOptions,
IKibanaSocket,
+ isKibanaRequest,
isRealRequest,
Headers,
HttpResponseOptions,
diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts
index 6a00db5a6cc4a..2b9193a280aec 100644
--- a/src/core/server/http/integration_tests/core_services.test.ts
+++ b/src/core/server/http/integration_tests/core_services.test.ts
@@ -406,7 +406,10 @@ describe('http service', () => {
// client contains authHeaders for BWC with legacy platform.
const [client] = MockLegacyScopedClusterClient.mock.calls;
const [, , clientHeaders] = client;
- expect(clientHeaders).toEqual(authHeaders);
+ expect(clientHeaders).toEqual({
+ ...authHeaders,
+ 'x-opaque-id': expect.any(String),
+ });
});
it('passes request authorization header to Elasticsearch if registerAuth was not set', async () => {
@@ -430,7 +433,10 @@ describe('http service', () => {
const [client] = MockLegacyScopedClusterClient.mock.calls;
const [, , clientHeaders] = client;
- expect(clientHeaders).toEqual({ authorization: authorizationHeader });
+ expect(clientHeaders).toEqual({
+ authorization: authorizationHeader,
+ 'x-opaque-id': expect.any(String),
+ });
});
it('forwards 401 errors returned from elasticsearch', async () => {
diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts
index e23426e630455..a1401ba73813b 100644
--- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts
+++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts
@@ -62,6 +62,10 @@ describe('core lifecycle handlers', () => {
'some-header': 'some-value',
},
xsrf: { disableProtection: false, whitelist: [whitelistedTestPath] },
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: [],
+ },
} as any)
);
server = createHttpServer({ configService });
diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts
index 3a7335583296e..0727ff848c189 100644
--- a/src/core/server/http/integration_tests/request.test.ts
+++ b/src/core/server/http/integration_tests/request.test.ts
@@ -288,4 +288,24 @@ describe('KibanaRequest', () => {
});
});
});
+
+ describe('request id', () => {
+ it('accepts x-opaque-id header case-insensitively', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+ router.get({ path: '/', validate: false }, async (context, req, res) => {
+ return res.ok({ body: { requestId: req.id } });
+ });
+ await server.start();
+
+ const st = supertest(innerServer.listener);
+
+ const resp1 = await st.get('/').set({ 'x-opaque-id': 'alpha' }).expect(200);
+ expect(resp1.body).toEqual({ requestId: 'alpha' });
+ const resp2 = await st.get('/').set({ 'X-Opaque-Id': 'beta' }).expect(200);
+ expect(resp2.body).toEqual({ requestId: 'beta' });
+ const resp3 = await st.get('/').set({ 'X-OPAQUE-ID': 'gamma' }).expect(200);
+ expect(resp3.body).toEqual({ requestId: 'gamma' });
+ });
+ });
});
diff --git a/src/core/server/http/lifecycle_handlers.test.ts b/src/core/server/http/lifecycle_handlers.test.ts
index a80e432e0d4cb..fdcf2a173b906 100644
--- a/src/core/server/http/lifecycle_handlers.test.ts
+++ b/src/core/server/http/lifecycle_handlers.test.ts
@@ -24,7 +24,7 @@ import {
} from './lifecycle_handlers';
import { httpServerMock } from './http_server.mocks';
import { HttpConfig } from './http_config';
-import { KibanaRequest, RouteMethod, KibanaRouteState } from './router';
+import { KibanaRequest, RouteMethod, KibanaRouteOptions } from './router';
const createConfig = (partial: Partial): HttpConfig => partial as HttpConfig;
@@ -32,14 +32,19 @@ const forgeRequest = ({
headers = {},
path = '/',
method = 'get',
- kibanaRouteState,
+ kibanaRouteOptions,
}: Partial<{
headers: Record;
path: string;
method: RouteMethod;
- kibanaRouteState: KibanaRouteState;
+ kibanaRouteOptions: KibanaRouteOptions;
}>): KibanaRequest => {
- return httpServerMock.createKibanaRequest({ headers, path, method, kibanaRouteState });
+ return httpServerMock.createKibanaRequest({
+ headers,
+ path,
+ method,
+ kibanaRouteOptions,
+ });
};
describe('xsrf post-auth handler', () => {
@@ -154,7 +159,7 @@ describe('xsrf post-auth handler', () => {
method: 'post',
headers: {},
path: '/some-path',
- kibanaRouteState: {
+ kibanaRouteOptions: {
xsrfRequired: false,
},
});
diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts
index 83ceff4a25d86..e09833ef6b2da 100644
--- a/src/core/server/http/router/index.ts
+++ b/src/core/server/http/router/index.ts
@@ -24,7 +24,9 @@ export {
KibanaRequestEvents,
KibanaRequestRoute,
KibanaRequestRouteOptions,
- KibanaRouteState,
+ KibanaRouteOptions,
+ KibanaRequestState,
+ isKibanaRequest,
isRealRequest,
LegacyRequest,
ensureRawRequest,
diff --git a/src/core/server/http/router/request.test.ts b/src/core/server/http/router/request.test.ts
index fb999dc60e39c..e741121f3d70c 100644
--- a/src/core/server/http/router/request.test.ts
+++ b/src/core/server/http/router/request.test.ts
@@ -16,12 +16,45 @@
* specific language governing permissions and limitations
* under the License.
*/
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'),
+}));
+
import { RouteOptions } from 'hapi';
import { KibanaRequest } from './request';
import { httpServerMock } from '../http_server.mocks';
import { schema } from '@kbn/config-schema';
describe('KibanaRequest', () => {
+ describe('id property', () => {
+ it('uses the request.app.requestId property if present', () => {
+ const request = httpServerMock.createRawRequest({
+ app: { requestId: 'fakeId' },
+ });
+ const kibanaRequest = KibanaRequest.from(request);
+ expect(kibanaRequest.id).toEqual('fakeId');
+ });
+
+ it('generates a new UUID if request.app property is not present', () => {
+ // Undefined app property
+ const request = httpServerMock.createRawRequest({
+ app: undefined,
+ });
+ const kibanaRequest = KibanaRequest.from(request);
+ expect(kibanaRequest.id).toEqual('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
+ });
+
+ it('generates a new UUID if request.app.requestId property is not present', () => {
+ // Undefined app.requestId property
+ const request = httpServerMock.createRawRequest({
+ app: {},
+ });
+ const kibanaRequest = KibanaRequest.from(request);
+ expect(kibanaRequest.id).toEqual('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
+ });
+ });
+
describe('get all headers', () => {
it('returns all headers', () => {
const request = httpServerMock.createRawRequest({
diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts
index 278bc222b754b..76f8761a7e998 100644
--- a/src/core/server/http/router/request.ts
+++ b/src/core/server/http/router/request.ts
@@ -18,7 +18,8 @@
*/
import { Url } from 'url';
-import { Request, ApplicationState } from 'hapi';
+import uuid from 'uuid';
+import { Request, RouteOptionsApp, ApplicationState } from 'hapi';
import { Observable, fromEvent, merge } from 'rxjs';
import { shareReplay, first, takeUntil } from 'rxjs/operators';
import { RecursiveReadonly } from '@kbn/utility-types';
@@ -34,9 +35,17 @@ const requestSymbol = Symbol('request');
/**
* @internal
*/
-export interface KibanaRouteState extends ApplicationState {
+export interface KibanaRouteOptions extends RouteOptionsApp {
xsrfRequired: boolean;
}
+
+/**
+ * @internal
+ */
+export interface KibanaRequestState extends ApplicationState {
+ requestId: string;
+}
+
/**
* Route options: If 'GET' or 'OPTIONS' method, body options won't be returned.
* @public
@@ -134,6 +143,15 @@ export class KibanaRequest<
return { query, params, body };
}
+ /**
+ * A identifier to identify this request.
+ *
+ * @remarks
+ * Depending on the user's configuration, this value may be sourced from the
+ * incoming request's `X-Opaque-Id` header which is not guaranteed to be unique
+ * per request.
+ */
+ public readonly id: string;
/** a WHATWG URL standard object. */
public readonly url: Url;
/** matched route details */
@@ -171,6 +189,11 @@ export class KibanaRequest<
// until that time we have to expose all the headers
private readonly withoutSecretHeaders: boolean
) {
+ // The `requestId` property will not be populated for requests that are 'faked' by internal systems that leverage
+ // KibanaRequest in conjunction with scoped Elaticcsearch and SavedObjectsClient in order to pass credentials.
+ // In these cases, the id defaults to a newly generated UUID.
+ this.id = (request.app as KibanaRequestState | undefined)?.requestId ?? uuid.v4();
+
this.url = request.url;
this.headers = deepFreeze({ ...request.headers });
this.isSystemRequest =
@@ -220,7 +243,7 @@ export class KibanaRequest<
const options = ({
authRequired: this.getAuthRequired(request),
// some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8
- xsrfRequired: (request.route.settings.app as KibanaRouteState)?.xsrfRequired ?? true,
+ xsrfRequired: (request.route.settings.app as KibanaRouteOptions)?.xsrfRequired ?? true,
tags: request.route.settings.tags || [],
timeout: {
payload: payloadTimeout,
@@ -276,7 +299,11 @@ export class KibanaRequest<
export const ensureRawRequest = (request: KibanaRequest | LegacyRequest) =>
isKibanaRequest(request) ? request[requestSymbol] : request;
-function isKibanaRequest(request: unknown): request is KibanaRequest {
+/**
+ * Checks if an incoming request is a {@link KibanaRequest}
+ * @internal
+ */
+export function isKibanaRequest(request: unknown): request is KibanaRequest {
return request instanceof KibanaRequest;
}
diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts
index bda66e1de8168..c3afae108027e 100644
--- a/src/core/server/http/test_utils.ts
+++ b/src/core/server/http/test_utils.ts
@@ -46,6 +46,10 @@ configService.atPath.mockReturnValue(
whitelist: [],
},
customResponseHeaders: {},
+ requestId: {
+ allowFromAnyIp: true,
+ ipAllowlist: [],
+ },
} as any)
);
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index 382318ea86a34..76bcf5f7df665 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -274,7 +274,9 @@ export {
SavedObjectsUpdateOptions,
SavedObjectsUpdateResponse,
SavedObjectsAddToNamespacesOptions,
+ SavedObjectsAddToNamespacesResponse,
SavedObjectsDeleteFromNamespacesOptions,
+ SavedObjectsDeleteFromNamespacesResponse,
SavedObjectsServiceStart,
SavedObjectsServiceSetup,
SavedObjectStatusMeta,
diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js
index 941c4091a66a7..6d85223d1fc88 100644
--- a/src/core/server/saved_objects/service/lib/repository.test.js
+++ b/src/core/server/saved_objects/service/lib/repository.test.js
@@ -393,14 +393,14 @@ describe('SavedObjectsRepository', () => {
});
describe('returns', () => {
- it(`returns an empty object on success`, async () => {
+ it(`returns all existing and new namespaces on success`, async () => {
const result = await addToNamespacesSuccess(type, id, [newNs1, newNs2]);
- expect(result).toEqual({});
+ expect(result).toEqual({ namespaces: [currentNs1, currentNs2, newNs1, newNs2] });
});
it(`succeeds when adding existing namespaces`, async () => {
const result = await addToNamespacesSuccess(type, id, [currentNs1]);
- expect(result).toEqual({});
+ expect(result).toEqual({ namespaces: [currentNs1, currentNs2] });
});
});
});
@@ -3102,17 +3102,17 @@ describe('SavedObjectsRepository', () => {
});
describe('returns', () => {
- it(`returns an empty object on success (delete)`, async () => {
+ it(`returns an empty namespaces array on success (delete)`, async () => {
const test = async (namespaces) => {
const result = await deleteFromNamespacesSuccess(type, id, namespaces, namespaces);
- expect(result).toEqual({});
+ expect(result).toEqual({ namespaces: [] });
client.delete.mockClear();
};
await test([namespace1]);
await test([namespace1, namespace2]);
});
- it(`returns an empty object on success (update)`, async () => {
+ it(`returns remaining namespaces on success (update)`, async () => {
const test = async (remaining) => {
const currentNamespaces = [namespace1].concat(remaining);
const result = await deleteFromNamespacesSuccess(
@@ -3121,7 +3121,7 @@ describe('SavedObjectsRepository', () => {
[namespace1],
currentNamespaces
);
- expect(result).toEqual({});
+ expect(result).toEqual({ namespaces: remaining });
client.delete.mockClear();
};
await test([namespace2]);
@@ -3132,7 +3132,7 @@ describe('SavedObjectsRepository', () => {
const namespaces = [namespace2];
const currentNamespaces = [namespace1];
const result = await deleteFromNamespacesSuccess(type, id, namespaces, currentNamespaces);
- expect(result).toEqual({});
+ expect(result).toEqual({ namespaces: currentNamespaces });
});
});
});
diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts
index 9f6db446ea195..28d409f7b65bb 100644
--- a/src/core/server/saved_objects/service/lib/repository.ts
+++ b/src/core/server/saved_objects/service/lib/repository.ts
@@ -52,7 +52,9 @@ import {
SavedObjectsBulkUpdateOptions,
SavedObjectsDeleteOptions,
SavedObjectsAddToNamespacesOptions,
+ SavedObjectsAddToNamespacesResponse,
SavedObjectsDeleteFromNamespacesOptions,
+ SavedObjectsDeleteFromNamespacesResponse,
} from '../saved_objects_client';
import {
SavedObject,
@@ -947,7 +949,7 @@ export class SavedObjectsRepository {
id: string,
namespaces: string[],
options: SavedObjectsAddToNamespacesOptions = {}
- ): Promise<{}> {
+ ): Promise {
if (!this._allowedTypes.includes(type)) {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
@@ -996,7 +998,7 @@ export class SavedObjectsRepository {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
- return {};
+ return { namespaces: doc.namespaces };
}
/**
@@ -1009,7 +1011,7 @@ export class SavedObjectsRepository {
id: string,
namespaces: string[],
options: SavedObjectsDeleteFromNamespacesOptions = {}
- ): Promise<{}> {
+ ): Promise {
if (!this._allowedTypes.includes(type)) {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
@@ -1063,7 +1065,7 @@ export class SavedObjectsRepository {
// see "404s from missing index" above
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
- return {};
+ return { namespaces: doc.namespaces };
} else {
// if there are no namespaces remaining, delete the saved object
const { body, statusCode } = await this.client.delete(
@@ -1080,7 +1082,7 @@ export class SavedObjectsRepository {
const deleted = body.result === 'deleted';
if (deleted) {
- return {};
+ return { namespaces: [] };
}
const deleteDocNotFound = body.result === 'not_found';
diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts
index 6a9f4f5143e84..812669ee108a2 100644
--- a/src/core/server/saved_objects/service/saved_objects_client.ts
+++ b/src/core/server/saved_objects/service/saved_objects_client.ts
@@ -135,6 +135,15 @@ export interface SavedObjectsAddToNamespacesOptions extends SavedObjectsBaseOpti
refresh?: MutatingOperationRefreshSetting;
}
+/**
+ *
+ * @public
+ */
+export interface SavedObjectsAddToNamespacesResponse {
+ /** The namespaces the object exists in after this operation is complete. */
+ namespaces: string[];
+}
+
/**
*
* @public
@@ -144,6 +153,15 @@ export interface SavedObjectsDeleteFromNamespacesOptions extends SavedObjectsBas
refresh?: MutatingOperationRefreshSetting;
}
+/**
+ *
+ * @public
+ */
+export interface SavedObjectsDeleteFromNamespacesResponse {
+ /** The namespaces the object exists in after this operation is complete. An empty array indicates the object was deleted. */
+ namespaces: string[];
+}
+
/**
*
* @public
@@ -320,7 +338,7 @@ export class SavedObjectsClient {
id: string,
namespaces: string[],
options: SavedObjectsAddToNamespacesOptions = {}
- ): Promise<{}> {
+ ): Promise {
return await this._repository.addToNamespaces(type, id, namespaces, options);
}
@@ -337,7 +355,7 @@ export class SavedObjectsClient {
id: string,
namespaces: string[],
options: SavedObjectsDeleteFromNamespacesOptions = {}
- ): Promise<{}> {
+ ): Promise {
return await this._repository.deleteFromNamespaces(type, id, namespaces, options);
}
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 01558c1460f1b..772be68f507d5 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -1059,6 +1059,7 @@ export class KibanaRequest(req: Request, routeSchemas?: RouteValidator | RouteValidatorFullConfig
, withoutSecretHeaders?: boolean): KibanaRequest
;
readonly headers: Headers;
+ readonly id: string;
readonly isSystemRequest: boolean;
// (undocumented)
readonly params: Params;
@@ -2022,6 +2023,11 @@ export interface SavedObjectsAddToNamespacesOptions extends SavedObjectsBaseOpti
version?: string;
}
+// @public (undocumented)
+export interface SavedObjectsAddToNamespacesResponse {
+ namespaces: string[];
+}
+
// Warning: (ae-forgotten-export) The symbol "SavedObjectDoc" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "Referencable" needs to be exported by the entry point index.d.ts
//
@@ -2091,13 +2097,13 @@ export interface SavedObjectsBulkUpdateResponse {
export class SavedObjectsClient {
// @internal
constructor(repository: ISavedObjectsRepository);
- addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise<{}>;
+ addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise;
bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>;
bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>;
bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>;
create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>;
delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
- deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>;
+ deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise;
// (undocumented)
static errors: typeof SavedObjectsErrorHelpers;
// (undocumented)
@@ -2193,6 +2199,11 @@ export interface SavedObjectsDeleteFromNamespacesOptions extends SavedObjectsBas
refresh?: MutatingOperationRefreshSetting;
}
+// @public (undocumented)
+export interface SavedObjectsDeleteFromNamespacesResponse {
+ namespaces: string[];
+}
+
// @public (undocumented)
export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions {
refresh?: MutatingOperationRefreshSetting;
@@ -2491,7 +2502,7 @@ export interface SavedObjectsRawDoc {
// @public (undocumented)
export class SavedObjectsRepository {
- addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise<{}>;
+ addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise;
bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>;
bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>;
bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>;
@@ -2502,7 +2513,7 @@ export class SavedObjectsRepository {
static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository;
delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise;
- deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>;
+ deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise;
// (undocumented)
find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>;
get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>;
diff --git a/src/dev/file.ts b/src/dev/file.ts
index 32998d3e776ef..1209cc323c4c3 100644
--- a/src/dev/file.ts
+++ b/src/dev/file.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { dirname, extname, join, relative, resolve, sep } from 'path';
+import { dirname, extname, join, relative, resolve, sep, basename } from 'path';
export class File {
private path: string;
@@ -38,6 +38,12 @@ export class File {
return this.relativePath;
}
+ public getWithoutExtension() {
+ const directory = dirname(this.path);
+ const stem = basename(this.path, this.ext);
+ return new File(resolve(directory, stem));
+ }
+
public isJs() {
return this.ext === '.js';
}
diff --git a/src/dev/i18n/tasks/extract_untracked_translations.ts b/src/dev/i18n/tasks/extract_untracked_translations.ts
index 21ab47641f2f0..928997aced801 100644
--- a/src/dev/i18n/tasks/extract_untracked_translations.ts
+++ b/src/dev/i18n/tasks/extract_untracked_translations.ts
@@ -47,7 +47,7 @@ export async function extractUntrackedMessagesTask({
'**/build/**',
'**/__fixtures__/**',
'**/packages/kbn-i18n/**',
- '**/packages/kbn-plugin-generator/sao_template/**',
+ '**/packages/kbn-plugin-generator/template/**',
'**/packages/kbn-ui-framework/generator-kui/**',
'**/target/**',
'**/test/**',
diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js
index d46b955f6668d..74e1ec5e2b4ed 100644
--- a/src/dev/jest/config.js
+++ b/src/dev/jest/config.js
@@ -78,7 +78,6 @@ export default {
setupFilesAfterEnv: [
'/src/dev/jest/setup/mocks.js',
'/src/dev/jest/setup/react_testing_library.js',
- '/src/dev/jest/setup/default_timeout.js',
],
coverageDirectory: '/target/kibana-coverage/jest',
coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html', 'text'],
diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js
index 19d03e41ac5a9..e22dc03cf57aa 100644
--- a/src/dev/precommit_hook/casing_check_config.js
+++ b/src/dev/precommit_hook/casing_check_config.js
@@ -109,6 +109,14 @@ export const IGNORE_DIRECTORY_GLOBS = [
'packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack',
];
+/**
+ * These patterns identify files which should have the extension stripped
+ * to reveal the actual name that should be checked.
+ *
+ * @type {Array}
+ */
+export const REMOVE_EXTENSION = ['packages/kbn-plugin-generator/template/**/*.ejs'];
+
/**
* DO NOT ADD FILES TO THIS LIST!!
*
diff --git a/src/dev/precommit_hook/check_file_casing.js b/src/dev/precommit_hook/check_file_casing.js
index ec77d9a8bad3c..243818d35a90b 100644
--- a/src/dev/precommit_hook/check_file_casing.js
+++ b/src/dev/precommit_hook/check_file_casing.js
@@ -29,6 +29,7 @@ import {
IGNORE_FILE_GLOBS,
TEMPORARILY_IGNORED_PATHS,
KEBAB_CASE_DIRECTORY_GLOBS,
+ REMOVE_EXTENSION,
} from './casing_check_config';
const NON_SNAKE_CASE_RE = /[A-Z \-]/;
@@ -143,6 +144,10 @@ async function checkForSnakeCase(log, files) {
}
export async function checkFileCasing(log, files) {
+ files = files.map((f) =>
+ matchesAnyGlob(f.getRelativePath(), REMOVE_EXTENSION) ? f.getWithoutExtension() : f
+ );
+
await checkForKebabCase(log, files);
await checkForSnakeCase(log, files);
}
diff --git a/src/legacy/core_plugins/console_legacy/index.ts b/src/legacy/core_plugins/console_legacy/index.ts
deleted file mode 100644
index 82e00a99c6cfd..0000000000000
--- a/src/legacy/core_plugins/console_legacy/index.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { first } from 'rxjs/operators';
-import { head } from 'lodash';
-import url from 'url';
-
-// TODO: Remove this hack once we can get the ES config we need for Console proxy a better way.
-let _legacyEsConfig: any;
-export const readLegacyEsConfig = () => {
- return _legacyEsConfig;
-};
-
-// eslint-disable-next-line import/no-default-export
-export default function (kibana: any) {
- return new kibana.Plugin({
- id: 'console_legacy',
-
- async init(server: any) {
- _legacyEsConfig = await server.newPlatform.__internals.elasticsearch.legacy.config$
- .pipe(first())
- .toPromise();
- },
-
- uiExports: {
- injectDefaultVars: () => ({
- elasticsearchUrl: url.format(
- Object.assign(url.parse(head(_legacyEsConfig.hosts) as any), { auth: false })
- ),
- }),
- },
- } as any);
-}
diff --git a/src/legacy/core_plugins/console_legacy/package.json b/src/legacy/core_plugins/console_legacy/package.json
deleted file mode 100644
index b78807daed959..0000000000000
--- a/src/legacy/core_plugins/console_legacy/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "console_legacy",
- "version": "kibana"
-}
diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js
index 2e30bc5ce05ee..176c5386961a5 100644
--- a/src/legacy/core_plugins/kibana/index.js
+++ b/src/legacy/core_plugins/kibana/index.js
@@ -21,7 +21,6 @@ import Fs from 'fs';
import { promisify } from 'util';
import { getUiSettingDefaults } from './server/ui_setting_defaults';
-import { registerCspCollector } from './server/lib/csp_usage_collector';
const mkdirAsync = promisify(Fs.mkdir);
@@ -53,10 +52,5 @@ export default function (kibana) {
throw err;
}
},
-
- init: async function (server) {
- const { usageCollection } = server.newPlatform.setup.plugins;
- registerCspCollector(usageCollection, server);
- },
});
}
diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
index 5ef1149146afe..b8d48b784dba7 100644
--- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
+++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
@@ -221,17 +221,6 @@ export const npSetup = {
registerFunction: sinon.fake(),
registerRenderer: sinon.fake(),
registerType: sinon.fake(),
- __LEGACY: {
- renderers: {
- register: () => undefined,
- get: () => null,
- },
- getExecutor: () => ({
- interpreter: {
- interpretAst: () => {},
- },
- }),
- },
},
data: {
autocomplete: {
diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts
index ee92eda064aa8..036157a9f3fbc 100644
--- a/src/legacy/ui/public/new_platform/set_services.ts
+++ b/src/legacy/ui/public/new_platform/set_services.ts
@@ -41,7 +41,6 @@ interface NpStart {
export function setSetupServices(npSetup: NpSetup) {
// Services that need to be set in the legacy platform since the legacy data plugin
// which previously provided them has been removed.
- dataServices.setInjectedMetadata(npSetup.core.injectedMetadata);
visualizationsServices.setUISettings(npSetup.core.uiSettings);
visualizationsServices.setUsageCollector(npSetup.plugins.usageCollection);
}
@@ -49,7 +48,6 @@ export function setSetupServices(npSetup: NpSetup) {
export function setStartServices(npStart: NpStart) {
// Services that need to be set in the legacy platform since the legacy data plugin
// which previously provided them has been removed.
- dataServices.setHttp(npStart.core.http);
dataServices.setNotifications(npStart.core.notifications);
dataServices.setOverlays(npStart.core.overlays);
dataServices.setUiSettings(npStart.core.uiSettings);
diff --git a/src/plugins/kibana_utils/public/core/state.ts b/src/plugins/console/common/types/api_responses.ts
similarity index 71%
rename from src/plugins/kibana_utils/public/core/state.ts
rename to src/plugins/console/common/types/api_responses.ts
index 52c4d166d737e..1c8166bbe27f2 100644
--- a/src/plugins/kibana_utils/public/core/state.ts
+++ b/src/plugins/console/common/types/api_responses.ts
@@ -17,7 +17,13 @@
* under the License.
*/
-import { createGetterSetter } from '../../common';
-import { CoreStart } from '../../../../core/public';
-
-export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart');
+export interface EsConfigApiResponse {
+ /**
+ * This is the first host in the hosts array that Kibana is configured to use
+ * to communicate with ES.
+ *
+ * At the moment this is used to power the copy as cURL functionality in Console
+ * to complete the host portion of the URL.
+ */
+ host?: string;
+}
diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
index 880069d8ebc7a..fc88b31711b23 100644
--- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
+++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
@@ -67,9 +67,8 @@ const inputId = 'ConAppInputTextarea';
function EditorUI({ initialTextValue }: EditorProps) {
const {
- services: { history, notifications, settings: settingsService },
+ services: { history, notifications, settings: settingsService, esHostService },
docLinkVersion,
- elasticsearchUrl,
} = useServicesContext();
const { settings } = useEditorReadContext();
@@ -232,7 +231,7 @@ function EditorUI({ initialTextValue }: EditorProps) {
{
- return editorInstanceRef.current!.getRequestsAsCURL(elasticsearchUrl);
+ return editorInstanceRef.current!.getRequestsAsCURL(esHostService.getHost());
}}
getDocumentation={() => {
return getDocumentation(editorInstanceRef.current!, docLinkVersion);
diff --git a/src/plugins/console/public/application/contexts/services_context.mock.ts b/src/plugins/console/public/application/contexts/services_context.mock.ts
index ae8d15a890782..ba982d3f50cfb 100644
--- a/src/plugins/console/public/application/contexts/services_context.mock.ts
+++ b/src/plugins/console/public/application/contexts/services_context.mock.ts
@@ -17,21 +17,27 @@
* under the License.
*/
import { notificationServiceMock } from '../../../../../core/public/mocks';
+import { httpServiceMock } from '../../../../../core/public/mocks';
+
import { HistoryMock } from '../../services/history.mock';
import { SettingsMock } from '../../services/settings.mock';
import { StorageMock } from '../../services/storage.mock';
+import { createApi, createEsHostService } from '../lib';
import { ContextValue } from './services_context';
export const serviceContextMock = {
create: (): ContextValue => {
const storage = new StorageMock({} as any, 'test');
+ const http = httpServiceMock.createSetupContract();
+ const api = createApi({ http });
+ const esHostService = createEsHostService({ api });
(storage.keys as jest.Mock).mockImplementation(() => []);
return {
- elasticsearchUrl: 'test',
services: {
trackUiMetric: { count: () => {}, load: () => {} },
storage,
+ esHostService,
settings: new SettingsMock(storage),
history: new HistoryMock(storage),
notifications: notificationServiceMock.createSetupContract(),
diff --git a/src/plugins/console/public/application/contexts/services_context.tsx b/src/plugins/console/public/application/contexts/services_context.tsx
index 3d4ac3291c5ac..e2f01a152b27b 100644
--- a/src/plugins/console/public/application/contexts/services_context.tsx
+++ b/src/plugins/console/public/application/contexts/services_context.tsx
@@ -17,22 +17,25 @@
* under the License.
*/
-import React, { createContext, useContext } from 'react';
+import React, { createContext, useContext, useEffect } from 'react';
import { NotificationsSetup } from 'kibana/public';
-import { History, Storage, Settings } from '../../services';
+import { History, Settings, Storage } from '../../services';
import { ObjectStorageClient } from '../../../common/types';
import { MetricsTracker } from '../../types';
+import { EsHostService } from '../lib';
+
+interface ContextServices {
+ history: History;
+ storage: Storage;
+ settings: Settings;
+ notifications: NotificationsSetup;
+ objectStorageClient: ObjectStorageClient;
+ trackUiMetric: MetricsTracker;
+ esHostService: EsHostService;
+}
export interface ContextValue {
- services: {
- history: History;
- storage: Storage;
- settings: Settings;
- notifications: NotificationsSetup;
- objectStorageClient: ObjectStorageClient;
- trackUiMetric: MetricsTracker;
- };
- elasticsearchUrl: string;
+ services: ContextServices;
docLinkVersion: string;
}
@@ -44,6 +47,11 @@ interface ContextProps {
const ServicesContext = createContext(null as any);
export function ServicesContextProvider({ children, value }: ContextProps) {
+ useEffect(() => {
+ // Fire and forget, we attempt to init the host service once.
+ value.services.esHostService.init();
+ }, [value.services.esHostService]);
+
return {children} ;
}
diff --git a/src/plugins/console/public/application/hooks/README.md b/src/plugins/console/public/application/hooks/README.md
new file mode 100644
index 0000000000000..10057193560e9
--- /dev/null
+++ b/src/plugins/console/public/application/hooks/README.md
@@ -0,0 +1,5 @@
+## Notes
+
+* Do not add any code directly to this directory. This code should be moved to the neighbouring `lib` directory to be in line with future ES UI plugin patterns.
+
+* The `es.send` method uses $.ajax under the hood and needs to be refactored to use the new platform-provided http client.
\ No newline at end of file
diff --git a/src/plugins/console/public/application/index.tsx b/src/plugins/console/public/application/index.tsx
index 051eaea27a7de..0a5a502eb5062 100644
--- a/src/plugins/console/public/application/index.tsx
+++ b/src/plugins/console/public/application/index.tsx
@@ -19,19 +19,20 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
-import { NotificationsSetup } from 'src/core/public';
+import { HttpSetup, NotificationsSetup } from 'src/core/public';
import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts';
import { Main } from './containers';
import { createStorage, createHistory, createSettings } from '../services';
import * as localStorageObjectClient from '../lib/local_storage_object_client';
import { createUsageTracker } from '../services/tracker';
import { UsageCollectionSetup } from '../../../usage_collection/public';
+import { createApi, createEsHostService } from './lib';
export interface BootDependencies {
+ http: HttpSetup;
docLinkVersion: string;
I18nContext: any;
notifications: NotificationsSetup;
- elasticsearchUrl: string;
usageCollection?: UsageCollectionSetup;
element: HTMLElement;
}
@@ -40,9 +41,9 @@ export function renderApp({
I18nContext,
notifications,
docLinkVersion,
- elasticsearchUrl,
usageCollection,
element,
+ http,
}: BootDependencies) {
const trackUiMetric = createUsageTracker(usageCollection);
trackUiMetric.load('opened_app');
@@ -54,14 +55,16 @@ export function renderApp({
const history = createHistory({ storage });
const settings = createSettings({ storage });
const objectStorageClient = localStorageObjectClient.create(storage);
+ const api = createApi({ http });
+ const esHostService = createEsHostService({ api });
render(
) => {
- const savedObjectsClient: KUSavedObjectClient = {
- get: (...args) => getCoreStart().savedObjects.client.get(...args),
- };
+export type Api = ReturnType;
- return savedObjectsClient;
+export const createApi = ({ http }: Dependencies) => {
+ return {
+ getEsConfig: () => {
+ return sendRequest(http, {
+ path: '/api/console/es_config',
+ method: 'get',
+ });
+ },
+ };
};
diff --git a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts b/src/plugins/console/public/application/lib/es_host_service.ts
similarity index 50%
rename from src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts
rename to src/plugins/console/public/application/lib/es_host_service.ts
index c5b23bdf0055f..887f270a24687 100644
--- a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts
+++ b/src/plugins/console/public/application/lib/es_host_service.ts
@@ -17,23 +17,38 @@
* under the License.
*/
-import { createKibanaUtilsCore } from './create_kibana_utils_core';
-import { CoreStart } from 'kibana/public';
+import { Api } from './api';
-describe('createKibanaUtilsCore', () => {
- it('should allows to work with multiple instances', () => {
- const core1 = {} as CoreStart;
- const core2 = {} as CoreStart;
+/**
+ * Very simple state for holding the current ES host.
+ *
+ * This is used to power the copy as cURL functionality.
+ */
+export class EsHostService {
+ private host = 'http://localhost:9200';
+
+ constructor(private readonly api: Api) {}
- const { setCoreStart: setCoreStart1, getCoreStart: getCoreStart1 } = createKibanaUtilsCore();
- const { setCoreStart: setCoreStart2, getCoreStart: getCoreStart2 } = createKibanaUtilsCore();
+ private setHost(host: string): void {
+ this.host = host;
+ }
- setCoreStart1(core1);
- setCoreStart2(core2);
+ /**
+ * Initialize the host value based on the value set on the server.
+ *
+ * This call is necessary because this value can only be retrieved at
+ * runtime.
+ */
+ public async init() {
+ const { data } = await this.api.getEsConfig();
+ if (data && data.host) {
+ this.setHost(data.host);
+ }
+ }
- expect(getCoreStart1()).toBe(core1);
- expect(getCoreStart2()).toBe(core2);
+ public getHost(): string {
+ return this.host;
+ }
+}
- expect(getCoreStart1() !== getCoreStart2()).toBeTruthy();
- });
-});
+export const createEsHostService = ({ api }: { api: Api }) => new EsHostService(api);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/charts.js b/src/plugins/console/public/application/lib/index.ts
similarity index 84%
rename from src/plugins/vis_type_timeseries/public/application/components/lib/charts.js
rename to src/plugins/console/public/application/lib/index.ts
index 2495026304be8..1ba99cc607269 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/lib/charts.js
+++ b/src/plugins/console/public/application/lib/index.ts
@@ -17,6 +17,5 @@
* under the License.
*/
-import { uniq, map, size, flow } from 'lodash';
-
-export const areFieldsDifferent = (name) => (series) => flow(uniq, size)(map(series, name)) > 1;
+export { createApi, Api } from './api';
+export { createEsHostService, EsHostService } from './es_host_service';
diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts
index 851dc7a063d7b..03b65a8bd145c 100644
--- a/src/plugins/console/public/plugin.ts
+++ b/src/plugins/console/public/plugin.ts
@@ -25,7 +25,7 @@ import { AppSetupUIPluginDependencies } from './types';
export class ConsoleUIPlugin implements Plugin {
public setup(
- { notifications, getStartServices }: CoreSetup,
+ { notifications, getStartServices, http }: CoreSetup,
{ devTools, home, usageCollection }: AppSetupUIPluginDependencies
) {
home.featureCatalogue.register({
@@ -53,23 +53,17 @@ export class ConsoleUIPlugin implements Plugin ({
import { duration } from 'moment';
import { ProxyConfigCollection } from '../../lib';
-import { CreateHandlerDependencies } from '../../routes/api/console/proxy/create_handler';
-import { coreMock } from '../../../../../core/server/mocks';
+import { RouteDependencies, ProxyDependencies } from '../../routes';
+import { EsLegacyConfigService, SpecDefinitionsService } from '../../services';
+import { coreMock, httpServiceMock } from '../../../../../core/server/mocks';
-export const getProxyRouteHandlerDeps = ({
- proxyConfigCollection = new ProxyConfigCollection([]),
- pathFilters = [/.*/],
- readLegacyESConfig = () => ({
+const defaultProxyValue = Object.freeze({
+ readLegacyESConfig: async () => ({
requestTimeout: duration(30000),
customHeaders: {},
requestHeadersWhitelist: [],
hosts: ['http://localhost:9200'],
}),
- log = coreMock.createPluginInitializerContext().logger.get(),
-}: Partial): CreateHandlerDependencies => ({
- proxyConfigCollection,
- pathFilters,
- readLegacyESConfig,
- log,
+ pathFilters: [/.*/],
+ proxyConfigCollection: new ProxyConfigCollection([]),
});
+
+interface MockDepsArgument extends Partial> {
+ proxy?: Partial;
+}
+
+export const getProxyRouteHandlerDeps = ({
+ proxy,
+ log = coreMock.createPluginInitializerContext().logger.get(),
+ router = httpServiceMock.createSetupContract().createRouter(),
+}: MockDepsArgument): RouteDependencies => {
+ const services: RouteDependencies['services'] = {
+ esLegacyConfigService: new EsLegacyConfigService(),
+ specDefinitionService: new SpecDefinitionsService(),
+ };
+
+ return {
+ services,
+ router,
+ proxy: proxy
+ ? {
+ ...defaultProxyValue,
+ ...proxy,
+ }
+ : defaultProxyValue,
+ log,
+ };
+};
diff --git a/src/plugins/console/server/__tests__/proxy_route/params.test.ts b/src/plugins/console/server/__tests__/proxy_route/params.test.ts
index 1ab9c3ae789cc..e1c5295f6d30f 100644
--- a/src/plugins/console/server/__tests__/proxy_route/params.test.ts
+++ b/src/plugins/console/server/__tests__/proxy_route/params.test.ts
@@ -36,7 +36,7 @@ describe('Console Proxy Route', () => {
describe('no matches', () => {
it('rejects with 403', async () => {
handler = createHandler(
- getProxyRouteHandlerDeps({ pathFilters: [/^\/foo\//, /^\/bar\//] })
+ getProxyRouteHandlerDeps({ proxy: { pathFilters: [/^\/foo\//, /^\/bar\//] } })
);
const { status } = await handler(
@@ -51,7 +51,7 @@ describe('Console Proxy Route', () => {
describe('one match', () => {
it('allows the request', async () => {
handler = createHandler(
- getProxyRouteHandlerDeps({ pathFilters: [/^\/foo\//, /^\/bar\//] })
+ getProxyRouteHandlerDeps({ proxy: { pathFilters: [/^\/foo\//, /^\/bar\//] } })
);
(requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo'));
@@ -68,7 +68,9 @@ describe('Console Proxy Route', () => {
});
describe('all match', () => {
it('allows the request', async () => {
- handler = createHandler(getProxyRouteHandlerDeps({ pathFilters: [/^\/foo\//] }));
+ handler = createHandler(
+ getProxyRouteHandlerDeps({ proxy: { pathFilters: [/^\/foo\//] } })
+ );
(requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo'));
diff --git a/src/plugins/console/server/__tests__/proxy_route/proxy_fallback.test.ts b/src/plugins/console/server/__tests__/proxy_route/proxy_fallback.test.ts
index b226bad11a01a..fc5233d0f833d 100644
--- a/src/plugins/console/server/__tests__/proxy_route/proxy_fallback.test.ts
+++ b/src/plugins/console/server/__tests__/proxy_route/proxy_fallback.test.ts
@@ -38,12 +38,14 @@ describe('Console Proxy Route', () => {
const handler = createHandler(
getProxyRouteHandlerDeps({
- readLegacyESConfig: () => ({
- requestTimeout: duration(30000),
- customHeaders: {},
- requestHeadersWhitelist: [],
- hosts: ['http://localhost:9201', 'http://localhost:9202', 'http://localhost:9203'],
- }),
+ proxy: {
+ readLegacyESConfig: async () => ({
+ requestTimeout: duration(30000),
+ customHeaders: {},
+ requestHeadersWhitelist: [],
+ hosts: ['http://localhost:9201', 'http://localhost:9202', 'http://localhost:9203'],
+ }),
+ },
})
);
diff --git a/src/plugins/console/server/plugin.ts b/src/plugins/console/server/plugin.ts
index eedd1541e8898..a76a35f8146c9 100644
--- a/src/plugins/console/server/plugin.ts
+++ b/src/plugins/console/server/plugin.ts
@@ -19,13 +19,12 @@
import { first } from 'rxjs/operators';
import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server';
-import { readLegacyEsConfig } from '../../../legacy/core_plugins/console_legacy';
-
import { ProxyConfigCollection } from './lib';
-import { SpecDefinitionsService } from './services';
+import { SpecDefinitionsService, EsLegacyConfigService } from './services';
import { ConfigType } from './config';
-import { registerProxyRoute } from './routes/api/console/proxy';
-import { registerSpecDefinitionsRoute } from './routes/api/console/spec_definitions';
+
+import { registerRoutes } from './routes';
+
import { ESConfigForProxy, ConsoleSetup, ConsoleStart } from './types';
export class ConsoleServerPlugin implements Plugin {
@@ -33,11 +32,13 @@ export class ConsoleServerPlugin implements Plugin {
specDefinitionsService = new SpecDefinitionsService();
+ esLegacyConfigService = new EsLegacyConfigService();
+
constructor(private readonly ctx: PluginInitializerContext) {
this.log = this.ctx.logger.get();
}
- async setup({ http, capabilities, getStartServices }: CoreSetup) {
+ async setup({ http, capabilities, getStartServices, elasticsearch }: CoreSetup) {
capabilities.registerProvider(() => ({
dev_tools: {
show: true,
@@ -46,30 +47,31 @@ export class ConsoleServerPlugin implements Plugin {
}));
const config = await this.ctx.config.create().pipe(first()).toPromise();
-
- const { elasticsearch } = await this.ctx.config.legacy.globalConfig$.pipe(first()).toPromise();
-
+ const globalConfig = await this.ctx.config.legacy.globalConfig$.pipe(first()).toPromise();
const proxyPathFilters = config.proxyFilter.map((str: string) => new RegExp(str));
+ this.esLegacyConfigService.setup(elasticsearch.legacy.config$);
+
const router = http.createRouter();
- registerProxyRoute({
+ registerRoutes({
+ router,
log: this.log,
- proxyConfigCollection: new ProxyConfigCollection(config.proxyConfig),
- readLegacyESConfig: (): ESConfigForProxy => {
- const legacyConfig = readLegacyEsConfig();
- return {
- ...elasticsearch,
- ...legacyConfig,
- };
+ services: {
+ esLegacyConfigService: this.esLegacyConfigService,
+ specDefinitionService: this.specDefinitionsService,
+ },
+ proxy: {
+ proxyConfigCollection: new ProxyConfigCollection(config.proxyConfig),
+ readLegacyESConfig: async (): Promise => {
+ const legacyConfig = await this.esLegacyConfigService.readConfig();
+ return {
+ ...globalConfig.elasticsearch,
+ ...legacyConfig,
+ };
+ },
+ pathFilters: proxyPathFilters,
},
- pathFilters: proxyPathFilters,
- router,
- });
-
- registerSpecDefinitionsRoute({
- router,
- services: { specDefinitions: this.specDefinitionsService },
});
return {
@@ -82,4 +84,8 @@ export class ConsoleServerPlugin implements Plugin {
...this.specDefinitionsService.start(),
};
}
+
+ stop() {
+ this.esLegacyConfigService.stop();
+ }
}
diff --git a/src/plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/plugins/console/server/routes/api/console/es_config/index.ts
similarity index 62%
rename from src/plugins/vis_type_metric/public/metric_vis_type.test.ts
rename to src/plugins/console/server/routes/api/console/es_config/index.ts
index 636118c692aaa..a115a6b32ad01 100644
--- a/src/plugins/vis_type_metric/public/metric_vis_type.test.ts
+++ b/src/plugins/console/server/routes/api/console/es_config/index.ts
@@ -17,13 +17,17 @@
* under the License.
*/
-import { createMetricVisTypeDefinition } from './metric_vis_type';
-import { MetricVisComponent } from './components/metric_vis_component';
+import { EsConfigApiResponse } from '../../../../../common/types/api_responses';
+import { RouteDependencies } from '../../../';
-describe('metric_vis - createMetricVisTypeDefinition', () => {
- it('has metric vis component set', () => {
- const def = createMetricVisTypeDefinition();
+export const registerEsConfigRoute = ({ router, services }: RouteDependencies): void => {
+ router.get({ path: '/api/console/es_config', validate: false }, async (ctx, req, res) => {
+ const {
+ hosts: [host],
+ } = await services.esLegacyConfigService.readConfig();
- expect(def.visConfig.component).toBe(MetricVisComponent);
+ const body: EsConfigApiResponse = { host };
+
+ return res.ok({ body });
});
-});
+};
diff --git a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts
index a16fb1dadfbcf..f6d9bcb77ddda 100644
--- a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts
+++ b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts
@@ -21,7 +21,7 @@ import { Agent, IncomingMessage } from 'http';
import * as url from 'url';
import { pick, trimStart, trimEnd } from 'lodash';
-import { KibanaRequest, Logger, RequestHandler } from 'kibana/server';
+import { KibanaRequest, RequestHandler } from 'kibana/server';
import { ESConfigForProxy } from '../../../../types';
import {
@@ -31,19 +31,14 @@ import {
setHeaders,
} from '../../../../lib';
-import { Body, Query } from './validation_config';
-
// TODO: find a better way to get information from the request like remoteAddress and remotePort
// for forwarding.
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { ensureRawRequest } from '../../../../../../../core/server/http/router';
-export interface CreateHandlerDependencies {
- log: Logger;
- readLegacyESConfig: () => ESConfigForProxy;
- pathFilters: RegExp[];
- proxyConfigCollection: ProxyConfigCollection;
-}
+import { RouteDependencies } from '../../../';
+
+import { Body, Query } from './validation_config';
function toURL(base: string, path: string) {
const urlResult = new url.URL(`${trimEnd(base, '/')}/${trimStart(path, '/')}`);
@@ -120,14 +115,8 @@ function getProxyHeaders(req: KibanaRequest) {
export const createHandler = ({
log,
- readLegacyESConfig,
- pathFilters,
- proxyConfigCollection,
-}: CreateHandlerDependencies): RequestHandler => async (
- ctx,
- request,
- response
-) => {
+ proxy: { readLegacyESConfig, pathFilters, proxyConfigCollection },
+}: RouteDependencies): RequestHandler => async (ctx, request, response) => {
const { body, query } = request;
const { path, method } = query;
@@ -140,7 +129,7 @@ export const createHandler = ({
});
}
- const legacyConfig = readLegacyESConfig();
+ const legacyConfig = await readLegacyESConfig();
const { hosts } = legacyConfig;
let esIncomingMessage: IncomingMessage;
diff --git a/src/plugins/console/server/routes/api/console/proxy/index.ts b/src/plugins/console/server/routes/api/console/proxy/index.ts
index 5f7df1d7cf66b..5841671c340bd 100644
--- a/src/plugins/console/server/routes/api/console/proxy/index.ts
+++ b/src/plugins/console/server/routes/api/console/proxy/index.ts
@@ -17,17 +17,13 @@
* under the License.
*/
-import { IRouter } from 'kibana/server';
import { routeValidationConfig } from './validation_config';
-import { createHandler, CreateHandlerDependencies } from './create_handler';
+import { createHandler } from './create_handler';
-export const registerProxyRoute = (
- deps: {
- router: IRouter;
- } & CreateHandlerDependencies
-) => {
- const { router, ...handlerDeps } = deps;
- router.post(
+import { RouteDependencies } from '../../../';
+
+export const registerProxyRoute = (deps: RouteDependencies) => {
+ deps.router.post(
{
path: '/api/console/proxy',
options: {
@@ -39,6 +35,6 @@ export const registerProxyRoute = (
},
validate: routeValidationConfig,
},
- createHandler(handlerDeps)
+ createHandler(deps)
);
};
diff --git a/src/plugins/console/server/routes/api/console/spec_definitions/index.ts b/src/plugins/console/server/routes/api/console/spec_definitions/index.ts
index 5c7e679cd0d35..a179c36364e26 100644
--- a/src/plugins/console/server/routes/api/console/spec_definitions/index.ts
+++ b/src/plugins/console/server/routes/api/console/spec_definitions/index.ts
@@ -16,8 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { IRouter, RequestHandler } from 'kibana/server';
-import { SpecDefinitionsService } from '../../../../services';
+import { RequestHandler } from 'kibana/server';
+import { RouteDependencies } from '../../../';
interface SpecDefinitionsRouteResponse {
es: {
@@ -27,16 +27,10 @@ interface SpecDefinitionsRouteResponse {
};
}
-export const registerSpecDefinitionsRoute = ({
- router,
- services,
-}: {
- router: IRouter;
- services: { specDefinitions: SpecDefinitionsService };
-}) => {
+export const registerSpecDefinitionsRoute = ({ router, services }: RouteDependencies) => {
const handler: RequestHandler = async (ctx, request, response) => {
const specResponse: SpecDefinitionsRouteResponse = {
- es: services.specDefinitions.asJson(),
+ es: services.specDefinitionService.asJson(),
};
return response.ok({
diff --git a/src/plugins/console/server/routes/index.ts b/src/plugins/console/server/routes/index.ts
new file mode 100644
index 0000000000000..cbd1cef7b36e3
--- /dev/null
+++ b/src/plugins/console/server/routes/index.ts
@@ -0,0 +1,50 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { IRouter, Logger } from 'kibana/server';
+
+import { EsLegacyConfigService, SpecDefinitionsService } from '../services';
+import { ESConfigForProxy } from '../types';
+import { ProxyConfigCollection } from '../lib';
+
+import { registerEsConfigRoute } from './api/console/es_config';
+import { registerProxyRoute } from './api/console/proxy';
+import { registerSpecDefinitionsRoute } from './api/console/spec_definitions';
+
+export interface ProxyDependencies {
+ readLegacyESConfig: () => Promise;
+ pathFilters: RegExp[];
+ proxyConfigCollection: ProxyConfigCollection;
+}
+
+export interface RouteDependencies {
+ router: IRouter;
+ log: Logger;
+ proxy: ProxyDependencies;
+ services: {
+ esLegacyConfigService: EsLegacyConfigService;
+ specDefinitionService: SpecDefinitionsService;
+ };
+}
+
+export const registerRoutes = (dependencies: RouteDependencies) => {
+ registerEsConfigRoute(dependencies);
+ registerProxyRoute(dependencies);
+ registerSpecDefinitionsRoute(dependencies);
+};
diff --git a/src/plugins/console/server/services/es_legacy_config_service.ts b/src/plugins/console/server/services/es_legacy_config_service.ts
new file mode 100644
index 0000000000000..37928839b1846
--- /dev/null
+++ b/src/plugins/console/server/services/es_legacy_config_service.ts
@@ -0,0 +1,64 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Observable, Subscription } from 'rxjs';
+import { first } from 'rxjs/operators';
+import { ElasticsearchConfig } from 'kibana/server';
+
+export class EsLegacyConfigService {
+ /**
+ * The elasticsearch config value at a given point in time.
+ */
+ private config?: ElasticsearchConfig;
+
+ /**
+ * An observable that emits elasticsearch config.
+ */
+ private config$?: Observable;
+
+ /**
+ * A reference to the subscription to the elasticsearch observable
+ */
+ private configSub?: Subscription;
+
+ setup(config$: Observable) {
+ this.config$ = config$;
+ this.configSub = this.config$.subscribe((config) => {
+ this.config = config;
+ });
+ }
+
+ stop() {
+ if (this.configSub) {
+ this.configSub.unsubscribe();
+ }
+ }
+
+ async readConfig(): Promise {
+ if (!this.config$) {
+ throw new Error('Could not read elasticsearch config, this service has not been setup!');
+ }
+
+ if (!this.config) {
+ return this.config$.pipe(first()).toPromise();
+ }
+
+ return this.config;
+ }
+}
diff --git a/src/plugins/console/server/services/index.ts b/src/plugins/console/server/services/index.ts
index c8dfeccd23070..c9d0b8b858150 100644
--- a/src/plugins/console/server/services/index.ts
+++ b/src/plugins/console/server/services/index.ts
@@ -17,4 +17,6 @@
* under the License.
*/
+export { EsLegacyConfigService } from './es_legacy_config_service';
+
export { SpecDefinitionsService } from './spec_definitions_service';
diff --git a/src/plugins/console/server/types.ts b/src/plugins/console/server/types.ts
index 4f026555ada7b..5dc8322c23ea9 100644
--- a/src/plugins/console/server/types.ts
+++ b/src/plugins/console/server/types.ts
@@ -38,8 +38,8 @@ export interface ESConfigForProxy {
requestTimeout: Duration;
ssl?: {
verificationMode: 'none' | 'certificate' | 'full';
- certificateAuthorities: string[] | string;
alwaysPresentCertificate: boolean;
+ certificateAuthorities?: string[];
certificate?: string;
key?: string;
keyPassphrase?: string;
diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx
new file mode 100644
index 0000000000000..9fa7fff9ad087
--- /dev/null
+++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx
@@ -0,0 +1,162 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { isErrorEmbeddable, IContainer, ReferenceOrValueEmbeddable } from '../../embeddable_plugin';
+import { DashboardContainer } from '../embeddable';
+import { getSampleDashboardInput } from '../test_helpers';
+import {
+ CONTACT_CARD_EMBEDDABLE,
+ ContactCardEmbeddableFactory,
+ ContactCardEmbeddable,
+ ContactCardEmbeddableInput,
+ ContactCardEmbeddableOutput,
+} from '../../embeddable_plugin_test_samples';
+import { coreMock } from '../../../../../core/public/mocks';
+import { CoreStart } from 'kibana/public';
+import { AddToLibraryAction } from '.';
+import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
+import { ViewMode } from '../../../../embeddable/public';
+
+const { setup, doStart } = embeddablePluginMock.createInstance();
+setup.registerEmbeddableFactory(
+ CONTACT_CARD_EMBEDDABLE,
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
+);
+const start = doStart();
+
+let container: DashboardContainer;
+let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable;
+let coreStart: CoreStart;
+beforeEach(async () => {
+ coreStart = coreMock.createStart();
+
+ const containerOptions = {
+ ExitFullScreenButton: () => null,
+ SavedObjectFinder: () => null,
+ application: {} as any,
+ embeddable: start,
+ inspector: {} as any,
+ notifications: {} as any,
+ overlays: coreStart.overlays,
+ savedObjectMetaData: {} as any,
+ uiActions: {} as any,
+ };
+
+ container = new DashboardContainer(getSampleDashboardInput(), containerOptions);
+
+ const contactCardEmbeddable = await container.addNewEmbeddable<
+ ContactCardEmbeddableInput,
+ ContactCardEmbeddableOutput,
+ ContactCardEmbeddable
+ >(CONTACT_CARD_EMBEDDABLE, {
+ firstName: 'Kibanana',
+ });
+
+ if (isErrorEmbeddable(contactCardEmbeddable)) {
+ throw new Error('Failed to create embeddable');
+ } else {
+ embeddable = embeddablePluginMock.mockRefOrValEmbeddable<
+ ContactCardEmbeddable,
+ ContactCardEmbeddableInput
+ >(contactCardEmbeddable, {
+ mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: contactCardEmbeddable.id },
+ mockedByValueInput: { firstName: 'Kibanana', id: contactCardEmbeddable.id },
+ });
+ embeddable.updateInput({ viewMode: ViewMode.EDIT });
+ }
+});
+
+test('Add to library is compatible when embeddable on dashboard has value type input', async () => {
+ const action = new AddToLibraryAction();
+ embeddable.updateInput(await embeddable.getInputAsValueType());
+ expect(await action.isCompatible({ embeddable })).toBe(true);
+});
+
+test('Add to library is not compatible when embeddable input is by reference', async () => {
+ const action = new AddToLibraryAction();
+ embeddable.updateInput(await embeddable.getInputAsRefType());
+ expect(await action.isCompatible({ embeddable })).toBe(false);
+});
+
+test('Add to library is not compatible when view mode is set to view', async () => {
+ const action = new AddToLibraryAction();
+ embeddable.updateInput(await embeddable.getInputAsRefType());
+ embeddable.updateInput({ viewMode: ViewMode.VIEW });
+ expect(await action.isCompatible({ embeddable })).toBe(false);
+});
+
+test('Add to library is not compatible when embeddable is not in a dashboard container', async () => {
+ let orphanContactCard = await container.addNewEmbeddable<
+ ContactCardEmbeddableInput,
+ ContactCardEmbeddableOutput,
+ ContactCardEmbeddable
+ >(CONTACT_CARD_EMBEDDABLE, {
+ firstName: 'Orphan',
+ });
+ orphanContactCard = embeddablePluginMock.mockRefOrValEmbeddable<
+ ContactCardEmbeddable,
+ ContactCardEmbeddableInput
+ >(orphanContactCard, {
+ mockedByReferenceInput: { savedObjectId: 'test', id: orphanContactCard.id },
+ mockedByValueInput: { firstName: 'Kibanana', id: orphanContactCard.id },
+ });
+ const action = new AddToLibraryAction();
+ expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false);
+});
+
+test('Add to library replaces embeddableId but retains panel count', async () => {
+ const dashboard = embeddable.getRoot() as IContainer;
+ const originalPanelCount = Object.keys(dashboard.getInput().panels).length;
+ const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels));
+ const action = new AddToLibraryAction();
+ await action.execute({ embeddable });
+ expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount);
+
+ const newPanelId = Object.keys(container.getInput().panels).find(
+ (key) => !originalPanelKeySet.has(key)
+ );
+ expect(newPanelId).toBeDefined();
+ const newPanel = container.getInput().panels[newPanelId!];
+ expect(newPanel.type).toEqual(embeddable.type);
+});
+
+test('Add to library returns reference type input', async () => {
+ const complicatedAttributes = {
+ attribute1: 'The best attribute',
+ attribute2: 22,
+ attribute3: ['array', 'of', 'strings'],
+ attribute4: { nestedattribute: 'hello from the nest' },
+ };
+
+ embeddable = embeddablePluginMock.mockRefOrValEmbeddable(embeddable, {
+ mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: embeddable.id },
+ mockedByValueInput: { attributes: complicatedAttributes, id: embeddable.id },
+ });
+ const dashboard = embeddable.getRoot() as IContainer;
+ const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels));
+ const action = new AddToLibraryAction();
+ await action.execute({ embeddable });
+ const newPanelId = Object.keys(container.getInput().panels).find(
+ (key) => !originalPanelKeySet.has(key)
+ );
+ expect(newPanelId).toBeDefined();
+ const newPanel = container.getInput().panels[newPanelId!];
+ expect(newPanel.type).toEqual(embeddable.type);
+ expect(newPanel.explicitInput.attributes).toBeUndefined();
+ expect(newPanel.explicitInput.savedObjectId).toBe('testSavedObjectId');
+});
diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx
new file mode 100644
index 0000000000000..3cc1a8a1dffe1
--- /dev/null
+++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx
@@ -0,0 +1,93 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import _ from 'lodash';
+import uuid from 'uuid';
+import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin';
+import { ViewMode, PanelState, IEmbeddable } from '../../embeddable_plugin';
+import {
+ PanelNotFoundError,
+ EmbeddableInput,
+ isReferenceOrValueEmbeddable,
+} from '../../../../embeddable/public';
+import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..';
+
+export const ACTION_ADD_TO_LIBRARY = 'addToFromLibrary';
+
+export interface AddToLibraryActionContext {
+ embeddable: IEmbeddable;
+}
+
+export class AddToLibraryAction implements ActionByType {
+ public readonly type = ACTION_ADD_TO_LIBRARY;
+ public readonly id = ACTION_ADD_TO_LIBRARY;
+ public order = 15;
+
+ constructor() {}
+
+ public getDisplayName({ embeddable }: AddToLibraryActionContext) {
+ if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
+ throw new IncompatibleActionError();
+ }
+ return i18n.translate('dashboard.panel.AddToLibrary', {
+ defaultMessage: 'Add to library',
+ });
+ }
+
+ public getIconType({ embeddable }: AddToLibraryActionContext) {
+ if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
+ throw new IncompatibleActionError();
+ }
+ return 'folderCheck';
+ }
+
+ public async isCompatible({ embeddable }: AddToLibraryActionContext) {
+ return Boolean(
+ embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
+ embeddable.getRoot() &&
+ embeddable.getRoot().isContainer &&
+ embeddable.getRoot().type === DASHBOARD_CONTAINER_TYPE &&
+ isReferenceOrValueEmbeddable(embeddable) &&
+ !embeddable.inputIsRefType(embeddable.getInput())
+ );
+ }
+
+ public async execute({ embeddable }: AddToLibraryActionContext) {
+ if (!isReferenceOrValueEmbeddable(embeddable)) {
+ throw new IncompatibleActionError();
+ }
+
+ const newInput = await embeddable.getInputAsRefType();
+
+ embeddable.updateInput(newInput);
+
+ const dashboard = embeddable.getRoot() as DashboardContainer;
+ const panelToReplace = dashboard.getInput().panels[embeddable.id] as DashboardPanelState;
+ if (!panelToReplace) {
+ throw new PanelNotFoundError();
+ }
+
+ const newPanel: PanelState = {
+ type: embeddable.type,
+ explicitInput: { ...newInput, id: uuid.v4() },
+ };
+ dashboard.replacePanel(panelToReplace, newPanel);
+ }
+}
diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts
index be183976c676f..4343a3409b696 100644
--- a/src/plugins/dashboard/public/application/actions/index.ts
+++ b/src/plugins/dashboard/public/application/actions/index.ts
@@ -33,7 +33,12 @@ export {
ACTION_CLONE_PANEL,
} from './clone_panel_action';
export {
+ AddToLibraryAction,
+ AddToLibraryActionContext,
+ ACTION_ADD_TO_LIBRARY,
+} from './add_to_library_action';
+export {
+ UnlinkFromLibraryAction,
UnlinkFromLibraryActionContext,
ACTION_UNLINK_FROM_LIBRARY,
- UnlinkFromLibraryAction,
} from './unlink_from_library_action';
diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx
index c2f529fe399f3..fe5f6a0c8e2bd 100644
--- a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx
+++ b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx
@@ -30,13 +30,21 @@ import {
SimpleSavedObject,
I18nStart,
NotificationsStart,
+ OverlayStart,
} from '../../../../core/public';
import {
SavedObjectSaveModal,
showSaveModal,
OnSaveProps,
SaveResult,
+ checkForDuplicateTitle,
} from '../../../saved_objects/public';
+import {
+ EmbeddableStart,
+ EmbeddableFactory,
+ EmbeddableFactoryNotFoundError,
+ Container,
+} from '../../../embeddable/public';
/**
* The attribute service is a shared, generic service that embeddables can use to provide the functionality
@@ -49,12 +57,22 @@ export class AttributeService<
ValType extends EmbeddableInput & { attributes: SavedObjectAttributes },
RefType extends SavedObjectEmbeddableInput
> {
+ private embeddableFactory: EmbeddableFactory;
+
constructor(
private type: string,
private savedObjectsClient: SavedObjectsClientContract,
+ private overlays: OverlayStart,
private i18nContext: I18nStart['Context'],
- private toasts: NotificationsStart['toasts']
- ) {}
+ private toasts: NotificationsStart['toasts'],
+ getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']
+ ) {
+ const factory = getEmbeddableFactory(this.type);
+ if (!factory) {
+ throw new EmbeddableFactoryNotFoundError(this.type);
+ }
+ this.embeddableFactory = factory;
+ }
public async unwrapAttributes(input: RefType | ValType): Promise {
if (this.inputIsRefType(input)) {
@@ -105,6 +123,15 @@ export class AttributeService<
return isSavedObjectEmbeddableInput(input);
};
+ public getExplicitInputFromEmbeddable(embeddable: IEmbeddable): ValType | RefType {
+ return embeddable.getRoot() &&
+ (embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput
+ ? ((embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput as
+ | ValType
+ | RefType)
+ : (embeddable.getInput() as ValType | RefType);
+ }
+
getInputAsValueType = async (input: ValType | RefType): Promise => {
if (!this.inputIsRefType(input)) {
return input;
@@ -124,16 +151,31 @@ export class AttributeService<
if (this.inputIsRefType(input)) {
return input;
}
-
return new Promise((resolve, reject) => {
const onSave = async (props: OnSaveProps): Promise => {
+ await checkForDuplicateTitle(
+ {
+ title: props.newTitle,
+ copyOnSave: false,
+ lastSavedTitle: '',
+ getEsType: () => this.type,
+ getDisplayName: this.embeddableFactory.getDisplayName,
+ },
+ props.isTitleDuplicateConfirmed,
+ props.onTitleDuplicate,
+ {
+ savedObjectsClient: this.savedObjectsClient,
+ overlays: this.overlays,
+ }
+ );
try {
- input.attributes.title = props.newTitle;
- const wrappedInput = (await this.wrapAttributes(input.attributes, true)) as RefType;
+ const newAttributes = { ...input.attributes };
+ newAttributes.title = props.newTitle;
+ const wrappedInput = (await this.wrapAttributes(newAttributes, true)) as RefType;
resolve(wrappedInput);
return { id: wrappedInput.savedObjectId };
} catch (error) {
- reject();
+ reject(error);
return { error };
}
};
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index 2a36f2d801850..a788b06f91905 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -95,6 +95,11 @@ import { addEmbeddableToDashboardUrl } from './url_utils/url_helper';
import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder';
import { UrlGeneratorState } from '../../share/public';
import { AttributeService } from '.';
+import {
+ AddToLibraryAction,
+ ACTION_ADD_TO_LIBRARY,
+ AddToLibraryActionContext,
+} from './application/actions/add_to_library_action';
declare module '../../share/public' {
export interface UrlGeneratorStateMapping {
@@ -155,6 +160,7 @@ declare module '../../../plugins/ui_actions/public' {
[ACTION_EXPAND_PANEL]: ExpandPanelActionContext;
[ACTION_REPLACE_PANEL]: ReplacePanelActionContext;
[ACTION_CLONE_PANEL]: ClonePanelActionContext;
+ [ACTION_ADD_TO_LIBRARY]: AddToLibraryActionContext;
[ACTION_UNLINK_FROM_LIBRARY]: UnlinkFromLibraryActionContext;
}
}
@@ -406,6 +412,7 @@ export class DashboardPlugin
const {
uiActions,
data: { indexPatterns, search },
+ embeddable,
} = plugins;
const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings);
@@ -424,6 +431,9 @@ export class DashboardPlugin
uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id);
if (this.dashboardFeatureFlagConfig?.allowByValueEmbeddables) {
+ const addToLibraryAction = new AddToLibraryAction();
+ uiActions.registerAction(addToLibraryAction);
+ uiActions.attachAction(CONTEXT_MENU_TRIGGER, addToLibraryAction.id);
const unlinkFromLibraryAction = new UnlinkFromLibraryAction();
uiActions.registerAction(unlinkFromLibraryAction);
uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id);
@@ -452,8 +462,10 @@ export class DashboardPlugin
new AttributeService(
type,
core.savedObjects.client,
+ core.overlays,
core.i18n.Context,
- core.notifications.toasts
+ core.notifications.toasts,
+ embeddable.getEmbeddableFactory
),
};
}
diff --git a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts
index ff8fc5b11b26e..87dbbcee03b81 100644
--- a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts
+++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts
@@ -18,10 +18,10 @@
*/
import { EsQueryConfig } from './build_es_query';
-import { UI_SETTINGS } from '../../';
+import { GetConfigFn, UI_SETTINGS } from '../../';
interface KibanaConfig {
- get(key: string): T;
+ get: GetConfigFn;
}
export function getEsQueryConfig(config: KibanaConfig) {
diff --git a/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts b/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts
index 89a63243c76f0..7a225cd7d1ae7 100644
--- a/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts
+++ b/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts
@@ -20,7 +20,8 @@
import { i18n } from '@kbn/i18n';
import { memoize, noop } from 'lodash';
import moment, { Moment } from 'moment';
-import { FieldFormat, FIELD_FORMAT_IDS, KBN_FIELD_TYPES, TextContextTypeConvert } from '../../';
+import { FieldFormat, FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../';
+import { TextContextTypeConvert } from '../types';
/**
* Analyse the given moment.js format pattern for the fractional sec part (S,SS,SSS...)
diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts
index 4b847ebc358d7..32f9f37b9ba53 100644
--- a/src/plugins/data/common/field_formats/field_formats_registry.ts
+++ b/src/plugins/data/common/field_formats/field_formats_registry.ts
@@ -32,7 +32,7 @@ import {
import { baseFormatters } from './constants/base_formatters';
import { FieldFormat } from './field_format';
import { SerializedFieldFormat } from '../../../expressions/common/types';
-import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../types';
+import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../kbn_field_types/types';
import { UI_SETTINGS } from '../constants';
export class FieldFormatsRegistry {
diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts
index 6f773378de08d..daa44b2b0f85b 100644
--- a/src/plugins/data/common/field_formats/types.ts
+++ b/src/plugins/data/common/field_formats/types.ts
@@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
+
+import { GetConfigFn } from '../types';
import { FieldFormat } from './field_format';
import { FieldFormatsRegistry } from './field_formats_registry';
@@ -72,7 +74,7 @@ export interface FieldFormatConfig {
es?: boolean;
}
-export type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T;
+export type FieldFormatsGetConfigFn = GetConfigFn;
export type IFieldFormat = PublicMethodsOf;
diff --git a/src/plugins/data/common/search/aggs/aggs_service.ts b/src/plugins/data/common/search/aggs/aggs_service.ts
index 59c54fcce6838..6f3e3904dbbd5 100644
--- a/src/plugins/data/common/search/aggs/aggs_service.ts
+++ b/src/plugins/data/common/search/aggs/aggs_service.ts
@@ -19,6 +19,7 @@
import { ExpressionsServiceSetup } from 'src/plugins/expressions/common';
import { UI_SETTINGS } from '../../../common';
+import { GetConfigFn } from '../../types';
import {
AggConfigs,
AggTypesRegistry,
@@ -48,7 +49,7 @@ export interface AggsCommonSetupDependencies {
/** @internal */
export interface AggsCommonStartDependencies {
- getConfig: (key: string) => T;
+ getConfig: GetConfigFn;
}
/**
diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts
index 6fc0923768703..8853e40dd0ad2 100644
--- a/src/plugins/data/common/search/es_search/types.ts
+++ b/src/plugins/data/common/search/es_search/types.ts
@@ -16,14 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { SearchParams, SearchResponse } from 'elasticsearch';
+import { SearchResponse } from 'elasticsearch';
+import { Search } from '@elastic/elasticsearch/api/requestParams';
import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types';
export const ES_SEARCH_STRATEGY = 'es';
export type ISearchRequestParams = {
trackTotalHits?: boolean;
-} & SearchParams;
+} & Search;
export interface IEsSearchRequest extends IKibanaSearchRequest {
params?: ISearchRequestParams;
diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts
index e2ec1a031b0ca..a1e1252b3112f 100644
--- a/src/plugins/data/common/types.ts
+++ b/src/plugins/data/common/types.ts
@@ -20,4 +20,15 @@
export * from './query/types';
export * from './kbn_field_types/types';
export * from './index_patterns/types';
-export { TextContextTypeConvert, IFieldFormatMetaParams } from './field_formats/types';
+
+/**
+ * If a service is being shared on both the client and the server, and
+ * the client code requires synchronous access to uiSettings, both client
+ * and server should wrap the core uiSettings services in a function
+ * matching this signature.
+ *
+ * This matches the signature of the public `core.uiSettings.get`, and
+ * should only be used in scenarios where async access to uiSettings is
+ * not possible.
+ */
+export type GetConfigFn = (key: string, defaultOverride?: T) => T;
diff --git a/src/plugins/data/public/field_formats/converters/date.ts b/src/plugins/data/public/field_formats/converters/date.ts
index 78ef8b293e8b9..8070633ef2dfe 100644
--- a/src/plugins/data/public/field_formats/converters/date.ts
+++ b/src/plugins/data/public/field_formats/converters/date.ts
@@ -20,12 +20,8 @@
import { i18n } from '@kbn/i18n';
import { memoize, noop } from 'lodash';
import moment from 'moment';
-import {
- FieldFormat,
- KBN_FIELD_TYPES,
- TextContextTypeConvert,
- FIELD_FORMAT_IDS,
-} from '../../../common';
+import { FieldFormat, KBN_FIELD_TYPES, FIELD_FORMAT_IDS } from '../../../common';
+import { TextContextTypeConvert } from '../../../common/field_formats/types';
export class DateFormat extends FieldFormat {
static id = FIELD_FORMAT_IDS.DATE;
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 564c571b6ccd6..ee0b0714febc0 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -48,9 +48,7 @@ import {
} from './index_patterns';
import {
setFieldFormats,
- setHttp,
setIndexPatterns,
- setInjectedMetadata,
setNotifications,
setOverlays,
setQueryService,
@@ -164,11 +162,9 @@ export class DataPublicPlugin
public start(core: CoreStart, { uiActions }: DataStartDependencies): DataPublicPluginStart {
const { uiSettings, http, notifications, savedObjects, overlays, application } = core;
- setHttp(http);
setNotifications(notifications);
setOverlays(overlays);
setUiSettings(uiSettings);
- setInjectedMetadata(core.injectedMetadata);
const fieldFormats = this.fieldFormatsService.start();
setFieldFormats(fieldFormats);
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 58c2bd9957ab8..7defddb8f570a 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -34,7 +34,6 @@ import { InjectedIntl } from '@kbn/i18n/react';
import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { IUiSettingsClient } from 'src/core/public';
-import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/public';
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { KibanaConfigType } from 'src/core/server/kibana_config';
import { Location } from 'history';
@@ -61,7 +60,7 @@ import * as Rx from 'rxjs';
import { SavedObject } from 'src/core/server';
import { SavedObject as SavedObject_3 } from 'src/core/public';
import { SavedObjectsClientContract } from 'src/core/public';
-import { SearchParams } from 'elasticsearch';
+import { Search } from '@elastic/elasticsearch/api/requestParams';
import { SearchResponse as SearchResponse_2 } from 'elasticsearch';
import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common';
import { Subscription } from 'rxjs';
@@ -594,10 +593,11 @@ export const fieldFormats: {
// @public (undocumented)
export type FieldFormatsContentType = 'html' | 'text';
+// Warning: (ae-forgotten-export) The symbol "GetConfigFn" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "FieldFormatsGetConfigFn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T;
+export type FieldFormatsGetConfigFn = GetConfigFn;
// Warning: (ae-missing-release-tag) "FieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -727,12 +727,11 @@ export function getEsPreference(uiSettings: IUiSettingsClient_2, sessionId?: str
export const getKbnTypeNames: () => string[];
// Warning: (ae-forgotten-export) The symbol "ISearchRequestParams" needs to be exported by the entry point index.d.ts
-// Warning: (ae-missing-release-tag) "getSearchParamsFromRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export function getSearchParamsFromRequest(searchRequest: SearchRequest, dependencies: {
- injectedMetadata: CoreStart['injectedMetadata'];
- uiSettings: IUiSettingsClient_3;
+ esShardTimeout: number;
+ getConfig: GetConfigFn;
}): ISearchRequestParams;
// Warning: (ae-missing-release-tag) "getTime" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts
index aaaac5ae6ff7c..315d4678cabf1 100644
--- a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts
+++ b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts
@@ -42,7 +42,7 @@ describe('Search Usage Collector', () => {
{} as any,
]);
mockUsageCollectionSetup = usageCollectionPluginMock.createSetupContract();
- usageCollector = createUsageCollector(mockCoreSetup, mockUsageCollectionSetup);
+ usageCollector = createUsageCollector(mockCoreSetup.getStartServices, mockUsageCollectionSetup);
});
test('tracks query timeouts', async () => {
diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.ts b/src/plugins/data/public/search/collectors/create_usage_collector.ts
index 7adb0c3caa675..321b2c5b99049 100644
--- a/src/plugins/data/public/search/collectors/create_usage_collector.ts
+++ b/src/plugins/data/public/search/collectors/create_usage_collector.ts
@@ -18,16 +18,16 @@
*/
import { first } from 'rxjs/operators';
-import { CoreSetup } from '../../../../../core/public';
+import { StartServicesAccessor } from '../../../../../core/public';
import { METRIC_TYPE, UsageCollectionSetup } from '../../../../usage_collection/public';
import { SEARCH_EVENT_TYPE, SearchUsageCollector } from './types';
export const createUsageCollector = (
- core: CoreSetup,
+ getStartServices: StartServicesAccessor,
usageCollection?: UsageCollectionSetup
): SearchUsageCollector => {
const getCurrentApp = async () => {
- const [{ application }] = await core.getStartServices();
+ const [{ application }] = await getStartServices();
return application.currentAppId$.pipe(first()).toPromise();
};
diff --git a/src/plugins/data/public/search/fetch/get_search_params.test.ts b/src/plugins/data/public/search/fetch/get_search_params.test.ts
index f9b62fdd4fc61..1ecb879b1602d 100644
--- a/src/plugins/data/public/search/fetch/get_search_params.test.ts
+++ b/src/plugins/data/public/search/fetch/get_search_params.test.ts
@@ -18,13 +18,10 @@
*/
import { getSearchParams } from './get_search_params';
-import { IUiSettingsClient } from 'kibana/public';
-import { UI_SETTINGS } from '../../../common';
+import { GetConfigFn, UI_SETTINGS } from '../../../common';
-function getConfigStub(config: any = {}) {
- return {
- get: (key) => config[key],
- } as IUiSettingsClient;
+function getConfigStub(config: any = {}): GetConfigFn {
+ return (key) => config[key];
}
describe('getSearchParams', () => {
diff --git a/src/plugins/data/public/search/fetch/get_search_params.ts b/src/plugins/data/public/search/fetch/get_search_params.ts
index 3246156b6b37e..5e0395189f647 100644
--- a/src/plugins/data/public/search/fetch/get_search_params.ts
+++ b/src/plugins/data/public/search/fetch/get_search_params.ts
@@ -17,37 +17,36 @@
* under the License.
*/
-import { IUiSettingsClient, CoreStart } from 'kibana/public';
-import { UI_SETTINGS, ISearchRequestParams } from '../../../common';
+import { UI_SETTINGS, ISearchRequestParams, GetConfigFn } from '../../../common';
import { SearchRequest } from './types';
const sessionId = Date.now();
-export function getSearchParams(config: IUiSettingsClient, esShardTimeout: number = 0) {
+export function getSearchParams(getConfig: GetConfigFn, esShardTimeout: number = 0) {
return {
rest_total_hits_as_int: true,
ignore_unavailable: true,
- ignore_throttled: getIgnoreThrottled(config),
- max_concurrent_shard_requests: getMaxConcurrentShardRequests(config),
- preference: getPreference(config),
+ ignore_throttled: getIgnoreThrottled(getConfig),
+ max_concurrent_shard_requests: getMaxConcurrentShardRequests(getConfig),
+ preference: getPreference(getConfig),
timeout: getTimeout(esShardTimeout),
};
}
-export function getIgnoreThrottled(config: IUiSettingsClient) {
- return !config.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
+export function getIgnoreThrottled(getConfig: GetConfigFn) {
+ return !getConfig(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);
}
-export function getMaxConcurrentShardRequests(config: IUiSettingsClient) {
- const maxConcurrentShardRequests = config.get(UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS);
+export function getMaxConcurrentShardRequests(getConfig: GetConfigFn) {
+ const maxConcurrentShardRequests = getConfig(UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS);
return maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined;
}
-export function getPreference(config: IUiSettingsClient) {
- const setRequestPreference = config.get(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE);
+export function getPreference(getConfig: GetConfigFn) {
+ const setRequestPreference = getConfig(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE);
if (setRequestPreference === 'sessionId') return sessionId;
return setRequestPreference === 'custom'
- ? config.get(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE)
+ ? getConfig(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE)
: undefined;
}
@@ -55,13 +54,15 @@ export function getTimeout(esShardTimeout: number) {
return esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined;
}
+/** @public */
+// TODO: Could provide this on runtime contract with dependencies
+// already wired up.
export function getSearchParamsFromRequest(
searchRequest: SearchRequest,
- dependencies: { injectedMetadata: CoreStart['injectedMetadata']; uiSettings: IUiSettingsClient }
+ dependencies: { esShardTimeout: number; getConfig: GetConfigFn }
): ISearchRequestParams {
- const { injectedMetadata, uiSettings } = dependencies;
- const esShardTimeout = injectedMetadata.getInjectedVar('esShardTimeout') as number;
- const searchParams = getSearchParams(uiSettings, esShardTimeout);
+ const { esShardTimeout, getConfig } = dependencies;
+ const searchParams = getSearchParams(getConfig, esShardTimeout);
return {
index: searchRequest.index.title || searchRequest.index,
diff --git a/src/plugins/data/public/search/fetch/types.ts b/src/plugins/data/public/search/fetch/types.ts
index dda66d6b5238d..18d277204815b 100644
--- a/src/plugins/data/public/search/fetch/types.ts
+++ b/src/plugins/data/public/search/fetch/types.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { IUiSettingsClient } from '../../../../../core/public';
+import { GetConfigFn } from '../../../common';
import { ISearchStartLegacy } from '../types';
export type SearchRequest = any;
@@ -30,7 +30,7 @@ export interface FetchOptions {
export interface FetchHandlers {
legacySearchService: ISearchStartLegacy;
- config: IUiSettingsClient;
+ config: { get: GetConfigFn };
esShardTimeout: number;
}
diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.ts b/src/plugins/data/public/search/legacy/default_search_strategy.ts
index 284768bc5a1cc..6ccb0a01cf898 100644
--- a/src/plugins/data/public/search/legacy/default_search_strategy.ts
+++ b/src/plugins/data/public/search/legacy/default_search_strategy.ts
@@ -42,7 +42,7 @@ function msearch({
index: index.title || index,
search_type: searchType,
ignore_unavailable: true,
- preference: getPreference(config),
+ preference: getPreference(config.get),
};
const inlineBody = {
...body,
@@ -52,7 +52,7 @@ function msearch({
});
const searching = es.msearch({
- ...getMSearchParams(config),
+ ...getMSearchParams(config.get),
body: `${inlineRequests.join('\n')}\n`,
});
diff --git a/src/plugins/data/public/search/legacy/es_client/get_es_client.ts b/src/plugins/data/public/search/legacy/es_client/get_es_client.ts
index 93d9d24920271..4367544ad9ff4 100644
--- a/src/plugins/data/public/search/legacy/es_client/get_es_client.ts
+++ b/src/plugins/data/public/search/legacy/es_client/get_es_client.ts
@@ -22,17 +22,20 @@ import { default as es } from 'elasticsearch-browser/elasticsearch';
import { CoreStart, PackageInfo } from 'kibana/public';
import { BehaviorSubject } from 'rxjs';
-export function getEsClient(
- injectedMetadata: CoreStart['injectedMetadata'],
- http: CoreStart['http'],
- packageInfo: PackageInfo
-) {
- const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number;
- const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string;
-
+export function getEsClient({
+ esRequestTimeout,
+ esApiVersion,
+ http,
+ packageVersion,
+}: {
+ esRequestTimeout: number;
+ esApiVersion: string;
+ http: CoreStart['http'];
+ packageVersion: PackageInfo['version'];
+}) {
// Use legacy es client for msearch.
const client = es.Client({
- host: getEsUrl(http, packageInfo),
+ host: getEsUrl(http, packageVersion),
log: 'info',
requestTimeout: esRequestTimeout,
apiVersion: esApiVersion,
@@ -78,7 +81,7 @@ function wrapEsClientMethod(esClient: any, method: string, loadingCount$: Behavi
};
}
-function getEsUrl(http: CoreStart['http'], packageInfo: PackageInfo) {
+function getEsUrl(http: CoreStart['http'], packageVersion: PackageInfo['version']) {
const a = document.createElement('a');
a.href = http.basePath.prepend('/elasticsearch');
const protocolPort = /https/.test(a.protocol) ? 443 : 80;
@@ -89,7 +92,7 @@ function getEsUrl(http: CoreStart['http'], packageInfo: PackageInfo) {
protocol: a.protocol,
pathname: a.pathname,
headers: {
- 'kbn-version': packageInfo.version,
+ 'kbn-version': packageVersion,
},
};
}
diff --git a/src/plugins/data/public/search/legacy/fetch_soon.test.ts b/src/plugins/data/public/search/legacy/fetch_soon.test.ts
index 61d3568350b6b..d375398af1add 100644
--- a/src/plugins/data/public/search/legacy/fetch_soon.test.ts
+++ b/src/plugins/data/public/search/legacy/fetch_soon.test.ts
@@ -19,15 +19,12 @@
import { fetchSoon } from './fetch_soon';
import { callClient } from './call_client';
-import { IUiSettingsClient } from 'kibana/public';
import { FetchHandlers, FetchOptions } from '../fetch/types';
import { SearchRequest, SearchResponse } from '../index';
-import { UI_SETTINGS } from '../../../common';
+import { GetConfigFn, UI_SETTINGS } from '../../../common';
-function getConfigStub(config: any = {}) {
- return {
- get: (key) => config[key],
- } as IUiSettingsClient;
+function getConfigStub(config: any = {}): GetConfigFn {
+ return (key) => config[key];
}
const mockResponses: Record = {
@@ -60,9 +57,9 @@ describe('fetchSoon', () => {
});
test('should execute asap if config is set to not batch searches', () => {
- const config = getConfigStub({
- [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false,
- });
+ const config = {
+ get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false }),
+ };
const request = {};
const options = {};
@@ -72,9 +69,9 @@ describe('fetchSoon', () => {
});
test('should delay by 50ms if config is set to batch searches', () => {
- const config = getConfigStub({
- [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
- });
+ const config = {
+ get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
+ };
const request = {};
const options = {};
@@ -88,9 +85,9 @@ describe('fetchSoon', () => {
});
test('should send a batch of requests to callClient', () => {
- const config = getConfigStub({
- [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
- });
+ const config = {
+ get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
+ };
const requests = [{ foo: 1 }, { foo: 2 }];
const options = [{ bar: 1 }, { bar: 2 }];
@@ -105,9 +102,9 @@ describe('fetchSoon', () => {
});
test('should return the response to the corresponding call for multiple batched requests', async () => {
- const config = getConfigStub({
- [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
- });
+ const config = {
+ get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
+ };
const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }];
const promises = requests.map((request) => {
@@ -120,9 +117,9 @@ describe('fetchSoon', () => {
});
test('should wait for the previous batch to start before starting a new batch', () => {
- const config = getConfigStub({
- [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true,
- });
+ const config = {
+ get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
+ };
const firstBatch = [{ foo: 1 }, { foo: 2 }];
const secondBatch = [{ bar: 1 }, { bar: 2 }];
diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts
index dc61e19406631..d3206950174c8 100644
--- a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts
+++ b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts
@@ -18,13 +18,10 @@
*/
import { getMSearchParams } from './get_msearch_params';
-import { IUiSettingsClient } from '../../../../../core/public';
-import { UI_SETTINGS } from '../../../common';
+import { GetConfigFn, UI_SETTINGS } from '../../../common';
-function getConfigStub(config: any = {}) {
- return {
- get: (key) => config[key],
- } as IUiSettingsClient;
+function getConfigStub(config: any = {}): GetConfigFn {
+ return (key) => config[key];
}
describe('getMSearchParams', () => {
diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.ts b/src/plugins/data/public/search/legacy/get_msearch_params.ts
index 48d13903c972f..c4f77b25078cd 100644
--- a/src/plugins/data/public/search/legacy/get_msearch_params.ts
+++ b/src/plugins/data/public/search/legacy/get_msearch_params.ts
@@ -17,13 +17,13 @@
* under the License.
*/
-import { IUiSettingsClient } from 'kibana/public';
+import { GetConfigFn } from '../../../common';
import { getIgnoreThrottled, getMaxConcurrentShardRequests } from '../fetch';
-export function getMSearchParams(config: IUiSettingsClient) {
+export function getMSearchParams(getConfig: GetConfigFn) {
return {
rest_total_hits_as_int: true,
- ignore_throttled: getIgnoreThrottled(config),
- max_concurrent_shard_requests: getMaxConcurrentShardRequests(config),
+ ignore_throttled: getIgnoreThrottled(getConfig),
+ max_concurrent_shard_requests: getMaxConcurrentShardRequests(getConfig),
};
}
diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts
index 4360a0caa7cde..738f1e8ffbca0 100644
--- a/src/plugins/data/public/search/search_service.test.ts
+++ b/src/plugins/data/public/search/search_service.test.ts
@@ -52,6 +52,7 @@ describe('Search service', () => {
} as any);
expect(start).toHaveProperty('aggs');
expect(start).toHaveProperty('search');
+ expect(start).toHaveProperty('searchSource');
});
});
});
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index 04e1a46c84652..a65b2b4b71f20 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -51,11 +51,22 @@ export class SearchService implements Plugin {
private usageCollector?: SearchUsageCollector;
public setup(
- core: CoreSetup,
- { packageInfo, usageCollection, expressions }: SearchServiceSetupDependencies
+ { http, getStartServices, injectedMetadata, notifications, uiSettings }: CoreSetup,
+ { expressions, packageInfo, usageCollection }: SearchServiceSetupDependencies
): ISearchSetup {
- this.usageCollector = createUsageCollector(core, usageCollection);
- this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo);
+ const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string;
+ const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number;
+ const packageVersion = packageInfo.version;
+
+ this.usageCollector = createUsageCollector(getStartServices, usageCollection);
+
+ this.esClient = getEsClient({
+ esRequestTimeout,
+ esApiVersion,
+ http,
+ packageVersion,
+ });
+
/**
* A global object that intercepts all searches and provides convenience methods for cancelling
* all pending search requests, as well as getting the number of pending search requests.
@@ -64,13 +75,13 @@ export class SearchService implements Plugin {
*/
this.searchInterceptor = new SearchInterceptor(
{
- toasts: core.notifications.toasts,
- http: core.http,
- uiSettings: core.uiSettings,
- startServices: core.getStartServices(),
+ toasts: notifications.toasts,
+ http,
+ uiSettings,
+ startServices: getStartServices(),
usageCollector: this.usageCollector!,
},
- core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
+ esRequestTimeout
);
expressions.registerFunction(esdsl);
@@ -79,7 +90,7 @@ export class SearchService implements Plugin {
return {
aggs: this.aggsService.setup({
registerFunction: expressions.registerFunction,
- uiSettings: core.uiSettings,
+ uiSettings,
}),
usageCollector: this.usageCollector!,
__enhance: (enhancements: SearchEnhancements) => {
@@ -101,8 +112,8 @@ export class SearchService implements Plugin {
};
const searchSourceDependencies: SearchSourceDependencies = {
- uiSettings,
- injectedMetadata,
+ getConfig: uiSettings.get.bind(uiSettings),
+ esShardTimeout: injectedMetadata.getInjectedVar('esShardTimeout') as number,
search,
legacySearch,
};
diff --git a/src/plugins/data/public/search/search_source/create_search_source.test.ts b/src/plugins/data/public/search/search_source/create_search_source.test.ts
index 23ab5979595af..56f6ca6c56270 100644
--- a/src/plugins/data/public/search/search_source/create_search_source.test.ts
+++ b/src/plugins/data/public/search/search_source/create_search_source.test.ts
@@ -16,27 +16,28 @@
* specific language governing permissions and limitations
* under the License.
*/
+
import { createSearchSource as createSearchSourceFactory } from './create_search_source';
+import { SearchSourceDependencies } from './search_source';
import { IIndexPattern } from '../../../common/index_patterns';
import { IndexPatternsContract } from '../../index_patterns/index_patterns';
import { Filter } from '../../../common/es_query/filters';
-import { coreMock } from '../../../../../core/public/mocks';
import { dataPluginMock } from '../../mocks';
describe('createSearchSource', () => {
const indexPatternMock: IIndexPattern = {} as IIndexPattern;
let indexPatternContractMock: jest.Mocked;
- let dependencies: any;
+ let dependencies: SearchSourceDependencies;
let createSearchSource: ReturnType;
beforeEach(() => {
- const core = coreMock.createStart();
const data = dataPluginMock.createStartContract();
dependencies = {
- searchService: data.search,
- uiSettings: core.uiSettings,
- injectedMetadata: core.injectedMetadata,
+ getConfig: jest.fn(),
+ search: jest.fn(),
+ legacySearch: data.search.__LEGACY,
+ esShardTimeout: 30000,
};
indexPatternContractMock = ({
diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts
index cf2d009e41b54..4e1c35557ffa6 100644
--- a/src/plugins/data/public/search/search_source/mocks.ts
+++ b/src/plugins/data/public/search/search_source/mocks.ts
@@ -17,10 +17,7 @@
* under the License.
*/
-import {
- injectedMetadataServiceMock,
- uiSettingsServiceMock,
-} from '../../../../../core/public/mocks';
+import { uiSettingsServiceMock } from '../../../../../core/public/mocks';
import { ISearchSource, SearchSource } from './search_source';
import { SearchSourceFields } from './types';
@@ -54,6 +51,8 @@ export const searchSourceMock = {
export const createSearchSourceMock = (fields?: SearchSourceFields) =>
new SearchSource(fields, {
+ getConfig: uiSettingsServiceMock.createStartContract().get,
+ esShardTimeout: 30000,
search: jest.fn(),
legacySearch: {
esClient: {
@@ -61,6 +60,4 @@ export const createSearchSourceMock = (fields?: SearchSourceFields) =>
msearch: jest.fn(),
},
},
- uiSettings: uiSettingsServiceMock.createStartContract(),
- injectedMetadata: injectedMetadataServiceMock.createStartContract(),
});
diff --git a/src/plugins/data/public/search/search_source/search_source.test.ts b/src/plugins/data/public/search/search_source/search_source.test.ts
index 6d53b8dfc4b4e..2f0fa0765e25a 100644
--- a/src/plugins/data/public/search/search_source/search_source.test.ts
+++ b/src/plugins/data/public/search/search_source/search_source.test.ts
@@ -16,13 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
+
import { Observable } from 'rxjs';
-import { SearchSource } from './search_source';
+import { GetConfigFn } from 'src/plugins/data/common';
+import { SearchSource, SearchSourceDependencies } from './search_source';
import { IndexPattern, SortDirection } from '../..';
import { fetchSoon } from '../legacy';
-import { IUiSettingsClient } from '../../../../../core/public';
import { dataPluginMock } from '../../../../data/public/mocks';
-import { coreMock } from '../../../../../core/public/mocks';
jest.mock('../legacy', () => ({
fetchSoon: jest.fn().mockResolvedValue({}),
@@ -51,10 +51,9 @@ const indexPattern2 = ({
describe('SearchSource', () => {
let mockSearchMethod: any;
- let searchSourceDependencies: any;
+ let searchSourceDependencies: SearchSourceDependencies;
beforeEach(() => {
- const core = coreMock.createStart();
const data = dataPluginMock.createStartContract();
mockSearchMethod = jest.fn(() => {
@@ -69,10 +68,10 @@ describe('SearchSource', () => {
});
searchSourceDependencies = {
+ getConfig: jest.fn(),
search: mockSearchMethod,
legacySearch: data.search.__LEGACY,
- injectedMetadata: core.injectedMetadata,
- uiSettings: core.uiSettings,
+ esShardTimeout: 30000,
};
});
@@ -184,16 +183,11 @@ describe('SearchSource', () => {
describe('#legacy fetch()', () => {
beforeEach(() => {
- const core = coreMock.createStart();
-
searchSourceDependencies = {
...searchSourceDependencies,
- uiSettings: {
- ...core.uiSettings,
- get: jest.fn(() => {
- return true; // batchSearches = true
- }),
- } as IUiSettingsClient,
+ getConfig: jest.fn(() => {
+ return true; // batchSearches = true
+ }) as GetConfigFn,
};
});
diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts
index 847dc8853d6ba..06ad13bfcfdf5 100644
--- a/src/plugins/data/public/search/search_source/search_source.ts
+++ b/src/plugins/data/public/search/search_source/search_source.ts
@@ -72,7 +72,6 @@
import { setWith } from '@elastic/safer-lodash-set';
import { uniqueId, uniq, extend, pick, difference, omit, isObject, keys, isFunction } from 'lodash';
import { map } from 'rxjs/operators';
-import { CoreStart } from 'kibana/public';
import { normalizeSortRequest } from './normalize_sort_request';
import { filterDocvalueFields } from './filter_docvalue_fields';
import { fieldWildcardFilter } from '../../../../kibana_utils/common';
@@ -82,15 +81,32 @@ import { FetchOptions, RequestFailure, handleResponse, getSearchParamsFromReques
import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common';
import { getHighlightRequest } from '../../../common/field_formats';
+import { GetConfigFn } from '../../../common/types';
import { fetchSoon } from '../legacy';
import { extractReferences } from './extract_references';
import { ISearchStartLegacy } from '../types';
+/** @internal */
+export const searchSourceRequiredUiSettings = [
+ 'dateFormat:tz',
+ UI_SETTINGS.COURIER_BATCH_SEARCHES,
+ UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE,
+ UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX,
+ UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS,
+ UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE,
+ UI_SETTINGS.DOC_HIGHLIGHT,
+ UI_SETTINGS.META_FIELDS,
+ UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS,
+ UI_SETTINGS.QUERY_STRING_OPTIONS,
+ UI_SETTINGS.SEARCH_INCLUDE_FROZEN,
+ UI_SETTINGS.SORT_OPTIONS,
+];
+
export interface SearchSourceDependencies {
- uiSettings: CoreStart['uiSettings'];
+ getConfig: GetConfigFn;
search: ISearchGeneric;
legacySearch: ISearchStartLegacy;
- injectedMetadata: CoreStart['injectedMetadata'];
+ esShardTimeout: number;
}
/** @public **/
@@ -204,11 +220,11 @@ export class SearchSource {
* @return {Observable>}
*/
private fetch$(searchRequest: SearchRequest, signal?: AbortSignal) {
- const { search, injectedMetadata, uiSettings } = this.dependencies;
+ const { search, esShardTimeout, getConfig } = this.dependencies;
const params = getSearchParamsFromRequest(searchRequest, {
- injectedMetadata,
- uiSettings,
+ esShardTimeout,
+ getConfig,
});
return search({ params, indexType: searchRequest.indexType }, { signal }).pipe(
@@ -221,8 +237,7 @@ export class SearchSource {
* @return {Promise>}
*/
private async legacyFetch(searchRequest: SearchRequest, options: FetchOptions) {
- const { injectedMetadata, legacySearch, uiSettings } = this.dependencies;
- const esShardTimeout = injectedMetadata.getInjectedVar('esShardTimeout') as number;
+ const { esShardTimeout, legacySearch, getConfig } = this.dependencies;
return await fetchSoon(
searchRequest,
@@ -232,7 +247,7 @@ export class SearchSource {
},
{
legacySearchService: legacySearch,
- config: uiSettings,
+ config: { get: getConfig },
esShardTimeout,
}
);
@@ -243,14 +258,14 @@ export class SearchSource {
* @async
*/
async fetch(options: FetchOptions = {}) {
- const { uiSettings } = this.dependencies;
+ const { getConfig } = this.dependencies;
await this.requestIsStarting(options);
const searchRequest = await this.flatten();
this.history = [searchRequest];
let response;
- if (uiSettings.get(UI_SETTINGS.COURIER_BATCH_SEARCHES)) {
+ if (getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES)) {
response = await this.legacyFetch(searchRequest, options);
} else {
response = this.fetch$(searchRequest, options.abortSignal).toPromise();
@@ -342,7 +357,7 @@ export class SearchSource {
}
};
- const { uiSettings } = this.dependencies;
+ const { getConfig } = this.dependencies;
switch (key) {
case 'filter':
@@ -364,7 +379,7 @@ export class SearchSource {
const sort = normalizeSortRequest(
val,
this.getField('index'),
- uiSettings.get(UI_SETTINGS.SORT_OPTIONS)
+ getConfig(UI_SETTINGS.SORT_OPTIONS)
);
return addToBody(key, sort);
default:
@@ -418,14 +433,11 @@ export class SearchSource {
body._source = index.getSourceFiltering();
}
- const { uiSettings } = this.dependencies;
+ const { getConfig } = this.dependencies;
if (body._source) {
// exclude source fields for this index pattern specified by the user
- const filter = fieldWildcardFilter(
- body._source.excludes,
- uiSettings.get(UI_SETTINGS.META_FIELDS)
- );
+ const filter = fieldWildcardFilter(body._source.excludes, getConfig(UI_SETTINGS.META_FIELDS));
body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) =>
filter(docvalueField.field)
);
@@ -445,11 +457,11 @@ export class SearchSource {
);
}
- const esQueryConfigs = getEsQueryConfig(uiSettings);
+ const esQueryConfigs = getEsQueryConfig({ get: getConfig });
body.query = buildEsQuery(index, query, filters, esQueryConfigs);
if (highlightAll && body.query) {
- body.highlight = getHighlightRequest(body.query, uiSettings.get(UI_SETTINGS.DOC_HIGHLIGHT));
+ body.highlight = getHighlightRequest(body.query, getConfig(UI_SETTINGS.DOC_HIGHLIGHT));
delete searchRequest.highlightAll;
}
diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts
index ba0b2de393bde..032bce6d8d2aa 100644
--- a/src/plugins/data/public/services.ts
+++ b/src/plugins/data/public/services.ts
@@ -31,8 +31,6 @@ export const [getUiSettings, setUiSettings] = createGetterSetter('Http');
-
export const [getFieldFormats, setFieldFormats] = createGetterSetter(
'FieldFormats'
);
@@ -47,10 +45,6 @@ export const [getQueryService, setQueryService] = createGetterSetter<
DataPublicPluginStart['query']
>('Query');
-export const [getInjectedMetadata, setInjectedMetadata] = createGetterSetter<
- CoreStart['injectedMetadata']
->('InjectedMetadata');
-
export const [getSearchService, setSearchService] = createGetterSetter<
DataPublicPluginStart['search']
>('Search');
diff --git a/src/plugins/data/server/field_formats/converters/date_nanos_server.ts b/src/plugins/data/server/field_formats/converters/date_nanos_server.ts
index 299b2aac93d49..b99febf0c7e73 100644
--- a/src/plugins/data/server/field_formats/converters/date_nanos_server.ts
+++ b/src/plugins/data/server/field_formats/converters/date_nanos_server.ts
@@ -24,7 +24,7 @@ import {
DateNanosFormat,
formatWithNanos,
} from '../../../common/field_formats/converters/date_nanos_shared';
-import { TextContextTypeConvert } from '../../../common';
+import { TextContextTypeConvert } from '../../../common/field_formats/types';
class DateNanosFormatServer extends DateNanosFormat {
textConvert: TextContextTypeConvert = (val) => {
diff --git a/src/plugins/data/server/field_formats/converters/date_server.ts b/src/plugins/data/server/field_formats/converters/date_server.ts
index 85eb65dfc6a8d..19c4adc90bade 100644
--- a/src/plugins/data/server/field_formats/converters/date_server.ts
+++ b/src/plugins/data/server/field_formats/converters/date_server.ts
@@ -23,11 +23,13 @@ import moment from 'moment-timezone';
import {
FieldFormat,
KBN_FIELD_TYPES,
- TextContextTypeConvert,
FIELD_FORMAT_IDS,
FieldFormatsGetConfigFn,
- IFieldFormatMetaParams,
} from '../../../common';
+import {
+ IFieldFormatMetaParams,
+ TextContextTypeConvert,
+} from '../../../common/field_formats/types';
export class DateFormat extends FieldFormat {
static id = FIELD_FORMAT_IDS.DATE;
diff --git a/src/plugins/data/server/search/collectors/fetch.ts b/src/plugins/data/server/search/collectors/fetch.ts
index ee29607b63ede..3551767eab017 100644
--- a/src/plugins/data/server/search/collectors/fetch.ts
+++ b/src/plugins/data/server/search/collectors/fetch.ts
@@ -19,14 +19,18 @@
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
-import { LegacyAPICaller, SharedGlobalConfig } from '../../../../../core/server';
+import { LegacyAPICaller, SharedGlobalConfig } from 'kibana/server';
import { Usage } from './register';
+interface SearchTelemetrySavedObject {
+ 'search-telemetry': Usage;
+}
+
export function fetchProvider(config$: Observable) {
return async (callCluster: LegacyAPICaller): Promise => {
const config = await config$.pipe(first()).toPromise();
- const response = await callCluster('search', {
+ const response = await callCluster('search', {
index: config.kibana.index,
body: {
query: { term: { type: { value: 'search-telemetry' } } },
@@ -35,7 +39,7 @@ export function fetchProvider(config$: Observable) {
});
return response.hits.hits.length
- ? (response.hits.hits[0]._source as Usage)
+ ? response.hits.hits[0]._source['search-telemetry']
: {
successCount: 0,
errorCount: 0,
diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
index 2888f9d9d20b5..11564bb336b08 100644
--- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
+++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
@@ -26,15 +26,17 @@ describe('ES search strategy', () => {
info: () => {},
};
const mockApiCaller = jest.fn().mockResolvedValue({
- _shards: {
- total: 10,
- failed: 1,
- skipped: 2,
- successful: 7,
+ body: {
+ _shards: {
+ total: 10,
+ failed: 1,
+ skipped: 2,
+ successful: 7,
+ },
},
});
const mockContext = {
- core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } },
+ core: { elasticsearch: { client: { asCurrentUser: { search: mockApiCaller } } } },
};
const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$;
@@ -55,8 +57,7 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('search');
- expect(mockApiCaller.mock.calls[0][1]).toEqual({
+ expect(mockApiCaller.mock.calls[0][0]).toEqual({
...params,
timeout: '0ms',
ignoreUnavailable: true,
@@ -71,8 +72,7 @@ describe('ES search strategy', () => {
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
- expect(mockApiCaller.mock.calls[0][0]).toBe('search');
- expect(mockApiCaller.mock.calls[0][1]).toEqual({
+ expect(mockApiCaller.mock.calls[0][0]).toEqual({
...params,
restTotalHitsAsInt: true,
});
diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts
index 234c30376d6db..7f9cb665b96b5 100644
--- a/src/plugins/data/server/search/es_search/es_search_strategy.ts
+++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts
@@ -20,6 +20,7 @@ import { first } from 'rxjs/operators';
import { SharedGlobalConfig, Logger } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { Observable } from 'rxjs';
+import { ApiResponse } from '@elastic/elasticsearch';
import { SearchUsage } from '../collectors/usage';
import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..';
@@ -46,11 +47,10 @@ export const esSearchStrategyProvider = (
};
try {
- const rawResponse = (await context.core.elasticsearch.legacy.client.callAsCurrentUser(
- 'search',
- params,
- options
- )) as SearchResponse;
+ const esResponse = (await context.core.elasticsearch.client.asCurrentUser.search(
+ params
+ )) as ApiResponse>;
+ const rawResponse = esResponse.body;
if (usage) usage.trackSuccess(rawResponse.took);
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 9c8a79f27a9db..f0a0d2763ff23 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -131,6 +131,7 @@ import { RequestStatistics } from 'src/plugins/inspector/common';
import { SavedObject } from 'src/core/server';
import { SavedObjectsClientContract as SavedObjectsClientContract_2 } from 'src/core/server';
import { ScrollParams } from 'elasticsearch';
+import { Search } from '@elastic/elasticsearch/api/requestParams';
import { SearchParams } from 'elasticsearch';
import { SearchResponse } from 'elasticsearch';
import { SearchShardsParams } from 'elasticsearch';
@@ -418,10 +419,11 @@ export const fieldFormats: {
TruncateFormat: typeof TruncateFormat;
};
+// Warning: (ae-forgotten-export) The symbol "GetConfigFn" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "FieldFormatsGetConfigFn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T;
+export type FieldFormatsGetConfigFn = GetConfigFn;
// Warning: (ae-missing-release-tag) "Filter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
diff --git a/src/plugins/discover/public/application/angular/directives/render_complete.ts b/src/plugins/discover/public/application/angular/directives/render_complete.ts
index 635cf68f12fcb..72f97138790b2 100644
--- a/src/plugins/discover/public/application/angular/directives/render_complete.ts
+++ b/src/plugins/discover/public/application/angular/directives/render_complete.ts
@@ -17,14 +17,14 @@
* under the License.
*/
import { IScope } from 'angular';
-import { RenderCompleteHelper } from '../../../../../kibana_utils/public';
+import { RenderCompleteListener } from '../../../../../kibana_utils/public';
export function createRenderCompleteDirective() {
return {
controller($scope: IScope, $element: JQLite) {
const el = $element[0];
- const renderCompleteHelper = new RenderCompleteHelper(el);
- $scope.$on('$destroy', renderCompleteHelper.destroy);
+ const renderCompleteListener = new RenderCompleteListener(el);
+ $scope.$on('$destroy', renderCompleteListener.destroy);
},
};
}
diff --git a/src/plugins/embeddable/kibana.json b/src/plugins/embeddable/kibana.json
index c9694ad7b9423..6a8e6079232aa 100644
--- a/src/plugins/embeddable/kibana.json
+++ b/src/plugins/embeddable/kibana.json
@@ -12,6 +12,7 @@
],
"requiredBundles": [
"savedObjects",
- "kibanaReact"
+ "kibanaReact",
+ "kibanaUtils"
]
}
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
index fcecf117d7d52..ffe8a5bf6e7dc 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
@@ -19,6 +19,8 @@
import { cloneDeep, isEqual } from 'lodash';
import * as Rx from 'rxjs';
+import { distinctUntilChanged, map } from 'rxjs/operators';
+import { RenderCompleteDispatcher } from '../../../../kibana_utils/public';
import { Adapters, ViewMode } from '../types';
import { IContainer } from '../containers';
import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
@@ -47,6 +49,8 @@ export abstract class Embeddable<
private readonly input$: Rx.BehaviorSubject;
private readonly output$: Rx.BehaviorSubject;
+ protected renderComplete = new RenderCompleteDispatcher();
+
// Listener to parent changes, if this embeddable exists in a parent, in order
// to update input when the parent changes.
private parentSubscription?: Rx.Subscription;
@@ -77,6 +81,15 @@ export abstract class Embeddable<
this.onResetInput(newInput);
});
}
+
+ this.getOutput$()
+ .pipe(
+ map(({ title }) => title || ''),
+ distinctUntilChanged()
+ )
+ .subscribe((title) => {
+ this.renderComplete.setTitle(title);
+ });
}
public getIsContainer(): this is IContainer {
@@ -105,8 +118,8 @@ export abstract class Embeddable<
return this.input;
}
- public getTitle() {
- return this.output.title;
+ public getTitle(): string {
+ return this.output.title || '';
}
/**
@@ -133,7 +146,10 @@ export abstract class Embeddable<
}
}
- public render(domNode: HTMLElement | Element): void {
+ public render(el: HTMLElement): void {
+ this.renderComplete.setEl(el);
+ this.renderComplete.setTitle(this.output.title || '');
+
if (this.destroyed) {
throw new Error('Embeddable has been destroyed');
}
diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
index 7628b1d41b452..9c4a1b5602c49 100644
--- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
@@ -22,6 +22,7 @@ import { Adapters } from '../types';
import { IContainer } from '../containers/i_container';
import { ViewMode } from '../types';
import { TriggerContextMapping } from '../../../../ui_actions/public';
+import type { TimeRange, Query, Filter } from '../../../../data/common';
export interface EmbeddableError {
name: string;
@@ -55,6 +56,21 @@ export interface EmbeddableInput {
*/
disableTriggers?: boolean;
+ /**
+ * Time range of the chart.
+ */
+ timeRange?: TimeRange;
+
+ /**
+ * Visualization query string used to narrow down results.
+ */
+ query?: Query;
+
+ /**
+ * Visualization filters used to narrow down results.
+ */
+ filters?: Filter[];
+
[key: string]: unknown;
}
diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts
index 42adb9d770e8a..ef79b18acd4f5 100644
--- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts
+++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts
@@ -19,7 +19,7 @@
import { coreMock, scopedHistoryMock } from '../../../../../core/public/mocks';
import { EmbeddableStateTransfer } from '.';
-import { ApplicationStart } from '../../../../../core/public';
+import { ApplicationStart, PublicAppInfo } from '../../../../../core/public';
function mockHistoryState(state: unknown) {
return scopedHistoryMock.create({ state });
@@ -37,6 +37,29 @@ describe('embeddable state transfer', () => {
stateTransfer = new EmbeddableStateTransfer(application.navigateToApp);
});
+ it('cannot fetch app name when given no app list', async () => {
+ expect(stateTransfer.getAppNameFromId('test')).toBeUndefined();
+ });
+
+ it('cannot fetch app name when app id is not in given app list', async () => {
+ const appsList = new Map([
+ ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo],
+ ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo],
+ ]);
+ stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, appsList);
+ expect(stateTransfer.getAppNameFromId('kibanana')).toBeUndefined();
+ });
+
+ it('can fetch app titles when given app list', async () => {
+ const appsList = new Map([
+ ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo],
+ ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo],
+ ]);
+ stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, appsList);
+ expect(stateTransfer.getAppNameFromId('testId')).toBe('State Transfer Test App Hello');
+ expect(stateTransfer.getAppNameFromId('testId2')).toBe('State Transfer Test App Goodbye');
+ });
+
it('can send an outgoing originating app state', async () => {
await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp } });
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts
index 8f70e5a66c478..780cff9f4be7e 100644
--- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts
+++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts
@@ -18,7 +18,12 @@
*/
import { cloneDeep } from 'lodash';
-import { ScopedHistory, ApplicationStart } from '../../../../../core/public';
+import {
+ ScopedHistory,
+ ApplicationStart,
+ PublicLegacyAppInfo,
+ PublicAppInfo,
+} from '../../../../../core/public';
import {
EmbeddableEditorState,
isEmbeddableEditorState,
@@ -35,9 +40,16 @@ import {
export class EmbeddableStateTransfer {
constructor(
private navigateToApp: ApplicationStart['navigateToApp'],
- private scopedHistory?: ScopedHistory
+ private scopedHistory?: ScopedHistory,
+ private appList?: ReadonlyMap | undefined
) {}
+ /**
+ * Fetches an internationalized app title when given an appId.
+ * @param appId - The id of the app to fetch the title for
+ */
+ public getAppNameFromId = (appId: string): string | undefined => this.appList?.get(appId)?.title;
+
/**
* Fetches an {@link EmbeddableEditorState | originating app} argument from the scoped
* history's location state.
diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx
index 7ec03ba659cda..2064236e9ae7f 100644
--- a/src/plugins/embeddable/public/mocks.tsx
+++ b/src/plugins/embeddable/public/mocks.tsx
@@ -25,6 +25,9 @@ import {
EmbeddableStateTransfer,
IEmbeddable,
EmbeddablePanel,
+ EmbeddableInput,
+ SavedObjectEmbeddableInput,
+ ReferenceOrValueEmbeddable,
} from '.';
import { EmbeddablePublicPlugin } from './plugin';
import { coreMock } from '../../../core/public/mocks';
@@ -35,7 +38,6 @@ import { dataPluginMock } from '../../data/public/mocks';
import { inspectorPluginMock } from '../../inspector/public/mocks';
import { uiActionsPluginMock } from '../../ui_actions/public/mocks';
-import { SavedObjectEmbeddableInput, ReferenceOrValueEmbeddable, EmbeddableInput } from './lib';
export type Setup = jest.Mocked;
export type Start = jest.Mocked;
diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx
index 3cbd49279564f..fb09729ab71c3 100644
--- a/src/plugins/embeddable/public/plugin.tsx
+++ b/src/plugins/embeddable/public/plugin.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import React from 'react';
+import { Subscription } from 'rxjs';
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../data/public';
import { getSavedObjectFinder } from '../../saved_objects/public';
import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public';
@@ -27,6 +28,8 @@ import {
CoreStart,
Plugin,
ScopedHistory,
+ PublicAppInfo,
+ PublicLegacyAppInfo,
} from '../../../core/public';
import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types';
import { bootstrap } from './bootstrap';
@@ -89,6 +92,8 @@ export class EmbeddablePublicPlugin implements Plugin;
+ private appListSubscription?: Subscription;
constructor(initializerContext: PluginInitializerContext) {}
@@ -121,7 +126,15 @@ export class EmbeddablePublicPlugin implements Plugin {
+ this.appList = appList;
+ });
+
+ this.outgoingOnlyStateTransfer = new EmbeddableStateTransfer(
+ core.application.navigateToApp,
+ undefined,
+ this.appList
+ );
this.isRegistryReady = true;
const getEmbeddablePanelHoc = (stateTransfer?: EmbeddableStateTransfer) => ({
@@ -151,7 +164,7 @@ export class EmbeddablePublicPlugin implements Plugin {
return history
- ? new EmbeddableStateTransfer(core.application.navigateToApp, history)
+ ? new EmbeddableStateTransfer(core.application.navigateToApp, history, this.appList)
: this.outgoingOnlyStateTransfer;
},
EmbeddablePanel: getEmbeddablePanelHoc(),
@@ -159,7 +172,11 @@ export class EmbeddablePublicPlugin implements Plugin {
this.ensureFactoriesExist();
diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts
index 419e80ad1608f..8c66a87adbaa1 100644
--- a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts
+++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts
@@ -23,6 +23,7 @@ import collapsingTests from './utils_string_collapsing.txt';
import expandingTests from './utils_string_expanding.txt';
import * as utils from '../index';
+import { extractJSONStringValues } from '../parser';
describe('JSON to XJSON conversion tools', () => {
it('will collapse multiline strings', () => {
@@ -34,6 +35,32 @@ describe('JSON to XJSON conversion tools', () => {
const multiline = '{ "foo": """bar\r\nbaz""" }';
expect(utils.collapseLiteralStrings(multiline)).toEqual('{ "foo": "bar\\r\\nbaz" }');
});
+
+ describe('JSON string values parser', () => {
+ test('correctly extracts JSON string values', () => {
+ const json = {
+ myString: 'string',
+ notAString: 1,
+ myStringArray: ['a', 1, 'test', { nestedString: 'string' }],
+ };
+ const jsonString = JSON.stringify(json);
+ const { stringValues } = extractJSONStringValues(jsonString);
+ expect(stringValues.length).toBe(4);
+
+ expect(jsonString.substring(stringValues[0].startIndex, stringValues[0].endIndex + 1)).toBe(
+ '"string"'
+ );
+ expect(jsonString.substring(stringValues[1].startIndex, stringValues[1].endIndex + 1)).toBe(
+ '"a"'
+ );
+ expect(jsonString.substring(stringValues[2].startIndex, stringValues[2].endIndex + 1)).toBe(
+ '"test"'
+ );
+ expect(jsonString.substring(stringValues[3].startIndex, stringValues[3].endIndex + 1)).toBe(
+ '"string"'
+ );
+ });
+ });
});
_.each(collapsingTests.split(/^=+$/m), function (fixture) {
diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt
index 7de874c244e74..d157ed73817c2 100644
--- a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt
+++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt
@@ -1,8 +1,9 @@
+
==========
Scripts in requests
-------------------------------------
{
- "f": { "script": { "source": "\ntest\ntest\\\\\\\\\\\\\\\\2\n" } },
+ "f": { "script": { "source": "\ntest\ntest\\2\n" } },
"g": { "script": "second + \"\\\";" },
"a": "short with \\",
"\\\\h": 1,
@@ -12,7 +13,7 @@ Scripts in requests
{
"f": { "script": { "source": """
test
-test\\\\\\\\2
+test\2
""" } },
"g": { "script": """second + "\";""" },
"a": """short with \""",
@@ -23,11 +24,11 @@ test\\\\\\\\2
Preserve triple quotes
-------------------------------------
{
- "content\\\": "tri\"ple",
+ "content\\": "tri\"ple",
}
-------------------------------------
{
- "content\\\": """tri"ple""",
+ "content\\": """tri"ple""",
}
==========
Correctly parse with JSON embedded inside values
@@ -82,3 +83,13 @@ Single quotes escaped special case, end
{
"query": "test\""
}
+==========
+Strings in Arrays
+-------------------------------------
+{
+ "array": ["expand \\ me", "do not expand", "do expand \\"]
+}
+-------------------------------------
+{
+ "array": ["""expand \ me""", "do not expand", """do expand \"""]
+}
diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts
index 28f1aca95efab..86fe9535a9619 100644
--- a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts
+++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import { extractJSONStringValues } from './parser';
+
export function collapseLiteralStrings(data: string) {
const splitData = data.split(`"""`);
for (let idx = 1; idx < splitData.length - 1; idx += 2) {
@@ -25,47 +27,60 @@ export function collapseLiteralStrings(data: string) {
return splitData.join('');
}
-/*
- The following regex describes global match on:
- 1. one colon followed by any number of space characters
- 2. one double quote (not escaped, special case for JSON in JSON).
- 3. greedily match any non double quote and non newline char OR any escaped double quote char (non-capturing).
- 4. handle a special case where an escaped slash may be the last character
- 5. one double quote
-
- For instance: `: "some characters \" here"`
- Will match and be expanded to: `"""some characters " here"""`
+// 5 megabytes
+const MAX_EXPANDABLE_JSON_SIZE = 5 * 1024 * 1024;
+/**
+ * Takes in a string representing some JSON data and expands strings,
+ * where needed, to a string literal representation.
+ *
+ * For example; given a value like: "{ "my_string": "\nhey!\n" }"
+ *
+ * Will return: "{ "my_string": """
+ * hey!
+ * """
+ * }"
*/
+export function expandLiteralStrings(data: string) {
+ // Assuming 1 byte per char
+ if (data.length > MAX_EXPANDABLE_JSON_SIZE) {
+ return data;
+ }
-const LITERAL_STRING_CANDIDATES = /((:[\s\r\n]*)([^\\])"(\\"|[^"\n])*\\?")/g;
+ const { stringValues } = extractJSONStringValues(data);
-export function expandLiteralStrings(data: string) {
- return data.replace(LITERAL_STRING_CANDIDATES, (match, string) => {
- // Expand to triple quotes if there are _any_ slashes
- if (string.match(/\\./)) {
- const firstDoubleQuoteIdx = string.indexOf('"');
- const lastDoubleQuoteIdx = string.lastIndexOf('"');
+ if (stringValues.length === 0) {
+ return data;
+ }
- // Handle a special case where we may have a value like "\"test\"". We don't
- // want to expand this to """"test"""" - so we terminate before processing the string
- // further if we detect this either at the start or end of the double quote section.
+ // Include JSON before our first string value
+ let result = data.substring(0, stringValues[0].startIndex);
- if (string[firstDoubleQuoteIdx + 1] === '\\' && string[firstDoubleQuoteIdx + 2] === '"') {
- return string;
- }
+ for (let x = 0; x < stringValues.length; x++) {
+ const { startIndex, endIndex } = stringValues[x];
+ const candidate = data.substring(startIndex, endIndex + 1);
- if (string[lastDoubleQuoteIdx - 1] === '"' && string[lastDoubleQuoteIdx - 2] === '\\') {
- return string;
- }
+ // Handle a special case where we may have a value like "\"test\"". We don't
+ // want to expand this to """"test"""" - so we terminate before processing the string
+ // further if we detect this either at the start or end of the double quote section.
+ const skip =
+ (candidate[1] === '\\' && candidate[2] === '"') ||
+ (candidate[candidate.length - 2] === '"' && candidate[candidate.length - 3] === '\\');
- const colonAndAnySpacing = string.slice(0, firstDoubleQuoteIdx);
- const rawStringifiedValue = string.slice(firstDoubleQuoteIdx, string.length);
- // Remove one level of JSON stringification
- const jsonValue = JSON.parse(rawStringifiedValue);
- return `${colonAndAnySpacing}"""${jsonValue}"""`;
+ if (!skip && candidate.match(/\\./)) {
+ result += `"""${JSON.parse(candidate)}"""`;
} else {
- return string;
+ result += candidate;
+ }
+
+ if (stringValues[x + 1]) {
+ // Add any JSON between string values
+ result += data.substring(endIndex + 1, stringValues[x + 1].startIndex);
}
- });
+ }
+
+ // Add any remaining JSON after all string values
+ result += data.substring(stringValues[stringValues.length - 1].endIndex + 1);
+
+ return result;
}
diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts
new file mode 100644
index 0000000000000..2cbd886860a07
--- /dev/null
+++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts
@@ -0,0 +1,97 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+type StringValues = Array<{ startIndex: number; endIndex: number }>;
+
+interface ParseResult {
+ stringValues: StringValues;
+}
+
+const JSON_COLON = ':';
+const JSON_STRING_DELIMITER = '"';
+const JSON_STRING_ESCAPE = '\\';
+
+/**
+ * Accepts JSON (as a string) and extracts the positions of all JSON string
+ * values.
+ *
+ * For example:
+ *
+ * '{ "my_string_value": "is this", "my_number_value": 42 }'
+ *
+ * Would extract one result:
+ *
+ * [ { startIndex: 21, endIndex: 29 } ]
+ *
+ * This result maps to `"is this"` from the example JSON.
+ *
+ */
+export const extractJSONStringValues = (input: string): ParseResult => {
+ let position = 0;
+ let currentStringStartPos: number;
+ let isInsideString = false;
+ const stringValues: StringValues = [];
+
+ function read() {
+ return input[position];
+ }
+
+ function peekNextNonWhitespace(): string | undefined {
+ let peekPosition = position + 1;
+
+ while (peekPosition < input.length) {
+ const peekChar = input[peekPosition];
+ if (peekChar.match(/[^\s\r\n]/)) {
+ return peekChar;
+ }
+ ++peekPosition;
+ }
+ }
+
+ function advance() {
+ ++position;
+ }
+
+ while (position < input.length) {
+ const char = read();
+ if (!isInsideString) {
+ if (char === JSON_STRING_DELIMITER) {
+ currentStringStartPos = position;
+ isInsideString = true;
+ }
+ // else continue scanning for JSON_STRING_DELIMITER
+ } else {
+ if (char === JSON_STRING_ESCAPE) {
+ // skip ahead - we are still inside of a string
+ advance();
+ } else if (char === JSON_STRING_DELIMITER) {
+ if (peekNextNonWhitespace() !== JSON_COLON) {
+ stringValues.push({
+ startIndex: currentStringStartPos!,
+ endIndex: position,
+ });
+ }
+ isInsideString = false;
+ }
+ }
+ advance();
+ }
+
+ return { stringValues };
+};
diff --git a/src/plugins/expressions/common/ast/build_function.test.ts b/src/plugins/expressions/common/ast/build_function.test.ts
index a2b54f31f6f8f..1f914d6f471e6 100644
--- a/src/plugins/expressions/common/ast/build_function.test.ts
+++ b/src/plugins/expressions/common/ast/build_function.test.ts
@@ -79,6 +79,11 @@ describe('buildExpressionFunction()', () => {
`);
});
+ test('ignores any args in initial state which value is undefined', () => {
+ const fn = buildExpressionFunction('hello', { world: undefined });
+ expect(fn.arguments).not.toHaveProperty('world');
+ });
+
test('returns all expected properties', () => {
const fn = buildExpressionFunction('hello', { world: [true] });
expect(Object.keys(fn)).toMatchInlineSnapshot(`
@@ -264,6 +269,18 @@ describe('buildExpressionFunction()', () => {
`);
});
+ test('does not add new argument if the value is undefined', () => {
+ const fn = buildExpressionFunction('hello', { world: [true] });
+ fn.addArgument('foo', undefined);
+ expect(fn.toAst().arguments).toMatchInlineSnapshot(`
+ Object {
+ "world": Array [
+ true,
+ ],
+ }
+ `);
+ });
+
test('mutates a function already associated with an expression', () => {
const fn = buildExpressionFunction('hello', { world: [true] });
const exp = buildExpression([fn]);
diff --git a/src/plugins/expressions/common/ast/build_function.ts b/src/plugins/expressions/common/ast/build_function.ts
index 5a1bd615d6450..6cd16b2bc1354 100644
--- a/src/plugins/expressions/common/ast/build_function.ts
+++ b/src/plugins/expressions/common/ast/build_function.ts
@@ -183,8 +183,10 @@ export function buildExpressionFunction<
acc[key] = value.map((v) => {
return isExpressionAst(v) ? buildExpression(v) : v;
});
- } else {
+ } else if (value !== undefined) {
acc[key] = isExpressionAst(value) ? [buildExpression(value)] : [value];
+ } else {
+ delete acc[key];
}
return acc;
}, initialArgs as FunctionBuilderArguments);
@@ -195,10 +197,12 @@ export function buildExpressionFunction<
arguments: args,
addArgument(key, value) {
- if (!args.hasOwnProperty(key)) {
- args[key] = [];
+ if (value !== undefined) {
+ if (!args.hasOwnProperty(key)) {
+ args[key] = [];
+ }
+ args[key].push(value);
}
- args[key].push(value);
return this;
},
diff --git a/src/plugins/expressions/kibana.json b/src/plugins/expressions/kibana.json
index 5163331088103..67bbf4b6e5454 100644
--- a/src/plugins/expressions/kibana.json
+++ b/src/plugins/expressions/kibana.json
@@ -3,9 +3,6 @@
"version": "kibana",
"server": true,
"ui": true,
- "requiredPlugins": [
- "bfetch"
- ],
"extraPublicDirs": ["common", "common/fonts"],
"requiredBundles": [
"kibanaUtils",
diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts
index e07a22a5e1d60..bf8b442769563 100644
--- a/src/plugins/expressions/public/loader.test.ts
+++ b/src/plugins/expressions/public/loader.test.ts
@@ -20,7 +20,7 @@
import { first, skip, toArray } from 'rxjs/operators';
import { loader, ExpressionLoader } from './loader';
import { Observable } from 'rxjs';
-import { ExpressionAstExpression, parseExpression, IInterpreterRenderHandlers } from '../common';
+import { parseExpression, IInterpreterRenderHandlers } from '../common';
// eslint-disable-next-line
const { __getLastExecution } = require('./services');
@@ -42,13 +42,6 @@ jest.mock('./services', () => {
const moduleMock = {
__execution: undefined,
__getLastExecution: () => moduleMock.__execution,
- getInterpreter: () => {
- return {
- interpretAst: async (expression: ExpressionAstExpression) => {
- return { type: 'render', as: 'test' };
- },
- };
- },
getRenderersRegistry: () => ({
get: (id: string) => renderers[id],
}),
diff --git a/src/plugins/expressions/public/mocks.tsx b/src/plugins/expressions/public/mocks.tsx
index 6e649c29ead7d..3865b4d20620a 100644
--- a/src/plugins/expressions/public/mocks.tsx
+++ b/src/plugins/expressions/public/mocks.tsx
@@ -21,7 +21,6 @@ import React from 'react';
import { ExpressionsSetup, ExpressionsStart, plugin as pluginInitializer } from '.';
import { coreMock } from '../../../core/public/mocks';
-import { bfetchPluginMock } from '../../bfetch/public/mocks';
export type Setup = jest.Mocked;
export type Start = jest.Mocked;
@@ -39,23 +38,6 @@ const createSetupContract = (): Setup => {
registerRenderer: jest.fn(),
registerType: jest.fn(),
run: jest.fn(),
- __LEGACY: {
- functions: {
- register: () => {},
- } as any,
- renderers: {
- register: () => {},
- } as any,
- types: {
- register: () => {},
- } as any,
- getExecutor: () => ({
- interpreter: {
- interpretAst: (() => {}) as any,
- },
- }),
- loadLegacyServerFunctionWrappers: () => Promise.resolve(),
- },
};
return setupContract;
};
@@ -84,9 +66,7 @@ const createPlugin = async () => {
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const plugin = pluginInitializer(pluginInitializerContext);
- const setup = await plugin.setup(coreSetup, {
- bfetch: bfetchPluginMock.createSetupContract(),
- });
+ const setup = await plugin.setup(coreSetup);
return {
pluginInitializerContext,
@@ -94,10 +74,7 @@ const createPlugin = async () => {
coreStart,
plugin,
setup,
- doStart: async () =>
- await plugin.start(coreStart, {
- bfetch: bfetchPluginMock.createStartContract(),
- }),
+ doStart: async () => await plugin.start(coreStart),
};
};
diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts
index ec60fbdf44c3a..9768ece899dd4 100644
--- a/src/plugins/expressions/public/plugin.ts
+++ b/src/plugins/expressions/public/plugin.ts
@@ -17,75 +17,19 @@
* under the License.
*/
-import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
-import { ExpressionExecutor } from './types';
+import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
import {
- ExpressionRendererRegistry,
- FunctionsRegistry,
- serializeProvider,
- TypesRegistry,
ExpressionsService,
ExpressionsServiceSetup,
ExpressionsServiceStart,
ExecutionContext,
} from '../common';
-import { BfetchPublicSetup, BfetchPublicStart } from '../../bfetch/public';
-import {
- setCoreStart,
- setInterpreter,
- setRenderersRegistry,
- setNotifications,
- setExpressionsService,
-} from './services';
+import { setRenderersRegistry, setNotifications, setExpressionsService } from './services';
import { ReactExpressionRenderer } from './react_expression_renderer';
import { ExpressionLoader, loader } from './loader';
import { render, ExpressionRenderHandler } from './render';
-export interface ExpressionsSetupDeps {
- bfetch: BfetchPublicSetup;
-}
-
-export interface ExpressionsStartDeps {
- bfetch: BfetchPublicStart;
-}
-
-export interface ExpressionsSetup extends ExpressionsServiceSetup {
- /**
- * @todo Get rid of these `__LEGACY` APIs.
- *
- * `__LEGACY` APIs are used by Canvas. It should be possible to stop
- * using all of them (except `loadLegacyServerFunctionWrappers`) and use
- * Kibana Platform plugin contracts instead.
- */
- __LEGACY: {
- /**
- * Use `registerType` and `getTypes` instead.
- */
- types: TypesRegistry;
-
- /**
- * Use `registerFunction` and `getFunctions` instead.
- */
- functions: FunctionsRegistry;
-
- /**
- * Use `registerRenderer` and `getRenderers`, and `getRenderer` instead.
- */
- renderers: ExpressionRendererRegistry;
-
- /**
- * Use `run` function instead.
- */
- getExecutor: () => ExpressionExecutor;
-
- /**
- * This function is used by Canvas to load server-side function and create
- * browser-side "wrapper" for each one. This function can be removed once
- * we enable expressions on server-side: https://github.com/elastic/kibana/issues/46906
- */
- loadLegacyServerFunctionWrappers: () => Promise;
- };
-}
+export type ExpressionsSetup = ExpressionsServiceSetup;
export interface ExpressionsStart extends ExpressionsServiceStart {
ExpressionLoader: typeof ExpressionLoader;
@@ -95,9 +39,7 @@ export interface ExpressionsStart extends ExpressionsServiceStart {
render: typeof render;
}
-export class ExpressionsPublicPlugin
- implements
- Plugin {
+export class ExpressionsPublicPlugin implements Plugin {
private readonly expressions: ExpressionsService = new ExpressionsService();
constructor(initializerContext: PluginInitializerContext) {}
@@ -116,68 +58,21 @@ export class ExpressionsPublicPlugin
});
}
- public setup(core: CoreSetup, { bfetch }: ExpressionsSetupDeps): ExpressionsSetup {
+ public setup(core: CoreSetup): ExpressionsSetup {
this.configureExecutor(core);
const { expressions } = this;
- const { executor, renderers } = expressions;
+ const { renderers } = expressions;
setRenderersRegistry(renderers);
- setExpressionsService(this.expressions);
+ setExpressionsService(expressions);
- const expressionsSetup = expressions.setup();
-
- // This is legacy. Should go away when we get rid of __LEGACY.
- const getExecutor = (): ExpressionExecutor => {
- return { interpreter: { interpretAst: expressionsSetup.run } };
- };
-
- setInterpreter(getExecutor().interpreter);
-
- let cached: Promise | null = null;
- const loadLegacyServerFunctionWrappers = async () => {
- if (!cached) {
- cached = (async () => {
- const serverFunctionList = await core.http.get(`/api/interpreter/fns`);
- const batchedFunction = bfetch.batchedFunction({ url: `/api/interpreter/fns` });
- const { serialize } = serializeProvider(executor.getTypes());
-
- // For every sever-side function, register a client-side
- // function that matches its definition, but which simply
- // calls the server-side function endpoint.
- Object.keys(serverFunctionList).forEach((functionName) => {
- if (expressionsSetup.getFunction(functionName)) {
- return;
- }
- const fn = () => ({
- ...serverFunctionList[functionName],
- fn: (input: any, args: any) => {
- return batchedFunction({ functionName, args, context: serialize(input) });
- },
- });
- expressionsSetup.registerFunction(fn);
- });
- })();
- }
- return cached;
- };
-
- const setup: ExpressionsSetup = {
- ...expressionsSetup,
- __LEGACY: {
- types: executor.types,
- functions: executor.functions,
- renderers,
- getExecutor,
- loadLegacyServerFunctionWrappers,
- },
- };
+ const setup = expressions.setup();
return Object.freeze(setup);
}
- public start(core: CoreStart, { bfetch }: ExpressionsStartDeps): ExpressionsStart {
- setCoreStart(core);
+ public start(core: CoreStart): ExpressionsStart {
setNotifications(core.notifications);
const { expressions } = this;
diff --git a/src/plugins/expressions/public/services.ts b/src/plugins/expressions/public/services.ts
index 016456c956666..e296566e661cd 100644
--- a/src/plugins/expressions/public/services.ts
+++ b/src/plugins/expressions/public/services.ts
@@ -18,22 +18,15 @@
*/
import { NotificationsStart } from 'kibana/public';
-import { createKibanaUtilsCore, createGetterSetter } from '../../kibana_utils/public';
-import { ExpressionInterpreter } from './types';
-import { ExpressionsSetup } from './plugin';
-import { ExpressionsService } from '../common';
+import { createGetterSetter } from '../../kibana_utils/public';
+import { ExpressionsService, ExpressionRendererRegistry } from '../common';
-export const { getCoreStart, setCoreStart } = createKibanaUtilsCore();
-
-export const [getInterpreter, setInterpreter] = createGetterSetter(
- 'Interpreter'
-);
export const [getNotifications, setNotifications] = createGetterSetter(
'Notifications'
);
export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter<
- ExpressionsSetup['__LEGACY']['renderers']
+ ExpressionRendererRegistry
>('Renderers registry');
export const [getExpressionsService, setExpressionsService] = createGetterSetter<
diff --git a/src/plugins/expressions/server/index.ts b/src/plugins/expressions/server/index.ts
index 9b2f0b794258b..6785457321595 100644
--- a/src/plugins/expressions/server/index.ts
+++ b/src/plugins/expressions/server/index.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { PluginInitializerContext } from '../../../core/server';
+import { PluginInitializerContext } from 'src/core/server';
import { ExpressionsServerPlugin } from './plugin';
export { ExpressionsServerSetup, ExpressionsServerStart } from './plugin';
diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts
deleted file mode 100644
index 8ff08542f18f8..0000000000000
--- a/src/plugins/expressions/server/legacy.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/* eslint-disable max-classes-per-file */
-
-// TODO: Remove this file once https://github.com/elastic/kibana/issues/46906 is complete.
-
-// @ts-ignore
-import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common';
-
-import Boom from 'boom';
-import { schema } from '@kbn/config-schema';
-import { CoreSetup, Logger, LegacyAPICaller } from 'src/core/server';
-import { ExpressionsServerSetupDependencies } from './plugin';
-import { typeSpecs, ExpressionType } from '../common';
-import { serializeProvider } from '../common';
-
-export class TypesRegistry extends Registry {
- wrapper(obj: any) {
- return new (ExpressionType as any)(obj);
- }
-}
-
-export class FunctionsRegistry extends Registry {
- wrapper(obj: any) {
- return new Fn(obj);
- }
-}
-
-export const registries = {
- types: new TypesRegistry(),
- serverFunctions: new FunctionsRegistry(),
-};
-
-export interface LegacyInterpreterServerApi {
- registries(): typeof registries;
- register(specs: Record): typeof registries;
-}
-
-export const createLegacyServerInterpreterApi = (): LegacyInterpreterServerApi => {
- const api = registryFactory(registries);
-
- register(registries, {
- types: typeSpecs,
- });
-
- return api;
-};
-
-export const createLegacyServerEndpoints = (
- api: LegacyInterpreterServerApi,
- logger: Logger,
- core: CoreSetup,
- plugins: ExpressionsServerSetupDependencies
-) => {
- const router = core.http.createRouter();
-
- /**
- * Register the endpoint that returns the list of server-only functions.
- */
- router.get(
- {
- path: `/api/interpreter/fns`,
- validate: {
- body: schema.any(),
- },
- },
- async (context, request, response) => {
- const functions = api.registries().serverFunctions.toJS();
- const body = JSON.stringify(functions);
- return response.ok({
- body,
- });
- }
- );
-
- /**
- * Run a single Canvas function.
- *
- * @param {*} server - The Kibana server object
- * @param {*} handlers - The Canvas handlers
- * @param {*} fnCall - Describes the function being run `{ functionName, args, context }`
- */
- async function runFunction(
- handlers: { environment: string; elasticsearchClient: LegacyAPICaller },
- fnCall: any
- ) {
- const { functionName, args, context } = fnCall;
- const { deserialize } = serializeProvider(registries.types.toJS());
- const fnDef = registries.serverFunctions.toJS()[functionName];
- if (!fnDef) throw Boom.notFound(`Function "${functionName}" could not be found.`);
- const deserialized = deserialize(context);
- const result = fnDef.fn(deserialized, args, handlers);
- return result;
- }
-
- /**
- * Register an endpoint that executes a batch of functions, and streams the
- * results back using ND-JSON.
- */
- plugins.bfetch.addBatchProcessingRoute(`/api/interpreter/fns`, (request) => {
- return {
- onBatchItem: async (fnCall: any) => {
- const [coreStart] = await core.getStartServices();
- const handlers = {
- environment: 'server',
- elasticsearchClient: coreStart.elasticsearch.legacy.client.asScoped(request)
- .callAsCurrentUser,
- };
- const result = await runFunction(handlers, fnCall);
- if (typeof result === 'undefined') {
- throw new Error(`Function ${fnCall.functionName} did not return anything.`);
- }
- return result;
- },
- };
- });
-};
diff --git a/src/plugins/expressions/server/mocks.ts b/src/plugins/expressions/server/mocks.ts
index e6b883e38f244..0512789d76ab0 100644
--- a/src/plugins/expressions/server/mocks.ts
+++ b/src/plugins/expressions/server/mocks.ts
@@ -20,7 +20,6 @@
import { ExpressionsServerSetup, ExpressionsServerStart } from '.';
import { plugin as pluginInitializer } from '.';
import { coreMock } from '../../../core/server/mocks';
-import { bfetchPluginMock } from '../../bfetch/server/mocks';
export type Setup = jest.Mocked;
export type Start = jest.Mocked;
@@ -38,10 +37,6 @@ const createSetupContract = (): Setup => {
registerRenderer: jest.fn(),
registerType: jest.fn(),
run: jest.fn(),
- __LEGACY: {
- register: jest.fn(),
- registries: jest.fn(),
- },
};
return setupContract;
};
@@ -67,9 +62,7 @@ const createPlugin = async () => {
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const plugin = pluginInitializer(pluginInitializerContext);
- const setup = await plugin.setup(coreSetup, {
- bfetch: bfetchPluginMock.createSetupContract(),
- });
+ const setup = await plugin.setup(coreSetup);
return {
pluginInitializerContext,
@@ -77,10 +70,7 @@ const createPlugin = async () => {
coreStart,
plugin,
setup,
- doStart: async () =>
- await plugin.start(coreStart, {
- bfetch: bfetchPluginMock.createStartContract(),
- }),
+ doStart: async () => await plugin.start(coreStart),
};
};
diff --git a/src/plugins/expressions/server/plugin.ts b/src/plugins/expressions/server/plugin.ts
index b99958262c542..9e412f9b33342 100644
--- a/src/plugins/expressions/server/plugin.ts
+++ b/src/plugins/expressions/server/plugin.ts
@@ -17,68 +17,30 @@
* under the License.
*/
-import { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
-import { BfetchServerSetup, BfetchServerStart } from '../../bfetch/server';
-import {
- LegacyInterpreterServerApi,
- createLegacyServerInterpreterApi,
- createLegacyServerEndpoints,
-} from './legacy';
+import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/server';
import { ExpressionsService, ExpressionsServiceSetup, ExpressionsServiceStart } from '../common';
-export interface ExpressionsServerSetupDependencies {
- bfetch: BfetchServerSetup;
-}
-
-export interface ExpressionsServerStartDependencies {
- bfetch: BfetchServerStart;
-}
-
-export interface ExpressionsServerSetup extends ExpressionsServiceSetup {
- __LEGACY: LegacyInterpreterServerApi;
-}
+export type ExpressionsServerSetup = ExpressionsServiceSetup;
export type ExpressionsServerStart = ExpressionsServiceStart;
export class ExpressionsServerPlugin
- implements
- Plugin<
- ExpressionsServerSetup,
- ExpressionsServerStart,
- ExpressionsServerSetupDependencies,
- ExpressionsServerStartDependencies
- > {
+ implements Plugin {
readonly expressions: ExpressionsService = new ExpressionsService();
- constructor(private readonly initializerContext: PluginInitializerContext) {}
-
- public setup(
- core: CoreSetup,
- plugins: ExpressionsServerSetupDependencies
- ): ExpressionsServerSetup {
- const logger = this.initializerContext.logger.get();
- const { expressions } = this;
- const { executor } = expressions;
+ constructor(initializerContext: PluginInitializerContext) {}
- executor.extendContext({
+ public setup(core: CoreSetup): ExpressionsServerSetup {
+ this.expressions.executor.extendContext({
environment: 'server',
});
- const legacyApi = createLegacyServerInterpreterApi();
- createLegacyServerEndpoints(legacyApi, logger, core, plugins);
-
- const setup = {
- ...this.expressions.setup(),
- __LEGACY: legacyApi,
- };
+ const setup = this.expressions.setup();
return Object.freeze(setup);
}
- public start(
- core: CoreStart,
- plugins: ExpressionsServerStartDependencies
- ): ExpressionsServerStart {
+ public start(core: CoreStart): ExpressionsServerStart {
const start = this.expressions.start();
return Object.freeze(start);
diff --git a/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts b/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts
index fb7b07c5dc1af..6b017fae1e21f 100644
--- a/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts
+++ b/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts
@@ -47,7 +47,8 @@ export function cloudwatchLogsSpecProvider(context: TutorialContext): TutorialSc
an AWS Lambda function. \
[Learn more]({learnMoreLink}).',
values: {
- learnMoreLink: '{config.docs.beats.functionbeat}/functionbeat-getting-started.html',
+ learnMoreLink:
+ '{config.docs.beats.functionbeat}/functionbeat-installation-configuration.html',
},
}),
euiIconType: 'logoAWS',
diff --git a/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts
index 2a6cfa0358709..b6f7aa8c53ac9 100644
--- a/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts
+++ b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts
@@ -31,9 +31,9 @@ export const createAuditbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Auditbeat',
}),
textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.install.osxTextPre', {
- defaultMessage: 'First time using Auditbeat? See the [Getting Started Guide]({linkUrl}).',
+ defaultMessage: 'First time using Auditbeat? See the [Quick Start]({linkUrl}).',
values: {
- linkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html',
+ linkUrl: '{config.docs.beats.auditbeat}/auditbeat-installation-configuration.html',
},
}),
commands: [
@@ -47,9 +47,9 @@ export const createAuditbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Auditbeat',
}),
textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.install.debTextPre', {
- defaultMessage: 'First time using Auditbeat? See the [Getting Started Guide]({linkUrl}).',
+ defaultMessage: 'First time using Auditbeat? See the [Quick Start]({linkUrl}).',
values: {
- linkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html',
+ linkUrl: '{config.docs.beats.auditbeat}/auditbeat-installation-configuration.html',
},
}),
commands: [
@@ -68,9 +68,9 @@ export const createAuditbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Auditbeat',
}),
textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.install.rpmTextPre', {
- defaultMessage: 'First time using Auditbeat? See the [Getting Started Guide]({linkUrl}).',
+ defaultMessage: 'First time using Auditbeat? See the [Quick Start]({linkUrl}).',
values: {
- linkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html',
+ linkUrl: '{config.docs.beats.auditbeat}/auditbeat-installation-configuration.html',
},
}),
commands: [
@@ -92,7 +92,7 @@ export const createAuditbeatInstructions = (context?: TutorialContext) => ({
'home.tutorials.common.auditbeatInstructions.install.windowsTextPre',
{
defaultMessage:
- 'First time using Auditbeat? See the [Getting Started Guide]({guideLinkUrl}).\n\
+ 'First time using Auditbeat? See the [Quick Start]({guideLinkUrl}).\n\
1. Download the Auditbeat Windows zip file from the [Download]({auditbeatLinkUrl}) page.\n\
2. Extract the contents of the zip file into {folderPath}.\n\
3. Rename the `{directoryName}` directory to `Auditbeat`.\n\
@@ -101,7 +101,7 @@ export const createAuditbeatInstructions = (context?: TutorialContext) => ({
5. From the PowerShell prompt, run the following commands to install Auditbeat as a Windows service.',
values: {
folderPath: '`C:\\Program Files`',
- guideLinkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html',
+ guideLinkUrl: '{config.docs.beats.auditbeat}/auditbeat-installation-configuration.html',
auditbeatLinkUrl: 'https://www.elastic.co/downloads/beats/auditbeat',
directoryName: 'auditbeat-{config.kibana.version}-windows',
},
diff --git a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts
index 0e99033b2ea69..c760840165bfc 100644
--- a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts
+++ b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts
@@ -31,9 +31,9 @@ export const createFilebeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Filebeat',
}),
textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.osxTextPre', {
- defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).',
+ defaultMessage: 'First time using Filebeat? See the [Quick Start]({linkUrl}).',
values: {
- linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ linkUrl: '{config.docs.beats.filebeat}/filebeat-installation-configuration.html',
},
}),
commands: [
@@ -47,9 +47,9 @@ export const createFilebeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Filebeat',
}),
textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.debTextPre', {
- defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).',
+ defaultMessage: 'First time using Filebeat? See the [Quick Start]({linkUrl}).',
values: {
- linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ linkUrl: '{config.docs.beats.filebeat}/filebeat-installation-configuration.html',
},
}),
commands: [
@@ -68,9 +68,9 @@ export const createFilebeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Filebeat',
}),
textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.rpmTextPre', {
- defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).',
+ defaultMessage: 'First time using Filebeat? See the [Quick Start]({linkUrl}).',
values: {
- linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ linkUrl: '{config.docs.beats.filebeat}/filebeat-installation-configuration.html',
},
}),
commands: [
@@ -90,7 +90,7 @@ export const createFilebeatInstructions = (context?: TutorialContext) => ({
}),
textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.windowsTextPre', {
defaultMessage:
- 'First time using Filebeat? See the [Getting Started Guide]({guideLinkUrl}).\n\
+ 'First time using Filebeat? See the [Quick Start]({guideLinkUrl}).\n\
1. Download the Filebeat Windows zip file from the [Download]({filebeatLinkUrl}) page.\n\
2. Extract the contents of the zip file into {folderPath}.\n\
3. Rename the `{directoryName}` directory to `Filebeat`.\n\
@@ -99,7 +99,7 @@ export const createFilebeatInstructions = (context?: TutorialContext) => ({
5. From the PowerShell prompt, run the following commands to install Filebeat as a Windows service.',
values: {
folderPath: '`C:\\Program Files`',
- guideLinkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html',
+ guideLinkUrl: '{config.docs.beats.filebeat}/filebeat-installation-configuration.html',
filebeatLinkUrl: 'https://www.elastic.co/downloads/beats/filebeat',
directoryName: 'filebeat-{config.kibana.version}-windows',
},
diff --git a/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts
index 06ff84146b5d8..61e76bd9d3c18 100644
--- a/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts
+++ b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts
@@ -31,8 +31,10 @@ export const createFunctionbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Functionbeat',
}),
textPre: i18n.translate('home.tutorials.common.functionbeatInstructions.install.osxTextPre', {
- defaultMessage: 'First time using Functionbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.functionbeat}/functionbeat-getting-started.html' },
+ defaultMessage: 'First time using Functionbeat? See the [Quick Start]({link}).',
+ values: {
+ link: '{config.docs.beats.functionbeat}/functionbeat-installation-configuration.html',
+ },
}),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/functionbeat/functionbeat-{config.kibana.version}-darwin-x86_64.tar.gz',
@@ -47,8 +49,10 @@ export const createFunctionbeatInstructions = (context?: TutorialContext) => ({
textPre: i18n.translate(
'home.tutorials.common.functionbeatInstructions.install.linuxTextPre',
{
- defaultMessage: 'First time using Functionbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.functionbeat}/functionbeat-getting-started.html' },
+ defaultMessage: 'First time using Functionbeat? See the [Quick Start]({link}).',
+ values: {
+ link: '{config.docs.beats.functionbeat}/functionbeat-installation-configuration.html',
+ },
}
),
commands: [
@@ -65,7 +69,7 @@ export const createFunctionbeatInstructions = (context?: TutorialContext) => ({
'home.tutorials.common.functionbeatInstructions.install.windowsTextPre',
{
defaultMessage:
- 'First time using Functionbeat? See the [Getting Started Guide]({functionbeatLink}).\n\
+ 'First time using Functionbeat? See the [Quick Start]({functionbeatLink}).\n\
1. Download the Functionbeat Windows zip file from the [Download]({elasticLink}) page.\n\
2. Extract the contents of the zip file into {folderPath}.\n\
3. Rename the {directoryName} directory to `Functionbeat`.\n\
@@ -75,7 +79,8 @@ export const createFunctionbeatInstructions = (context?: TutorialContext) => ({
values: {
directoryName: '`functionbeat-{config.kibana.version}-windows`',
folderPath: '`C:\\Program Files`',
- functionbeatLink: '{config.docs.beats.functionbeat}/functionbeat-getting-started.html',
+ functionbeatLink:
+ '{config.docs.beats.functionbeat}/functionbeat-installation-configuration.html',
elasticLink: 'https://www.elastic.co/downloads/beats/functionbeat',
},
}
diff --git a/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts
index fa5bf5df13b6b..4d519ad8aa01e 100644
--- a/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts
+++ b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts
@@ -31,8 +31,8 @@ export const createHeartbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Heartbeat',
}),
textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.install.osxTextPre', {
- defaultMessage: 'First time using Heartbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html' },
+ defaultMessage: 'First time using Heartbeat? See the [Quick Start]({link}).',
+ values: { link: '{config.docs.beats.heartbeat}/heartbeat-installation-configuration.html' },
}),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/heartbeat/heartbeat-{config.kibana.version}-darwin-x86_64.tar.gz',
@@ -45,8 +45,8 @@ export const createHeartbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Heartbeat',
}),
textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.install.debTextPre', {
- defaultMessage: 'First time using Heartbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html' },
+ defaultMessage: 'First time using Heartbeat? See the [Quick Start]({link}).',
+ values: { link: '{config.docs.beats.heartbeat}/heartbeat-installation-configuration.html' },
}),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/heartbeat/heartbeat-{config.kibana.version}-amd64.deb',
@@ -62,8 +62,8 @@ export const createHeartbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Heartbeat',
}),
textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.install.rpmTextPre', {
- defaultMessage: 'First time using Heartbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html' },
+ defaultMessage: 'First time using Heartbeat? See the [Quick Start]({link}).',
+ values: { link: '{config.docs.beats.heartbeat}/heartbeat-installation-configuration.html' },
}),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/heartbeat/heartbeat-{config.kibana.version}-x86_64.rpm',
@@ -82,7 +82,7 @@ export const createHeartbeatInstructions = (context?: TutorialContext) => ({
'home.tutorials.common.heartbeatInstructions.install.windowsTextPre',
{
defaultMessage:
- 'First time using Heartbeat? See the [Getting Started Guide]({heartbeatLink}).\n\
+ 'First time using Heartbeat? See the [Quick Start]({heartbeatLink}).\n\
1. Download the Heartbeat Windows zip file from the [Download]({elasticLink}) page.\n\
2. Extract the contents of the zip file into {folderPath}.\n\
3. Rename the {directoryName} directory to `Heartbeat`.\n\
@@ -92,7 +92,8 @@ export const createHeartbeatInstructions = (context?: TutorialContext) => ({
values: {
directoryName: '`heartbeat-{config.kibana.version}-windows`',
folderPath: '`C:\\Program Files`',
- heartbeatLink: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html',
+ heartbeatLink:
+ '{config.docs.beats.heartbeat}/heartbeat-installation-configuration.html',
elasticLink: 'https://www.elastic.co/downloads/beats/heartbeat',
},
}
@@ -357,7 +358,7 @@ export function heartbeatEnableInstructionsOnPrem() {
'Where {hostTemplate} is your monitored URL, For more details on how to configure Monitors in \
Heartbeat, read the [Heartbeat configuration docs.]({configureLink})',
values: {
- configureLink: '{config.docs.beats.heartbeat}/heartbeat-configuration.html',
+ configureLink: '{config.docs.beats.heartbeat}/configuring-howto-heartbeat.html',
hostTemplate: '``',
},
}
@@ -428,7 +429,7 @@ export function heartbeatEnableInstructionsCloud() {
{
defaultMessage:
'For more details on how to configure Monitors in Heartbeat, read the [Heartbeat configuration docs.]({configureLink})',
- values: { configureLink: '{config.docs.beats.heartbeat}/heartbeat-configuration.html' },
+ values: { configureLink: '{config.docs.beats.heartbeat}/configuring-howto-heartbeat.html' },
}
);
return {
diff --git a/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts
index 651405941610f..cce93e0dfb527 100644
--- a/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts
+++ b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts
@@ -31,8 +31,10 @@ export const createMetricbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Metricbeat',
}),
textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.install.osxTextPre', {
- defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' },
+ defaultMessage: 'First time using Metricbeat? See the [Quick Start]({link}).',
+ values: {
+ link: '{config.docs.beats.metricbeat}/metricbeat-installation-configuration.html',
+ },
}),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-darwin-x86_64.tar.gz',
@@ -45,8 +47,10 @@ export const createMetricbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Metricbeat',
}),
textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.install.debTextPre', {
- defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' },
+ defaultMessage: 'First time using Metricbeat? See the [Quick Start]({link}).',
+ values: {
+ link: '{config.docs.beats.metricbeat}/metricbeat-installation-configuration.html',
+ },
}),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-amd64.deb',
@@ -62,8 +66,10 @@ export const createMetricbeatInstructions = (context?: TutorialContext) => ({
defaultMessage: 'Download and install Metricbeat',
}),
textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.install.rpmTextPre', {
- defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).',
- values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' },
+ defaultMessage: 'First time using Metricbeat? See the [Quick Start]({link}).',
+ values: {
+ link: '{config.docs.beats.metricbeat}/metricbeat-installation-configuration.html',
+ },
}),
commands: [
'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-x86_64.rpm',
@@ -82,7 +88,7 @@ export const createMetricbeatInstructions = (context?: TutorialContext) => ({
'home.tutorials.common.metricbeatInstructions.install.windowsTextPre',
{
defaultMessage:
- 'First time using Metricbeat? See the [Getting Started Guide]({metricbeatLink}).\n\
+ 'First time using Metricbeat? See the [Quick Start]({metricbeatLink}).\n\
1. Download the Metricbeat Windows zip file from the [Download]({elasticLink}) page.\n\
2. Extract the contents of the zip file into {folderPath}.\n\
3. Rename the {directoryName} directory to `Metricbeat`.\n\
@@ -92,7 +98,8 @@ export const createMetricbeatInstructions = (context?: TutorialContext) => ({
values: {
directoryName: '`metricbeat-{config.kibana.version}-windows`',
folderPath: '`C:\\Program Files`',
- metricbeatLink: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html',
+ metricbeatLink:
+ '{config.docs.beats.metricbeat}/metricbeat-installation-configuration.html',
elasticLink: 'https://www.elastic.co/downloads/beats/metricbeat',
},
}
diff --git a/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts
index 27d7822e080a3..1eacbb729aee4 100644
--- a/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts
+++ b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts
@@ -34,7 +34,7 @@ export const createWinlogbeatInstructions = (context?: TutorialContext) => ({
'home.tutorials.common.winlogbeatInstructions.install.windowsTextPre',
{
defaultMessage:
- 'First time using Winlogbeat? See the [Getting Started Guide]({winlogbeatLink}).\n\
+ 'First time using Winlogbeat? See the [Quick Start]({winlogbeatLink}).\n\
1. Download the Winlogbeat Windows zip file from the [Download]({elasticLink}) page.\n\
2. Extract the contents of the zip file into {folderPath}.\n\
3. Rename the {directoryName} directory to `Winlogbeat`.\n\
@@ -44,7 +44,8 @@ export const createWinlogbeatInstructions = (context?: TutorialContext) => ({
values: {
directoryName: '`winlogbeat-{config.kibana.version}-windows`',
folderPath: '`C:\\Program Files`',
- winlogbeatLink: '{config.docs.beats.winlogbeat}/winlogbeat-getting-started.html',
+ winlogbeatLink:
+ '{config.docs.beats.winlogbeat}/winlogbeat-installation-configuration.html',
elasticLink: 'https://www.elastic.co/downloads/beats/winlogbeat',
},
}
diff --git a/src/plugins/home/server/tutorials/uptime_monitors/index.ts b/src/plugins/home/server/tutorials/uptime_monitors/index.ts
index 7366583e59778..96b81c9fb4181 100644
--- a/src/plugins/home/server/tutorials/uptime_monitors/index.ts
+++ b/src/plugins/home/server/tutorials/uptime_monitors/index.ts
@@ -47,7 +47,7 @@ export function uptimeMonitorsSpecProvider(context: TutorialContext): TutorialSc
Given a list of URLs, Heartbeat asks the simple question: Are you alive? \
[Learn more]({learnMoreLink}).',
values: {
- learnMoreLink: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html',
+ learnMoreLink: '{config.docs.beats.heartbeat}/heartbeat-installation-configuration.html',
},
}),
euiIconType: 'uptimeApp',
diff --git a/src/plugins/kibana_usage_collection/README.md b/src/plugins/kibana_usage_collection/README.md
index 6ef4f19c1570f..73a4d53f305f2 100644
--- a/src/plugins/kibana_usage_collection/README.md
+++ b/src/plugins/kibana_usage_collection/README.md
@@ -7,3 +7,4 @@ This plugin registers the basic usage collectors from Kibana:
- Ops stats
- Number of Saved Objects per type
- Non-default UI Settings
+- CSP configuration
diff --git a/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap b/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap
index f07912eff02b7..47a4c458a8398 100644
--- a/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap
+++ b/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap
@@ -9,3 +9,5 @@ exports[`kibana_usage_collection Runs the setup method without issues 3`] = `fal
exports[`kibana_usage_collection Runs the setup method without issues 4`] = `false`;
exports[`kibana_usage_collection Runs the setup method without issues 5`] = `false`;
+
+exports[`kibana_usage_collection Runs the setup method without issues 6`] = `true`;
diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.test.ts
similarity index 79%
rename from src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts
rename to src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.test.ts
index 63c2cbec21b57..465b21e3578ba 100644
--- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.test.ts
@@ -17,35 +17,24 @@
* under the License.
*/
-import { CspConfig, ICspConfig } from '../../../../../../core/server';
+import { CspConfig, ICspConfig } from '../../../../../core/server';
import { createCspCollector } from './csp_collector';
-
-const createMockKbnServer = () => ({
- newPlatform: {
- setup: {
- core: {
- http: {
- csp: new CspConfig(),
- },
- },
- },
- },
-});
+import { httpServiceMock } from '../../../../../core/server/mocks';
describe('csp collector', () => {
- let kbnServer: ReturnType;
+ let httpMock: ReturnType;
const mockCallCluster = null as any;
function updateCsp(config: Partial) {
- kbnServer.newPlatform.setup.core.http.csp = new CspConfig(config);
+ httpMock.csp = new CspConfig(config);
}
beforeEach(() => {
- kbnServer = createMockKbnServer();
+ httpMock = httpServiceMock.createSetupContract();
});
test('fetches whether strict mode is enabled', async () => {
- const collector = createCspCollector(kbnServer as any);
+ const collector = createCspCollector(httpMock);
expect((await collector.fetch(mockCallCluster)).strict).toEqual(true);
@@ -54,7 +43,7 @@ describe('csp collector', () => {
});
test('fetches whether the legacy browser warning is enabled', async () => {
- const collector = createCspCollector(kbnServer as any);
+ const collector = createCspCollector(httpMock);
expect((await collector.fetch(mockCallCluster)).warnLegacyBrowsers).toEqual(true);
@@ -63,7 +52,7 @@ describe('csp collector', () => {
});
test('fetches whether the csp rules have been changed or not', async () => {
- const collector = createCspCollector(kbnServer as any);
+ const collector = createCspCollector(httpMock);
expect((await collector.fetch(mockCallCluster)).rulesChangedFromDefault).toEqual(false);
@@ -72,7 +61,7 @@ describe('csp collector', () => {
});
test('does not include raw csp rules under any property names', async () => {
- const collector = createCspCollector(kbnServer as any);
+ const collector = createCspCollector(httpMock);
// It's important that we do not send the value of csp.rules here as it
// can be customized with values that can be identifiable to given
diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.ts
similarity index 75%
rename from src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts
rename to src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.ts
index 9c124a90e66eb..c45a83588ee44 100644
--- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/csp/csp_collector.ts
@@ -17,12 +17,8 @@
* under the License.
*/
-import { Server } from 'hapi';
-import { CspConfig } from '../../../../../../core/server';
-import {
- UsageCollectionSetup,
- CollectorOptions,
-} from '../../../../../../plugins/usage_collection/server';
+import { UsageCollectionSetup, CollectorOptions } from 'src/plugins/usage_collection/server';
+import { HttpServiceSetup, CspConfig } from '../../../../../core/server';
interface Usage {
strict: boolean;
@@ -30,12 +26,12 @@ interface Usage {
rulesChangedFromDefault: boolean;
}
-export function createCspCollector(server: Server): CollectorOptions {
+export function createCspCollector(http: HttpServiceSetup): CollectorOptions {
return {
type: 'csp',
isReady: () => true,
async fetch() {
- const { strict, warnLegacyBrowsers, header } = server.newPlatform.setup.core.http.csp;
+ const { strict, warnLegacyBrowsers, header } = http.csp;
return {
strict,
@@ -60,8 +56,11 @@ export function createCspCollector(server: Server): CollectorOptions {
};
}
-export function registerCspCollector(usageCollection: UsageCollectionSetup, server: Server): void {
- const collectorConfig = createCspCollector(server);
- const collector = usageCollection.makeUsageCollector(collectorConfig);
+export function registerCspCollector(
+ usageCollection: UsageCollectionSetup,
+ http: HttpServiceSetup
+): void {
+ const collectorOptions = createCspCollector(http);
+ const collector = usageCollection.makeUsageCollector(collectorOptions);
usageCollection.registerCollector(collector);
}
diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/index.ts b/src/plugins/kibana_usage_collection/server/collectors/csp/index.ts
similarity index 100%
rename from src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/index.ts
rename to src/plugins/kibana_usage_collection/server/collectors/csp/index.ts
diff --git a/src/plugins/kibana_usage_collection/server/collectors/index.ts b/src/plugins/kibana_usage_collection/server/collectors/index.ts
index 1ca237528b41f..1f9fe130fa45d 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/index.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/index.ts
@@ -22,3 +22,4 @@ export { registerManagementUsageCollector } from './management';
export { registerApplicationUsageCollector } from './application_usage';
export { registerKibanaUsageCollector } from './kibana';
export { registerOpsStatsCollector } from './ops_stats';
+export { registerCspCollector } from './csp';
diff --git a/src/plugins/kibana_usage_collection/server/plugin.ts b/src/plugins/kibana_usage_collection/server/plugin.ts
index 803a9146bd08f..d4295c770803e 100644
--- a/src/plugins/kibana_usage_collection/server/plugin.ts
+++ b/src/plugins/kibana_usage_collection/server/plugin.ts
@@ -37,6 +37,7 @@ import {
registerManagementUsageCollector,
registerOpsStatsCollector,
registerUiMetricUsageCollector,
+ registerCspCollector,
} from './collectors';
interface KibanaUsageCollectionPluginsDepsSetup {
@@ -56,12 +57,9 @@ export class KibanaUsageCollectionPlugin implements Plugin {
this.metric$ = new Subject();
}
- public setup(
- { savedObjects }: CoreSetup,
- { usageCollection }: KibanaUsageCollectionPluginsDepsSetup
- ) {
- this.registerUsageCollectors(usageCollection, this.metric$, (opts) =>
- savedObjects.registerType(opts)
+ public setup(coreSetup: CoreSetup, { usageCollection }: KibanaUsageCollectionPluginsDepsSetup) {
+ this.registerUsageCollectors(usageCollection, coreSetup, this.metric$, (opts) =>
+ coreSetup.savedObjects.registerType(opts)
);
}
@@ -79,6 +77,7 @@ export class KibanaUsageCollectionPlugin implements Plugin {
private registerUsageCollectors(
usageCollection: UsageCollectionSetup,
+ coreSetup: CoreSetup,
metric$: Subject,
registerType: SavedObjectsRegisterType
) {
@@ -90,5 +89,6 @@ export class KibanaUsageCollectionPlugin implements Plugin {
registerManagementUsageCollector(usageCollection, getUiSettingsClient);
registerUiMetricUsageCollector(usageCollection, registerType, getSavedObjectsClient);
registerApplicationUsageCollector(usageCollection, registerType, getSavedObjectsClient);
+ registerCspCollector(usageCollection, coreSetup.http);
}
}
diff --git a/src/plugins/kibana_utils/public/core/index.ts b/src/plugins/kibana_utils/public/core/index.ts
index 8bbb2129071f5..5fb557f651e3a 100644
--- a/src/plugins/kibana_utils/public/core/index.ts
+++ b/src/plugins/kibana_utils/public/core/index.ts
@@ -17,5 +17,4 @@
* under the License.
*/
-export * from './create_kibana_utils_core';
export * from './create_start_service_getter';
diff --git a/src/plugins/kibana_utils/public/render_complete/index.ts b/src/plugins/kibana_utils/public/render_complete/index.ts
index ec86144f65a35..8f14b6ca8f0ab 100644
--- a/src/plugins/kibana_utils/public/render_complete/index.ts
+++ b/src/plugins/kibana_utils/public/render_complete/index.ts
@@ -17,19 +17,5 @@
* under the License.
*/
-const dispatchCustomEvent = (el: HTMLElement, eventName: string) => {
- // we're using the native events so that we aren't tied to the jQuery custom events,
- // otherwise we have to use jQuery(element).on(...) because jQuery's events sit on top
- // of the native events per https://github.com/jquery/jquery/issues/2476
- el.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
-};
-
-export function dispatchRenderComplete(el: HTMLElement) {
- dispatchCustomEvent(el, 'renderComplete');
-}
-
-export function dispatchRenderStart(el: HTMLElement) {
- dispatchCustomEvent(el, 'renderStart');
-}
-
-export * from './render_complete_helper';
+export * from './render_complete_listener';
+export * from './render_complete_dispatcher';
diff --git a/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts b/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts
new file mode 100644
index 0000000000000..386bf1eb62568
--- /dev/null
+++ b/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts
@@ -0,0 +1,83 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+const dispatchEvent = (el: HTMLElement, eventName: string) => {
+ el.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
+};
+
+export function dispatchRenderComplete(el: HTMLElement) {
+ dispatchEvent(el, 'renderComplete');
+}
+
+export function dispatchRenderStart(el: HTMLElement) {
+ dispatchEvent(el, 'renderStart');
+}
+
+/**
+ * Should call `dispatchComplete()` when UI block has finished loading its data and has
+ * completely rendered. Should `dispatchInProgress()` every time UI block
+ * starts loading data again. At the start it is assumed that UI block is loading
+ * so it dispatches "in progress" automatically, so you need to call `setRenderComplete`
+ * at least once.
+ *
+ * This is used for reporting to know that UI block is ready, so
+ * it can take a screenshot. It is also used in functional tests to know that
+ * page has stabilized.
+ */
+export class RenderCompleteDispatcher {
+ private count: number = 0;
+ private el?: HTMLElement;
+
+ constructor(el?: HTMLElement) {
+ this.setEl(el);
+ }
+
+ public setEl(el?: HTMLElement) {
+ this.el = el;
+ this.count = 0;
+ if (el) this.dispatchInProgress();
+ }
+
+ public dispatchInProgress() {
+ if (!this.el) return;
+ this.el.setAttribute('data-render-complete', 'false');
+ this.el.setAttribute('data-rendering-count', String(this.count));
+ dispatchRenderStart(this.el);
+ }
+
+ public dispatchComplete() {
+ if (!this.el) return;
+ this.count++;
+ this.el.setAttribute('data-render-complete', 'true');
+ this.el.setAttribute('data-rendering-count', String(this.count));
+ dispatchRenderComplete(this.el);
+ }
+
+ public dispatchError() {
+ if (!this.el) return;
+ this.count++;
+ this.el.setAttribute('data-render-complete', 'false');
+ this.el.setAttribute('data-rendering-count', String(this.count));
+ }
+
+ public setTitle(title: string) {
+ if (!this.el) return;
+ this.el.setAttribute('data-title', title);
+ }
+}
diff --git a/src/plugins/kibana_utils/public/render_complete/render_complete_helper.ts b/src/plugins/kibana_utils/public/render_complete/render_complete_listener.ts
similarity index 80%
rename from src/plugins/kibana_utils/public/render_complete/render_complete_helper.ts
rename to src/plugins/kibana_utils/public/render_complete/render_complete_listener.ts
index 1230638a1d709..d9289b20d4bfa 100644
--- a/src/plugins/kibana_utils/public/render_complete/render_complete_helper.ts
+++ b/src/plugins/kibana_utils/public/render_complete/render_complete_listener.ts
@@ -17,9 +17,9 @@
* under the License.
*/
-const attributeName = 'data-render-complete';
+export class RenderCompleteListener {
+ private readonly attributeName = 'data-render-complete';
-export class RenderCompleteHelper {
constructor(private readonly element: HTMLElement) {
this.setup();
}
@@ -30,23 +30,23 @@ export class RenderCompleteHelper {
};
public setup = () => {
- this.element.setAttribute(attributeName, 'false');
+ this.element.setAttribute(this.attributeName, 'false');
this.element.addEventListener('renderStart', this.start);
this.element.addEventListener('renderComplete', this.complete);
};
public disable = () => {
- this.element.setAttribute(attributeName, 'disabled');
+ this.element.setAttribute(this.attributeName, 'disabled');
this.destroy();
};
private start = () => {
- this.element.setAttribute(attributeName, 'false');
+ this.element.setAttribute(this.attributeName, 'false');
return true;
};
private complete = () => {
- this.element.setAttribute(attributeName, 'true');
+ this.element.setAttribute(this.attributeName, 'true');
return true;
};
}
diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx
index 63863b0826f11..ce08151d37c2c 100644
--- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx
+++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx
@@ -32,6 +32,7 @@ interface SaveModalDocumentInfo {
interface OriginSaveModalProps {
originatingApp?: string;
+ getAppNameFromId?: (appId: string) => string | undefined;
documentInfo: SaveModalDocumentInfo;
objectType: string;
onClose: () => void;
@@ -53,16 +54,13 @@ export function SavedObjectSaveModalOrigin(props: OriginSaveModalProps) {
if (!props.originatingApp) {
return;
}
- let origin = props.originatingApp!;
-
- // TODO: Remove this after https://github.com/elastic/kibana/pull/63443
- if (origin.startsWith('kibana:')) {
- origin = origin.split(':')[1];
- }
+ const origin = props.getAppNameFromId
+ ? props.getAppNameFromId(props.originatingApp) || props.originatingApp
+ : props.originatingApp;
if (
!state.copyOnSave ||
- origin === 'dashboards' // dashboard supports adding a copied panel on save...
+ props.originatingApp === 'dashboards' // dashboard supports adding a copied panel on save...
) {
const originVerb = !documentInfo.id || state.copyOnSave ? addLabel : returnLabel;
return (
diff --git a/src/plugins/telemetry/schema/legacy_oss_plugins.json b/src/plugins/telemetry/schema/legacy_oss_plugins.json
deleted file mode 100644
index e660ccac9dc36..0000000000000
--- a/src/plugins/telemetry/schema/legacy_oss_plugins.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "properties": {
- "csp": {
- "properties": {
- "strict": {
- "type": "boolean"
- },
- "warnLegacyBrowsers": {
- "type": "boolean"
- },
- "rulesChangedFromDefault": {
- "type": "boolean"
- }
- }
- }
- }
-}
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts
index 2d0864b1cb75f..7e4176281db41 100644
--- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts
+++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts
@@ -128,6 +128,9 @@ export const DATA_DATASETS_INDEX_PATTERNS = [
{ pattern: '*suricata*', patternName: 'suricata' },
// { pattern: '*fsf*', patternName: 'fsf' }, // Disabled because it's too vague
{ pattern: '*wazuh*', patternName: 'wazuh' },
+
+ // meow attacks
+ { pattern: '*meow*', patternName: 'meow' },
] as const;
// Get the unique list of index patterns (some are duplicated for documentation purposes)
diff --git a/src/plugins/vis_type_markdown/kibana.json b/src/plugins/vis_type_markdown/kibana.json
index 9241f5eeee837..4196bd7e85707 100644
--- a/src/plugins/vis_type_markdown/kibana.json
+++ b/src/plugins/vis_type_markdown/kibana.json
@@ -4,5 +4,5 @@
"ui": true,
"server": true,
"requiredPlugins": ["expressions", "visualizations"],
- "requiredBundles": ["kibanaUtils", "kibanaReact", "data", "charts"]
+ "requiredBundles": ["kibanaUtils", "kibanaReact", "data", "charts", "visualizations", "expressions"]
}
diff --git a/src/plugins/vis_type_markdown/public/__snapshots__/markdown_fn.test.ts.snap b/src/plugins/vis_type_markdown/public/__snapshots__/markdown_fn.test.ts.snap
index 5a107bdfed9e5..473e2cba742b7 100644
--- a/src/plugins/vis_type_markdown/public/__snapshots__/markdown_fn.test.ts.snap
+++ b/src/plugins/vis_type_markdown/public/__snapshots__/markdown_fn.test.ts.snap
@@ -2,7 +2,7 @@
exports[`interpreter/functions#markdown returns an object with the correct structure 1`] = `
Object {
- "as": "visualization",
+ "as": "markdown_vis",
"type": "render",
"value": Object {
"visConfig": Object {
diff --git a/src/plugins/vis_type_markdown/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_markdown/public/__snapshots__/to_ast.test.ts.snap
new file mode 100644
index 0000000000000..2b8ff47be3f23
--- /dev/null
+++ b/src/plugins/vis_type_markdown/public/__snapshots__/to_ast.test.ts.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`markdown vis toExpressionAst function with params 1`] = `
+Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "font": Array [
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "size": Array [
+ 15,
+ ],
+ },
+ "function": "font",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ ],
+ "markdown": Array [
+ "### my markdown",
+ ],
+ "openLinksInNewTab": Array [
+ true,
+ ],
+ },
+ "function": "markdownVis",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+}
+`;
+
+exports[`markdown vis toExpressionAst function without params 1`] = `
+Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "font": Array [
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "size": Array [
+ "undefined",
+ ],
+ },
+ "function": "font",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ ],
+ },
+ "function": "markdownVis",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+}
+`;
diff --git a/src/plugins/vis_type_markdown/public/markdown_fn.ts b/src/plugins/vis_type_markdown/public/markdown_fn.ts
index 9f0809109e465..4b3c9989431f9 100644
--- a/src/plugins/vis_type_markdown/public/markdown_fn.ts
+++ b/src/plugins/vis_type_markdown/public/markdown_fn.ts
@@ -26,12 +26,14 @@ interface RenderValue {
visConfig: MarkdownVisParams;
}
-export const createMarkdownVisFn = (): ExpressionFunctionDefinition<
+export type MarkdownVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
'markdownVis',
unknown,
Arguments,
Render
-> => ({
+>;
+
+export const createMarkdownVisFn = (): MarkdownVisExpressionFunctionDefinition => ({
name: 'markdownVis',
type: 'render',
inputTypes: [],
@@ -65,7 +67,7 @@ export const createMarkdownVisFn = (): ExpressionFunctionDefinition<
fn(input, args) {
return {
type: 'render',
- as: 'visualization',
+ as: 'markdown_vis',
value: {
visType: 'markdown',
visConfig: {
diff --git a/src/plugins/vis_type_markdown/public/markdown_renderer.tsx b/src/plugins/vis_type_markdown/public/markdown_renderer.tsx
new file mode 100644
index 0000000000000..5950a762635b2
--- /dev/null
+++ b/src/plugins/vis_type_markdown/public/markdown_renderer.tsx
@@ -0,0 +1,57 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { VisualizationContainer } from '../../visualizations/public';
+import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers';
+import { MarkdownVisWrapper } from './markdown_vis_controller';
+import { StartServicesGetter } from '../../kibana_utils/public';
+
+export const getMarkdownRenderer = (start: StartServicesGetter) => {
+ const markdownVisRenderer: () => ExpressionRenderDefinition = () => ({
+ name: 'markdown_vis',
+ displayName: 'markdown visualization',
+ reuseDomNode: true,
+ render: async (domNode: HTMLElement, config: any, handlers: any) => {
+ const { visConfig } = config;
+
+ const I18nContext = await start().core.i18n.Context;
+
+ handlers.onDestroy(() => {
+ unmountComponentAtNode(domNode);
+ });
+
+ render(
+
+
+
+
+ ,
+ domNode
+ );
+ },
+ });
+
+ return markdownVisRenderer;
+};
diff --git a/src/plugins/vis_type_markdown/public/markdown_vis.ts b/src/plugins/vis_type_markdown/public/markdown_vis.ts
index 089e00bb44937..27ac038aee6ff 100644
--- a/src/plugins/vis_type_markdown/public/markdown_vis.ts
+++ b/src/plugins/vis_type_markdown/public/markdown_vis.ts
@@ -19,10 +19,10 @@
import { i18n } from '@kbn/i18n';
-import { MarkdownVisWrapper } from './markdown_vis_controller';
import { MarkdownOptions } from './markdown_options';
import { SettingsOptions } from './settings_options_lazy';
import { DefaultEditorSize } from '../../vis_default_editor/public';
+import { toExpressionAst } from './to_ast';
export const markdownVisDefinition = {
name: 'markdown',
@@ -32,8 +32,8 @@ export const markdownVisDefinition = {
description: i18n.translate('visTypeMarkdown.markdownDescription', {
defaultMessage: 'Create a document using markdown syntax',
}),
+ toExpressionAst,
visConfig: {
- component: MarkdownVisWrapper,
defaults: {
fontSize: 12,
openLinksInNewTab: false,
diff --git a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
index 103879cb6e6df..ff0cc89a5d9c9 100644
--- a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
+++ b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
@@ -25,13 +25,15 @@ describe('markdown vis controller', () => {
it('should set html from markdown params', () => {
const vis = {
params: {
+ openLinksInNewTab: false,
+ fontSize: 16,
markdown:
'This is a test of the [markdown](http://daringfireball.net/projects/markdown) vis.',
},
};
const wrapper = render(
-
+
);
expect(wrapper.find('a').text()).toBe('markdown');
});
@@ -39,12 +41,14 @@ describe('markdown vis controller', () => {
it('should not render the html', () => {
const vis = {
params: {
+ openLinksInNewTab: false,
+ fontSize: 16,
markdown: 'Testing html ',
},
};
const wrapper = render(
-
+
);
expect(wrapper.text()).toBe('Testing html \n');
});
@@ -52,12 +56,14 @@ describe('markdown vis controller', () => {
it('should update the HTML when render again with changed params', () => {
const vis = {
params: {
+ openLinksInNewTab: false,
+ fontSize: 16,
markdown: 'Initial',
},
};
const wrapper = mount(
-
+
);
expect(wrapper.text().trim()).toBe('Initial');
vis.params.markdown = 'Updated';
@@ -66,52 +72,68 @@ describe('markdown vis controller', () => {
});
describe('renderComplete', () => {
+ const vis = {
+ params: {
+ openLinksInNewTab: false,
+ fontSize: 16,
+ markdown: 'test',
+ },
+ };
+
+ const renderComplete = jest.fn();
+
+ beforeEach(() => {
+ renderComplete.mockClear();
+ });
+
it('should be called on initial rendering', () => {
- const vis = {
- params: {
- markdown: 'test',
- },
- };
- const renderComplete = jest.fn();
mount(
-
+
);
expect(renderComplete.mock.calls.length).toBe(1);
});
it('should be called on successive render when params change', () => {
- const vis = {
- params: {
- markdown: 'test',
- },
- };
- const renderComplete = jest.fn();
mount(
-
+
);
expect(renderComplete.mock.calls.length).toBe(1);
renderComplete.mockClear();
vis.params.markdown = 'changed';
mount(
-
+
);
expect(renderComplete.mock.calls.length).toBe(1);
});
it('should be called on successive render even without data change', () => {
- const vis = {
- params: {
- markdown: 'test',
- },
- };
- const renderComplete = jest.fn();
mount(
-
+
);
expect(renderComplete.mock.calls.length).toBe(1);
renderComplete.mockClear();
mount(
-
+
);
expect(renderComplete.mock.calls.length).toBe(1);
});
diff --git a/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx b/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx
index 4e77bb196b713..e1155ca42df72 100644
--- a/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx
+++ b/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx
@@ -22,7 +22,7 @@ import { Markdown } from '../../kibana_react/public';
import { MarkdownVisParams } from './types';
interface MarkdownVisComponentProps extends MarkdownVisParams {
- renderComplete: () => {};
+ renderComplete: () => void;
}
/**
@@ -80,7 +80,14 @@ class MarkdownVisComponent extends React.Component {
* The way React works, this wrapper nearly brings no overhead, but allows us
* to use proper lifecycle methods in the actual component.
*/
-export function MarkdownVisWrapper(props: any) {
+
+export interface MarkdownVisWrapperProps {
+ visParams: MarkdownVisParams;
+ fireEvent: (event: any) => void;
+ renderComplete: () => void;
+}
+
+export function MarkdownVisWrapper(props: MarkdownVisWrapperProps) {
return (
{
}
public setup(core: CoreSetup, { expressions, visualizations }: MarkdownPluginSetupDependencies) {
- visualizations.createReactVisualization(markdownVisDefinition);
+ const start = createStartServicesGetter(core.getStartServices);
+ visualizations.createBaseVisualization(markdownVisDefinition);
+ expressions.registerRenderer(getMarkdownRenderer(start));
expressions.registerFunction(createMarkdownVisFn);
}
diff --git a/src/plugins/vis_type_markdown/public/to_ast.test.ts b/src/plugins/vis_type_markdown/public/to_ast.test.ts
new file mode 100644
index 0000000000000..1ad1fa0ee2517
--- /dev/null
+++ b/src/plugins/vis_type_markdown/public/to_ast.test.ts
@@ -0,0 +1,54 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { toExpressionAst } from './to_ast';
+import { Vis } from '../../visualizations/public';
+
+describe('markdown vis toExpressionAst function', () => {
+ let vis: Vis;
+
+ beforeEach(() => {
+ vis = {
+ isHierarchical: () => false,
+ type: {},
+ params: {
+ percentageMode: false,
+ },
+ data: {
+ indexPattern: { id: '123' } as any,
+ aggs: {
+ getResponseAggs: () => [],
+ aggs: [],
+ } as any,
+ },
+ } as any;
+ });
+
+ it('without params', () => {
+ vis.params = {};
+ const actual = toExpressionAst(vis);
+ expect(actual).toMatchSnapshot();
+ });
+
+ it('with params', () => {
+ vis.params = { markdown: '### my markdown', fontSize: 15, openLinksInNewTab: true };
+ const actual = toExpressionAst(vis);
+ expect(actual).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts b/src/plugins/vis_type_markdown/public/to_ast.ts
similarity index 57%
rename from src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts
rename to src/plugins/vis_type_markdown/public/to_ast.ts
index c528c68f29edb..9b481218b42ea 100644
--- a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts
+++ b/src/plugins/vis_type_markdown/public/to_ast.ts
@@ -17,23 +17,23 @@
* under the License.
*/
-import { createGetterSetter, Get, Set } from '../../common';
-import { CoreStart } from '../../../../core/public';
-import { KUSavedObjectClient, createSavedObjectsClient } from './saved_objects_client';
+import { Vis } from '../../visualizations/public';
+import { buildExpression, buildExpressionFunction } from '../../expressions/public';
+import { MarkdownVisExpressionFunctionDefinition } from './markdown_fn';
-interface Return {
- getCoreStart: Get;
- setCoreStart: Set;
- savedObjects: KUSavedObjectClient;
-}
+export const toExpressionAst = (vis: Vis) => {
+ const { markdown, fontSize, openLinksInNewTab } = vis.params;
-export const createKibanaUtilsCore = (): Return => {
- const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart');
- const savedObjects = createSavedObjectsClient(getCoreStart);
+ const markdownVis = buildExpressionFunction(
+ 'markdownVis',
+ {
+ markdown,
+ font: buildExpression(`font size=${fontSize}`),
+ openLinksInNewTab,
+ }
+ );
- return {
- getCoreStart,
- setCoreStart,
- savedObjects,
- };
+ const ast = buildExpression([markdownVis]);
+
+ return ast.toAst();
};
diff --git a/src/plugins/vis_type_metric/kibana.json b/src/plugins/vis_type_metric/kibana.json
index b2ebc91471e9d..26ca09e22f26e 100644
--- a/src/plugins/vis_type_metric/kibana.json
+++ b/src/plugins/vis_type_metric/kibana.json
@@ -4,6 +4,6 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
- "requiredPlugins": ["data", "visualizations", "charts","expressions"],
+ "requiredPlugins": ["data", "visualizations", "charts", "expressions"],
"requiredBundles": ["kibanaUtils", "kibanaReact"]
}
diff --git a/src/plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap b/src/plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap
index 44414cedf966a..706d2a902aa90 100644
--- a/src/plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap
+++ b/src/plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap
@@ -2,7 +2,7 @@
exports[`interpreter/functions#metric returns an object with the correct structure 1`] = `
Object {
- "as": "visualization",
+ "as": "metric_vis",
"type": "render",
"value": Object {
"params": Object {
diff --git a/src/plugins/vis_type_metric/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_metric/public/__snapshots__/to_ast.test.ts.snap
new file mode 100644
index 0000000000000..117c188c6cce2
--- /dev/null
+++ b/src/plugins/vis_type_metric/public/__snapshots__/to_ast.test.ts.snap
@@ -0,0 +1,73 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`metric vis toExpressionAst function with percentage mode should have percentage format 1`] = `
+Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "aggConfigs": Array [
+ "[]",
+ ],
+ "includeFormatHints": Array [
+ false,
+ ],
+ "index": Array [
+ "123",
+ ],
+ "metricsAtAllLevels": Array [
+ false,
+ ],
+ "partialRows": Array [
+ false,
+ ],
+ },
+ "function": "esaggs",
+ "type": "function",
+ },
+ Object {
+ "arguments": Object {
+ "percentageMode": Array [
+ true,
+ ],
+ },
+ "function": "metricVis",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+}
+`;
+
+exports[`metric vis toExpressionAst function without params 1`] = `
+Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "aggConfigs": Array [
+ "[]",
+ ],
+ "includeFormatHints": Array [
+ false,
+ ],
+ "index": Array [
+ "123",
+ ],
+ "metricsAtAllLevels": Array [
+ false,
+ ],
+ "partialRows": Array [
+ false,
+ ],
+ },
+ "function": "esaggs",
+ "type": "function",
+ },
+ Object {
+ "arguments": Object {},
+ "function": "metricVis",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+}
+`;
diff --git a/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap b/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap
index 47ca3f1e0465e..f07fdfa682d87 100644
--- a/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap
+++ b/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap
@@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MetricVisComponent should render correct structure for multi-value metrics 1`] = `
-
+Array [
+ />,
-
+ />,
+]
`;
exports[`MetricVisComponent should render correct structure for single metric 1`] = `
-
-
-
+ }
+ showLabel={true}
+/>
`;
diff --git a/src/plugins/vis_type_metric/public/components/metric_vis_component.test.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_component.test.tsx
index ba32fb2712e68..b56d4e4f62e41 100644
--- a/src/plugins/vis_type_metric/public/components/metric_vis_component.test.tsx
+++ b/src/plugins/vis_type_metric/public/components/metric_vis_component.test.tsx
@@ -21,7 +21,6 @@ import React from 'react';
import { shallow } from 'enzyme';
import { MetricVisComponent, MetricVisComponentProps } from './metric_vis_component';
-import { ExprVis } from '../../../visualizations/public';
jest.mock('../services', () => ({
getFormatService: () => ({
@@ -41,29 +40,30 @@ const baseVisData = {
} as any;
describe('MetricVisComponent', function () {
- const vis: ExprVis = {
- params: {
- metric: {
- colorSchema: 'Green to Red',
- colorsRange: [{ from: 0, to: 1000 }],
- style: {},
- labels: {
- show: true,
- },
- },
- dimensions: {
- metrics: [{ accessor: 0 }],
- bucket: null,
+ const visParams = {
+ type: 'metric',
+ addTooltip: false,
+ addLegend: false,
+ metric: {
+ colorSchema: 'Green to Red',
+ colorsRange: [{ from: 0, to: 1000 }],
+ style: {},
+ labels: {
+ show: true,
},
},
- } as any;
+ dimensions: {
+ metrics: [{ accessor: 0 } as any],
+ bucket: undefined,
+ },
+ };
const getComponent = (propOverrides: Partial = {} as Partial) => {
const props: Props = {
- vis,
- visParams: vis.params as any,
+ visParams: visParams as any,
visData: baseVisData,
renderComplete: jest.fn(),
+ fireEvent: jest.fn(),
...propOverrides,
};
@@ -88,9 +88,9 @@ describe('MetricVisComponent', function () {
rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }],
},
visParams: {
- ...vis.params,
+ ...visParams,
dimensions: {
- ...vis.params.dimensions,
+ ...visParams.dimensions,
metrics: [{ accessor: 0 }, { accessor: 1 }],
},
},
diff --git a/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx
index 4385826762612..9ce3820ee4e23 100644
--- a/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx
+++ b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx
@@ -27,13 +27,13 @@ import { KibanaDatatable } from '../../../expressions/public';
import { getHeatmapColors } from '../../../charts/public';
import { VisParams, MetricVisMetric } from '../types';
import { getFormatService } from '../services';
-import { SchemaConfig, ExprVis } from '../../../visualizations/public';
+import { SchemaConfig } from '../../../visualizations/public';
import { Range } from '../../../expressions/public';
export interface MetricVisComponentProps {
visParams: VisParams;
visData: Input;
- vis: ExprVis;
+ fireEvent: (event: any) => void;
renderComplete: () => void;
}
@@ -166,10 +166,17 @@ export class MetricVisComponent extends Component {
return;
}
const table = this.props.visData;
- this.props.vis.API.events.filter({
- table,
- column: dimensions.bucket.accessor,
- row: metric.rowIndex,
+ this.props.fireEvent({
+ name: 'filterBucket',
+ data: {
+ data: [
+ {
+ table,
+ column: dimensions.bucket.accessor,
+ row: metric.rowIndex,
+ },
+ ],
+ },
});
};
@@ -199,6 +206,6 @@ export class MetricVisComponent extends Component {
const metrics = this.processTableGroups(this.props.visData);
metricsHtml = metrics.map(this.renderMetric);
}
- return {metricsHtml}
;
+ return metricsHtml;
}
}
diff --git a/src/plugins/vis_type_metric/public/metric_vis_fn.ts b/src/plugins/vis_type_metric/public/metric_vis_fn.ts
index 3d16fed0fa385..b58be63581724 100644
--- a/src/plugins/vis_type_metric/public/metric_vis_fn.ts
+++ b/src/plugins/vis_type_metric/public/metric_vis_fn.ts
@@ -53,12 +53,14 @@ interface RenderValue {
params: any;
}
-export const createMetricVisFn = (): ExpressionFunctionDefinition<
+export type MetricVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
'metricVis',
Input,
Arguments,
Render
-> => ({
+>;
+
+export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({
name: 'metricVis',
type: 'render',
inputTypes: ['kibana_datatable'],
@@ -175,7 +177,7 @@ export const createMetricVisFn = (): ExpressionFunctionDefinition<
return {
type: 'render',
- as: 'visualization',
+ as: 'metric_vis',
value: {
visData: input,
visType,
diff --git a/src/plugins/vis_type_metric/public/metric_vis_renderer.tsx b/src/plugins/vis_type_metric/public/metric_vis_renderer.tsx
new file mode 100644
index 0000000000000..2bae668b080ea
--- /dev/null
+++ b/src/plugins/vis_type_metric/public/metric_vis_renderer.tsx
@@ -0,0 +1,54 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { MetricVisComponent } from './components/metric_vis_component';
+import { getI18n } from './services';
+import { VisualizationContainer } from '../../visualizations/public';
+import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers';
+
+export const metricVisRenderer: () => ExpressionRenderDefinition = () => ({
+ name: 'metric_vis',
+ displayName: 'metric visualization',
+ reuseDomNode: true,
+ render: async (domNode: HTMLElement, config: any, handlers: any) => {
+ const { visData, visConfig } = config;
+
+ const I18nContext = getI18n().Context;
+
+ handlers.onDestroy(() => {
+ unmountComponentAtNode(domNode);
+ });
+
+ render(
+
+
+
+
+ ,
+ domNode
+ );
+ },
+});
diff --git a/src/plugins/vis_type_metric/public/metric_vis_type.ts b/src/plugins/vis_type_metric/public/metric_vis_type.ts
index b7e9213283bee..6b4d6e151693f 100644
--- a/src/plugins/vis_type_metric/public/metric_vis_type.ts
+++ b/src/plugins/vis_type_metric/public/metric_vis_type.ts
@@ -18,12 +18,11 @@
*/
import { i18n } from '@kbn/i18n';
-
-import { MetricVisComponent } from './components/metric_vis_component';
import { MetricVisOptions } from './components/metric_vis_options';
import { ColorSchemas, colorSchemas, ColorModes } from '../../charts/public';
import { AggGroupNames } from '../../data/public';
import { Schemas } from '../../vis_default_editor/public';
+import { toExpressionAst } from './to_ast';
export const createMetricVisTypeDefinition = () => ({
name: 'metric',
@@ -32,8 +31,8 @@ export const createMetricVisTypeDefinition = () => ({
description: i18n.translate('visTypeMetric.metricDescription', {
defaultMessage: 'Display a calculation as a single number',
}),
+ toExpressionAst,
visConfig: {
- component: MetricVisComponent,
defaults: {
addTooltip: true,
addLegend: false,
diff --git a/src/plugins/vis_type_metric/public/plugin.ts b/src/plugins/vis_type_metric/public/plugin.ts
index a3951fa46c21c..b9e094aa76889 100644
--- a/src/plugins/vis_type_metric/public/plugin.ts
+++ b/src/plugins/vis_type_metric/public/plugin.ts
@@ -25,8 +25,9 @@ import { createMetricVisFn } from './metric_vis_fn';
import { createMetricVisTypeDefinition } from './metric_vis_type';
import { ChartsPluginSetup } from '../../charts/public';
import { DataPublicPluginStart } from '../../data/public';
-import { setFormatService } from './services';
+import { setFormatService, setI18n } from './services';
import { ConfigSchema } from '../config';
+import { metricVisRenderer } from './metric_vis_renderer';
/** @internal */
export interface MetricVisPluginSetupDependencies {
@@ -53,10 +54,12 @@ export class MetricVisPlugin implements Plugin {
{ expressions, visualizations, charts }: MetricVisPluginSetupDependencies
) {
expressions.registerFunction(createMetricVisFn);
- visualizations.createReactVisualization(createMetricVisTypeDefinition());
+ expressions.registerRenderer(metricVisRenderer);
+ visualizations.createBaseVisualization(createMetricVisTypeDefinition());
}
public start(core: CoreStart, { data }: MetricVisPluginStartDependencies) {
+ setI18n(core.i18n);
setFormatService(data.fieldFormats);
}
}
diff --git a/src/plugins/vis_type_metric/public/services.ts b/src/plugins/vis_type_metric/public/services.ts
index 681afbaf0b268..0e19cfdce228d 100644
--- a/src/plugins/vis_type_metric/public/services.ts
+++ b/src/plugins/vis_type_metric/public/services.ts
@@ -17,9 +17,12 @@
* under the License.
*/
+import { I18nStart } from 'kibana/public';
import { createGetterSetter } from '../../kibana_utils/common';
import { DataPublicPluginStart } from '../../data/public';
export const [getFormatService, setFormatService] = createGetterSetter<
DataPublicPluginStart['fieldFormats']
>('metric data.fieldFormats');
+
+export const [getI18n, setI18n] = createGetterSetter('I18n');
diff --git a/src/plugins/vis_type_metric/public/to_ast.test.ts b/src/plugins/vis_type_metric/public/to_ast.test.ts
new file mode 100644
index 0000000000000..e741e64c0422b
--- /dev/null
+++ b/src/plugins/vis_type_metric/public/to_ast.test.ts
@@ -0,0 +1,54 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { toExpressionAst } from './to_ast';
+import { Vis } from '../../visualizations/public';
+
+describe('metric vis toExpressionAst function', () => {
+ let vis: Vis;
+
+ beforeEach(() => {
+ vis = {
+ isHierarchical: () => false,
+ type: {},
+ params: {
+ percentageMode: false,
+ },
+ data: {
+ indexPattern: { id: '123' } as any,
+ aggs: {
+ getResponseAggs: () => [],
+ aggs: [],
+ } as any,
+ },
+ } as any;
+ });
+
+ it('without params', () => {
+ vis.params = { metric: {} };
+ const actual = toExpressionAst(vis, {});
+ expect(actual).toMatchSnapshot();
+ });
+
+ it('with percentage mode should have percentage format', () => {
+ vis.params = { metric: { percentageMode: true } };
+ const actual = toExpressionAst(vis, {});
+ expect(actual).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/vis_type_metric/public/to_ast.ts b/src/plugins/vis_type_metric/public/to_ast.ts
new file mode 100644
index 0000000000000..7eefd8328ab76
--- /dev/null
+++ b/src/plugins/vis_type_metric/public/to_ast.ts
@@ -0,0 +1,103 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { get } from 'lodash';
+import { getVisSchemas, SchemaConfig, Vis } from '../../visualizations/public';
+import { buildExpression, buildExpressionFunction } from '../../expressions/public';
+import { MetricVisExpressionFunctionDefinition } from './metric_vis_fn';
+import { EsaggsExpressionFunctionDefinition } from '../../data/common/search/expressions';
+
+const prepareDimension = (params: SchemaConfig) => {
+ const visdimension = buildExpressionFunction('visdimension', { accessor: params.accessor });
+
+ if (params.format) {
+ visdimension.addArgument('format', params.format.id);
+ visdimension.addArgument('formatParams', JSON.stringify(params.format.params));
+ }
+
+ return buildExpression([visdimension]);
+};
+
+export const toExpressionAst = (vis: Vis, params: any) => {
+ // soon this becomes: const esaggs = vis.data.aggs!.toExpressionAst();
+ const esaggs = buildExpressionFunction('esaggs', {
+ index: vis.data.indexPattern!.id!,
+ metricsAtAllLevels: vis.isHierarchical(),
+ partialRows: vis.type.requiresPartialRows || vis.params.showPartialRows || false,
+ aggConfigs: JSON.stringify(vis.data.aggs!.aggs),
+ includeFormatHints: false,
+ });
+
+ const schemas = getVisSchemas(vis, params);
+
+ const {
+ percentageMode,
+ useRanges,
+ colorSchema,
+ metricColorMode,
+ colorsRange,
+ labels,
+ invertColors,
+ style,
+ } = vis.params.metric;
+
+ // fix formatter for percentage mode
+ if (get(vis.params, 'metric.percentageMode') === true) {
+ schemas.metric.forEach((metric: SchemaConfig) => {
+ metric.format = { id: 'percent' };
+ });
+ }
+
+ // @ts-expect-error
+ const metricVis = buildExpressionFunction('metricVis', {
+ percentageMode,
+ colorSchema,
+ colorMode: metricColorMode,
+ useRanges,
+ invertColors,
+ showLabels: labels && labels.show,
+ });
+
+ if (style) {
+ metricVis.addArgument('bgFill', style.bgFill);
+ metricVis.addArgument('font', buildExpression(`font size=${style.fontSize}`));
+ metricVis.addArgument('subText', style.subText);
+ }
+
+ if (colorsRange) {
+ colorsRange.forEach((range: any) => {
+ metricVis.addArgument(
+ 'colorRange',
+ buildExpression(`range from=${range.from} to=${range.to}`)
+ );
+ });
+ }
+
+ if (schemas.group) {
+ metricVis.addArgument('bucket', prepareDimension(schemas.group[0]));
+ }
+
+ schemas.metric.forEach((metric) => {
+ metricVis.addArgument('metric', prepareDimension(metric));
+ });
+
+ const ast = buildExpression([esaggs, metricVis]);
+
+ return ast.toAst();
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
index 612a7a48bade1..c14148d4a020f 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
@@ -31,7 +31,6 @@ import { MarkdownSimple } from '../../../../../../../plugins/kibana_react/public
import { replaceVars } from '../../lib/replace_vars';
import { getAxisLabelString } from '../../lib/get_axis_label_string';
import { getInterval } from '../../lib/get_interval';
-import { areFieldsDifferent } from '../../lib/charts';
import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';
import { STACKED_OPTIONS } from '../../../visualizations/constants';
import { getCoreStart } from '../../../../services';
@@ -164,7 +163,6 @@ export class TimeseriesVisualization extends Component {
const mainAxisGroupId = yAxisIdGenerator('main_group');
const seriesModel = model.series.filter((s) => !s.hidden).map((s) => cloneDeep(s));
- const enableHistogramMode = areFieldsDifferent('chart_type')(seriesModel);
const firstSeries = seriesModel.find((s) => s.formatter && !s.separate_axis);
const mainAxisScaleType = TimeseriesVisualization.getAxisScaleType(model);
@@ -243,7 +241,6 @@ export class TimeseriesVisualization extends Component {
series={series}
yAxis={yAxis}
onBrush={onBrush}
- enableHistogramMode={enableHistogramMode}
backgroundColor={model.background_color}
showGrid={Boolean(model.show_grid)}
legend={Boolean(model.show_legend)}
diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js
index 64ac5cc5f871f..6b5d84dc56981 100644
--- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js
+++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js
@@ -67,7 +67,6 @@ export const TimeSeries = ({
onBrush,
xAxisFormatter,
annotations,
- enableHistogramMode,
}) => {
const chartRef = useRef();
const updateCursor = (_, cursor) => {
@@ -181,6 +180,7 @@ export const TimeSeries = ({
) => {
const stackAccessors = getStackAccessors(stack);
const isPercentage = stack === STACKED_OPTIONS.PERCENT;
+ const isStacked = stack !== STACKED_OPTIONS.NONE;
const key = `${id}-${label}`;
// Only use color mapping if there is no color from the server
const finalColor = color ?? colors.mappedColors.mapping[label];
@@ -201,7 +201,7 @@ export const TimeSeries = ({
xScaleType={xScaleType}
yScaleType={yScaleType}
timeZone={timeZone}
- enableHistogramMode={enableHistogramMode}
+ enableHistogramMode={isStacked}
useDefaultGroupDomain={useDefaultGroupDomain}
sortIndex={sortIndex}
y1AccessorFormat={y1AccessorFormat}
@@ -227,7 +227,7 @@ export const TimeSeries = ({
xScaleType={xScaleType}
yScaleType={yScaleType}
timeZone={timeZone}
- enableHistogramMode={enableHistogramMode}
+ enableHistogramMode={isStacked}
useDefaultGroupDomain={useDefaultGroupDomain}
sortIndex={sortIndex}
y1AccessorFormat={y1AccessorFormat}
@@ -283,5 +283,4 @@ TimeSeries.propTypes = {
onBrush: PropTypes.func,
xAxisFormatter: PropTypes.func,
annotations: PropTypes.array,
- enableHistogramMode: PropTypes.bool.isRequired,
};
diff --git a/src/plugins/vis_type_vega/public/data_model/search_api.ts b/src/plugins/vis_type_vega/public/data_model/search_api.ts
index a213b59be2ad0..d2ce8c95b9f90 100644
--- a/src/plugins/vis_type_vega/public/data_model/search_api.ts
+++ b/src/plugins/vis_type_vega/public/data_model/search_api.ts
@@ -51,8 +51,10 @@ export class SearchAPI {
searchRequests.map((request) => {
const requestId = request.name;
const params = getSearchParamsFromRequest(request, {
- uiSettings: this.dependencies.uiSettings,
- injectedMetadata: this.dependencies.injectedMetadata,
+ esShardTimeout: this.dependencies.injectedMetadata.getInjectedVar(
+ 'esShardTimeout'
+ ) as number,
+ getConfig: this.dependencies.uiSettings.get.bind(this.dependencies.uiSettings),
});
if (this.inspectorAdapters) {
diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts
index f49816017b684..46fd2fbc5587e 100644
--- a/src/plugins/vis_type_vega/public/vega_type.ts
+++ b/src/plugins/vis_type_vega/public/vega_type.ts
@@ -58,7 +58,6 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen
getSupportedTriggers: () => {
return [VIS_EVENT_TO_TRIGGER.applyFilter];
},
- stage: 'experimental',
inspectorAdapters: createInspectorAdapters,
};
};
diff --git a/src/plugins/visualizations/public/components/index.ts b/src/plugins/visualizations/public/components/index.ts
index 73685de81d68e..195a49f9e0d54 100644
--- a/src/plugins/visualizations/public/components/index.ts
+++ b/src/plugins/visualizations/public/components/index.ts
@@ -18,3 +18,5 @@
*/
export { Visualization } from './visualization';
+export { VisualizationContainer } from './visualization_container';
+export { VisualizationNoResults } from './visualization_noresults';
diff --git a/src/plugins/visualize/config.ts b/src/plugins/visualizations/public/components/visualization_container.tsx
similarity index 71%
rename from src/plugins/visualize/config.ts
rename to src/plugins/visualizations/public/components/visualization_container.tsx
index ee79a37717f26..d6f87d4cea123 100644
--- a/src/plugins/visualize/config.ts
+++ b/src/plugins/visualizations/public/components/visualization_container.tsx
@@ -17,10 +17,14 @@
* under the License.
*/
-import { schema, TypeOf } from '@kbn/config-schema';
+import React, { ReactNode } from 'react';
-export const configSchema = schema.object({
- showNewVisualizeFlow: schema.boolean({ defaultValue: false }),
-});
+interface VisualizationContainerProps {
+ className?: string;
+ children: ReactNode;
+}
-export type ConfigSchema = TypeOf;
+export const VisualizationContainer = (props: VisualizationContainerProps) => {
+ const classes = `visualization ${props.className}`;
+ return {props.children}
;
+};
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
index c4d5f5206ee90..4efdfd2911cbc 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
@@ -36,7 +36,6 @@ import {
IContainer,
Adapters,
} from '../../../../plugins/embeddable/public';
-import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public';
import {
IExpressionLoaderParams,
ExpressionsStart,
@@ -61,9 +60,6 @@ export interface VisualizeEmbeddableConfiguration {
}
export interface VisualizeInput extends EmbeddableInput {
- timeRange?: TimeRange;
- query?: Query;
- filters?: Filter[];
vis?: {
colors?: { [key: string]: string };
};
@@ -85,7 +81,6 @@ export class VisualizeEmbeddable extends Embeddable;
private subscriptions: Subscription[] = [];
@@ -158,7 +153,7 @@ export class VisualizeEmbeddable extends Embeddable Boolean(this.getInspectorAdapters());
onContainerLoading = () => {
- this.domNode.setAttribute('data-render-complete', 'false');
+ this.renderComplete.dispatchInProgress();
this.updateOutput({ loading: true, error: undefined });
};
- onContainerRender = (count: number) => {
- this.domNode.setAttribute('data-render-complete', 'true');
- this.domNode.setAttribute('data-rendering-count', count.toString());
+ onContainerRender = () => {
+ this.renderComplete.dispatchComplete();
this.updateOutput({ loading: false, error: undefined });
- dispatchRenderComplete(this.domNode);
};
onContainerError = (error: ExpressionRenderError) => {
if (this.abortController) {
this.abortController.abort();
}
- this.domNode.setAttribute(
- 'data-rendering-count',
- this.domNode.getAttribute('data-rendering-count') + 1
- );
- this.domNode.setAttribute('data-render-complete', 'false');
+ this.renderComplete.dispatchError();
this.updateOutput({ loading: false, error });
};
@@ -274,7 +253,6 @@ export class VisualizeEmbeddable extends Embeddable {
describe('prepareJson', () => {
@@ -122,23 +123,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
expect(actual).toMatchSnapshot();
});
- it('handles markdown function', () => {
- const params = {
- markdown: '## hello _markdown_',
- fontSize: 12,
- openLinksInNewTab: true,
- foo: 'bar',
- };
- const actual = buildPipelineVisFunction.markdown(params, schemasDef, uiState);
- expect(actual).toMatchSnapshot();
- });
-
- it('handles undefined markdown function', () => {
- const params = { fontSize: 12, openLinksInNewTab: true, foo: 'bar' };
- const actual = buildPipelineVisFunction.markdown(params, schemasDef, uiState);
- expect(actual).toMatchSnapshot();
- });
-
describe('handles table function', () => {
it('without splits or buckets', () => {
const params = { foo: 'bar' };
@@ -217,42 +201,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
});
});
- describe('handles metric function', () => {
- it('without buckets', () => {
- const params = { metric: {} };
- const schemas = {
- ...schemasDef,
- metric: [
- { ...schemaConfig, accessor: 0 },
- { ...schemaConfig, accessor: 1 },
- ],
- };
- const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
- expect(actual).toMatchSnapshot();
- });
-
- it('with buckets', () => {
- const params = { metric: {} };
- const schemas = {
- ...schemasDef,
- metric: [
- { ...schemaConfig, accessor: 0 },
- { ...schemaConfig, accessor: 1 },
- ],
- group: [{ accessor: 2 }],
- };
- const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
- expect(actual).toMatchSnapshot();
- });
-
- it('with percentage mode should have percentage format', () => {
- const params = { metric: { percentageMode: true } };
- const schemas = { ...schemasDef };
- const actual = buildPipelineVisFunction.metric(params, schemas, uiState);
- expect(actual).toMatchSnapshot();
- });
- });
-
describe('handles tagcloud function', () => {
it('without buckets', () => {
const actual = buildPipelineVisFunction.tagcloud({}, schemasDef, uiState);
@@ -331,7 +279,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
},
// @ts-ignore
type: {
- toExpression: () => 'testing custom expressions',
+ toExpressionAst: () => parseExpression('test'),
},
} as unknown) as Vis;
const expression = await buildPipeline(vis, {
diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts
index d52e2fcc13bff..438a6d2337724 100644
--- a/src/plugins/visualizations/public/legacy/build_pipeline.ts
+++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts
@@ -19,7 +19,7 @@
import { get } from 'lodash';
import moment from 'moment';
-import { SerializedFieldFormat } from '../../../../plugins/expressions/public';
+import { formatExpression, SerializedFieldFormat } from '../../../../plugins/expressions/public';
import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public';
import { Vis, VisParams } from '../types';
const { isDateHistogramBucketAggConfig } = search.aggs;
@@ -80,7 +80,7 @@ const vislibCharts: string[] = [
'line',
];
-const getSchemas = (
+export const getSchemas = (
vis: Vis,
opts: {
timeRange?: any;
@@ -269,17 +269,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
const interval = prepareString('interval', params.interval);
return `timelion_vis ${expression}${interval}`;
},
- markdown: (params) => {
- const { markdown, fontSize, openLinksInNewTab } = params;
- let escapedMarkdown = '';
- if (typeof markdown === 'string' || markdown instanceof String) {
- escapedMarkdown = escapeString(markdown.toString());
- }
- let expr = `markdownvis '${escapedMarkdown}' `;
- expr += prepareValue('font', `{font size=${fontSize}}`, true);
- expr += prepareValue('openLinksInNewTab', openLinksInNewTab);
- return expr;
- },
table: (params, schemas) => {
const visConfig = {
...params,
@@ -287,52 +276,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
};
return `kibana_table ${prepareJson('visConfig', visConfig)}`;
},
- metric: (params, schemas) => {
- const {
- percentageMode,
- useRanges,
- colorSchema,
- metricColorMode,
- colorsRange,
- labels,
- invertColors,
- style,
- } = params.metric;
- const { metrics, bucket } = buildVisConfig.metric(schemas).dimensions;
-
- // fix formatter for percentage mode
- if (get(params, 'metric.percentageMode') === true) {
- metrics.forEach((metric: SchemaConfig) => {
- metric.format = { id: 'percent' };
- });
- }
-
- let expr = `metricvis `;
- expr += prepareValue('percentageMode', percentageMode);
- expr += prepareValue('colorSchema', colorSchema);
- expr += prepareValue('colorMode', metricColorMode);
- expr += prepareValue('useRanges', useRanges);
- expr += prepareValue('invertColors', invertColors);
- expr += prepareValue('showLabels', labels && labels.show);
- if (style) {
- expr += prepareValue('bgFill', style.bgFill);
- expr += prepareValue('font', `{font size=${style.fontSize}}`, true);
- expr += prepareValue('subText', style.subText);
- expr += prepareDimension('bucket', bucket);
- }
-
- if (colorsRange) {
- colorsRange.forEach((range: any) => {
- expr += prepareValue('colorRange', `{range from=${range.from} to=${range.to}}`, true);
- });
- }
-
- metrics.forEach((metric: SchemaConfig) => {
- expr += prepareDimension('metric', metric);
- });
-
- return expr;
- },
tagcloud: (params, schemas) => {
const { scale, orientation, minFontSize, maxFontSize, showLabel } = params;
const { metric, bucket } = buildVisConfig.tagcloud(schemas);
@@ -390,14 +333,6 @@ const buildVisConfig: BuildVisConfigFunction = {
}
return visConfig;
},
- metric: (schemas) => {
- const visConfig = { dimensions: {} } as any;
- visConfig.dimensions.metrics = schemas.metric;
- if (schemas.group) {
- visConfig.dimensions.bucket = schemas.group[0];
- }
- return visConfig;
- },
tagcloud: (schemas) => {
const visConfig = {} as any;
visConfig.metric = schemas.metric[0];
@@ -507,39 +442,46 @@ export const buildPipeline = async (
}
pipeline += '| ';
- // request handler
- if (vis.type.requestHandler === 'courier') {
- pipeline += `esaggs
+ if (vis.type.toExpressionAst) {
+ const visAst = await vis.type.toExpressionAst(vis, params);
+ pipeline += formatExpression(visAst);
+ } else {
+ // request handler
+ if (vis.type.requestHandler === 'courier') {
+ pipeline += `esaggs
${prepareString('index', indexPattern!.id)}
metricsAtAllLevels=${vis.isHierarchical()}
partialRows=${vis.type.requiresPartialRows || vis.params.showPartialRows || false}
${prepareJson('aggConfigs', vis.data.aggs!.aggs)} | `;
- }
+ }
- const schemas = getSchemas(vis, {
- timeRange: params.timeRange,
- timefilter: params.timefilter,
- });
- if (buildPipelineVisFunction[vis.type.name]) {
- pipeline += buildPipelineVisFunction[vis.type.name]({ title, ...vis.params }, schemas, uiState);
- } else if (vislibCharts.includes(vis.type.name)) {
- const visConfig = { ...vis.params };
- visConfig.dimensions = await buildVislibDimensions(vis, params);
-
- pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visConfig)}`;
- } else if (vis.type.toExpression) {
- pipeline += await vis.type.toExpression(vis, params);
- } else {
- const visConfig = { ...vis.params };
- visConfig.dimensions = schemas;
- pipeline += `visualization type='${vis.type.name}'
+ const schemas = getSchemas(vis, {
+ timeRange: params.timeRange,
+ timefilter: params.timefilter,
+ });
+ if (buildPipelineVisFunction[vis.type.name]) {
+ pipeline += buildPipelineVisFunction[vis.type.name](
+ { title, ...vis.params },
+ schemas,
+ uiState
+ );
+ } else if (vislibCharts.includes(vis.type.name)) {
+ const visConfig = { ...vis.params };
+ visConfig.dimensions = await buildVislibDimensions(vis, params);
+
+ pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visConfig)}`;
+ } else {
+ const visConfig = { ...vis.params };
+ visConfig.dimensions = schemas;
+ pipeline += `visualization type='${vis.type.name}'
${prepareJson('visConfig', visConfig)}
metricsAtAllLevels=${vis.isHierarchical()}
partialRows=${vis.type.requiresPartialRows || vis.params.showPartialRows || false} `;
- if (indexPattern) {
- pipeline += `${prepareString('index', indexPattern.id)} `;
- if (vis.data.aggs) {
- pipeline += `${prepareJson('aggConfigs', vis.data.aggs!.aggs)}`;
+ if (indexPattern) {
+ pipeline += `${prepareString('index', indexPattern.id)} `;
+ if (vis.data.aggs) {
+ pipeline += `${prepareJson('aggConfigs', vis.data.aggs!.aggs)}`;
+ }
}
}
}
diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts
index daf275297fb82..f47ffbbe921a2 100644
--- a/src/plugins/visualizations/public/types.ts
+++ b/src/plugins/visualizations/public/types.ts
@@ -18,7 +18,11 @@
*/
import { SavedObject } from '../../../plugins/saved_objects/public';
-import { AggConfigOptions, SearchSourceFields } from '../../../plugins/data/public';
+import {
+ AggConfigOptions,
+ SearchSourceFields,
+ TimefilterContract,
+} from '../../../plugins/data/public';
import { SerializedVis, Vis, VisParams } from './vis';
export { Vis, SerializedVis, VisParams };
@@ -60,3 +64,11 @@ export interface VisResponseValue {
visConfig: object;
params?: object;
}
+
+export interface VisToExpressionAstParams {
+ timefilter: TimefilterContract;
+ timeRange?: any;
+ abortSignal?: AbortSignal;
+}
+
+export type VisToExpressionAst = (vis: Vis, params: VisToExpressionAstParams) => string;
diff --git a/src/plugins/visualizations/public/vis.ts b/src/plugins/visualizations/public/vis.ts
index e8ae48cdce145..b4fc9df1c6ecc 100644
--- a/src/plugins/visualizations/public/vis.ts
+++ b/src/plugins/visualizations/public/vis.ts
@@ -202,8 +202,8 @@ export class Vis {
};
}
- toAST() {
- return this.type.toAST(this.params);
+ toExpressionAst() {
+ return this.type.toExpressionAst(this.params);
}
// deprecated
diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts
index 1a3a426398e36..fa0bbfc5e250a 100644
--- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts
+++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts
@@ -18,7 +18,7 @@
*/
import _ from 'lodash';
-import { VisualizationControllerConstructor } from '../types';
+import { VisToExpressionAst, VisualizationControllerConstructor } from '../types';
import { TriggerContextMapping } from '../../../ui_actions/public';
import { Adapters } from '../../../inspector/public';
@@ -31,7 +31,7 @@ export interface BaseVisTypeOptions {
image?: string;
stage?: 'experimental' | 'beta' | 'production';
options?: Record;
- visualization: VisualizationControllerConstructor;
+ visualization: VisualizationControllerConstructor | undefined;
visConfig?: Record;
editor?: any;
editorConfig?: Record;
@@ -42,6 +42,7 @@ export interface BaseVisTypeOptions {
setup?: unknown;
useCustomNoDataScreen?: boolean;
inspectorAdapters?: Adapters | (() => Adapters);
+ toExpressionAst?: VisToExpressionAst;
}
export class BaseVisType {
@@ -54,7 +55,7 @@ export class BaseVisType {
stage: 'experimental' | 'beta' | 'production';
isExperimental: boolean;
options: Record;
- visualization: VisualizationControllerConstructor;
+ visualization: VisualizationControllerConstructor | undefined;
visConfig: Record;
editor: any;
editorConfig: Record;
@@ -66,6 +67,7 @@ export class BaseVisType {
setup?: unknown;
useCustomNoDataScreen: boolean;
inspectorAdapters?: Adapters | (() => Adapters);
+ toExpressionAst?: VisToExpressionAst;
constructor(opts: BaseVisTypeOptions) {
if (!opts.icon && !opts.image) {
@@ -102,6 +104,7 @@ export class BaseVisType {
this.hierarchicalData = opts.hierarchicalData || false;
this.useCustomNoDataScreen = opts.useCustomNoDataScreen || false;
this.inspectorAdapters = opts.inspectorAdapters;
+ this.toExpressionAst = opts.toExpressionAst;
}
public get schemas() {
diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap
index a27dfa13e743e..6aed16e937713 100644
--- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap
+++ b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap
@@ -142,21 +142,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiOverlayMask euiOverlayMask--aboveHeader"
>
-
-
-
}
>
@@ -419,3912 +398,1491 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
role="menu"
>
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
+
-
+ New Visualization
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
-
Select a visualization type
-
-
+
+
+
+
+
+
+
+
- Start creating your visualization by selecting a type for that visualization.
+
+ Start creating your visualization by selecting a type for that visualization.
+
-
-
+
+
-
-
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- returnFocus={[Function]}
- shards={Array []}
- sideCar={
- Object {
- "assignMedium": [Function],
- "assignSyncMedium": [Function],
- "options": Object {
- "async": true,
- "ssr": false,
- },
- "read": [Function],
- "useMedium": [Function],
- }
- }
- >
-
-
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- returnFocus={[Function]}
- shards={Array []}
- sideCar={
- Object {
- "assignMedium": [Function],
- "assignSyncMedium": [Function],
- "options": Object {
- "async": true,
- "ssr": false,
- },
- "read": [Function],
- "useMedium": [Function],
- }
- }
- >
-
-
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- returnFocus={[Function]}
- shards={Array []}
- >
-
-
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- returnFocus={[Function]}
- shards={Array []}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Visualization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2 types found
-
-
-
-
-
-
-
- Vis with alias Url
-
- }
- onBlur={[Function]}
- onClick={[Function]}
- onFocus={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
- role="menuitem"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Vis with alias Url
-
-
-
-
-
-
-
-
- Vis with search
-
- }
- onBlur={[Function]}
- onClick={[Function]}
- onFocus={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
- role="menuitem"
- >
-
-
-
-
-
- Vis with search
-
-
-
-
-
-
-
-
- Vis Type 1
-
- }
- onBlur={[Function]}
- onClick={[Function]}
- onFocus={[Function]}
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
- role="menuitem"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Select a visualization type
-
-
-
-
-
-
-
-
-
-
-
- Start creating your visualization by selecting a type for that visualization.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`NewVisModal should render as expected 1`] = `
-
-
-
+
+
+`;
+
+exports[`NewVisModal should render as expected 1`] = `
+
+
+
diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json
index a6cc8d8f8af60..29fcd30184cb2 100644
--- a/src/plugins/visualize/kibana.json
+++ b/src/plugins/visualize/kibana.json
@@ -10,6 +10,7 @@
"savedObjects",
"visualizations",
"embeddable",
+ "dashboard",
"uiActions"
],
"optionalPlugins": ["home", "share"],
diff --git a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx
index 12a3d1cdf95b1..130561b6245ae 100644
--- a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx
+++ b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx
@@ -75,6 +75,7 @@ const TopNav = ({
},
[visInstance.embeddableHandler]
);
+ const stateTransfer = services.embeddable.getStateTransfer();
const config = useMemo(() => {
if (isEmbeddableRendered) {
@@ -89,6 +90,7 @@ const TopNav = ({
visInstance,
stateContainer,
visualizationIdFromUrl,
+ stateTransfer,
embeddableId,
},
services
@@ -107,6 +109,7 @@ const TopNav = ({
visualizationIdFromUrl,
services,
embeddableId,
+ stateTransfer,
]);
const [indexPattern, setIndexPattern] = useState(vis.data.indexPattern);
const showDatePicker = () => {
diff --git a/src/plugins/visualize/public/application/types.ts b/src/plugins/visualize/public/application/types.ts
index 65b88485b2f06..0a12dbc22a744 100644
--- a/src/plugins/visualize/public/application/types.ts
+++ b/src/plugins/visualize/public/application/types.ts
@@ -44,7 +44,7 @@ import { SharePluginStart } from 'src/plugins/share/public';
import { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/public';
import { EmbeddableStart } from 'src/plugins/embeddable/public';
import { KibanaLegacyStart } from 'src/plugins/kibana_legacy/public';
-import { ConfigSchema } from '../../config';
+import { DashboardStart } from '../../../dashboard/public';
export type PureVisState = SavedVisState;
@@ -111,7 +111,7 @@ export interface VisualizeServices extends CoreStart {
createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject'];
restorePreviousUrl: () => void;
scopedHistory: ScopedHistory;
- featureFlagConfig: ConfigSchema;
+ dashboard: DashboardStart;
}
export interface SavedVisInstance {
diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
index 87a6437192aa9..0423e48bfb41e 100644
--- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
+++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
@@ -38,6 +38,7 @@ import {
} from '../types';
import { VisualizeConstants } from '../visualize_constants';
import { getEditBreadcrumbs } from './breadcrumbs';
+import { EmbeddableStateTransfer } from '../../../../embeddable/public';
interface TopNavConfigParams {
hasUnsavedChanges: boolean;
@@ -49,6 +50,7 @@ interface TopNavConfigParams {
visInstance: VisualizeEditorVisInstance;
stateContainer: VisualizeAppStateContainer;
visualizationIdFromUrl?: string;
+ stateTransfer: EmbeddableStateTransfer;
embeddableId?: string;
}
@@ -63,19 +65,19 @@ export const getTopNavConfig = (
visInstance,
stateContainer,
visualizationIdFromUrl,
+ stateTransfer,
embeddableId,
}: TopNavConfigParams,
{
application,
chrome,
- embeddable,
history,
share,
setActiveUrl,
toastNotifications,
visualizeCapabilities,
i18n: { Context: I18nContext },
- featureFlagConfig,
+ dashboard,
}: VisualizeServices
) => {
const { vis, embeddableHandler } = visInstance;
@@ -118,8 +120,8 @@ export const getTopNavConfig = (
history.replace(appPath);
setActiveUrl(appPath);
- if (newlyCreated && embeddable) {
- embeddable.getStateTransfer().navigateToWithEmbeddablePackage(originatingApp, {
+ if (newlyCreated && stateTransfer) {
+ stateTransfer.navigateToWithEmbeddablePackage(originatingApp, {
state: { id, type: VISUALIZE_EMBEDDABLE_TYPE },
});
} else {
@@ -174,7 +176,7 @@ export const getTopNavConfig = (
if (embeddableId) {
state.embeddableId = embeddableId;
}
- embeddable.getStateTransfer().navigateToWithEmbeddablePackage(originatingApp, { state });
+ stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { state });
};
const topNavMenu: TopNavMenuData[] = [
@@ -212,7 +214,7 @@ export const getTopNavConfig = (
};
if (
originatingApp === 'dashboards' &&
- featureFlagConfig.showNewVisualizeFlow &&
+ dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables &&
!savedVis
) {
return createVisReference();
@@ -284,6 +286,7 @@ export const getTopNavConfig = (
{}}
originatingApp={originatingApp}
@@ -292,7 +295,7 @@ export const getTopNavConfig = (
const isSaveAsButton = anchorElement.classList.contains('saveAsButton');
if (
originatingApp === 'dashboards' &&
- featureFlagConfig.showNewVisualizeFlow &&
+ dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables &&
!isSaveAsButton
) {
createVisReference();
diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts
index 8794593d6c958..29d6f978bd05e 100644
--- a/src/plugins/visualize/public/plugin.ts
+++ b/src/plugins/visualize/public/plugin.ts
@@ -48,6 +48,7 @@ import { VisualizeServices } from './application/types';
import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
import { SavedObjectsStart } from '../../saved_objects/public';
import { EmbeddableStart } from '../../embeddable/public';
+import { DashboardStart } from '../../dashboard/public';
import { UiActionsStart, VISUALIZE_FIELD_TRIGGER } from '../../ui_actions/public';
import {
setUISettings,
@@ -67,6 +68,7 @@ export interface VisualizePluginStartDependencies {
embeddable: EmbeddableStart;
kibanaLegacy: KibanaLegacyStart;
savedObjects: SavedObjectsStart;
+ dashboard: DashboardStart;
uiActions: UiActionsStart;
}
@@ -77,10 +79,6 @@ export interface VisualizePluginSetupDependencies {
share?: SharePluginSetup;
}
-export interface FeatureFlagConfig {
- showNewVisualizeFlow: boolean;
-}
-
export class VisualizePlugin
implements
Plugin {
@@ -171,7 +169,6 @@ export class VisualizePlugin
* this should be replaced to use only scoped history after moving legacy apps to browser routing
*/
const history = createHashHistory();
-
const services: VisualizeServices = {
...coreStart,
history,
@@ -198,7 +195,7 @@ export class VisualizePlugin
savedObjectsPublic: pluginsStart.savedObjects,
scopedHistory: params.history,
restorePreviousUrl,
- featureFlagConfig: this.initializerContext.config.get(),
+ dashboard: pluginsStart.dashboard,
};
params.element.classList.add('visAppWrapper');
diff --git a/src/plugins/visualize/server/index.ts b/src/plugins/visualize/server/index.ts
index 6da0a513b1475..5cebef71d8d22 100644
--- a/src/plugins/visualize/server/index.ts
+++ b/src/plugins/visualize/server/index.ts
@@ -17,17 +17,8 @@
* under the License.
*/
-import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server';
+import { PluginInitializerContext } from 'kibana/server';
import { VisualizeServerPlugin } from './plugin';
-import { ConfigSchema, configSchema } from '../config';
-
-export const config: PluginConfigDescriptor = {
- exposeToBrowser: {
- showNewVisualizeFlow: true,
- },
- schema: configSchema,
-};
-
export const plugin = (initContext: PluginInitializerContext) =>
new VisualizeServerPlugin(initContext);
diff --git a/test/api_integration/apis/status/status.js b/test/api_integration/apis/status/status.js
index c60d354090cc2..edfd4ca08b34d 100644
--- a/test/api_integration/apis/status/status.js
+++ b/test/api_integration/apis/status/status.js
@@ -39,10 +39,6 @@ export default function ({ getService }) {
expect(body.status.overall.state).to.be('green');
expect(body.status.statuses).to.be.an('array');
- const kibanaPlugin = body.status.statuses.find((s) => {
- return s.id.indexOf('plugin:kibana') === 0;
- });
- expect(kibanaPlugin.state).to.be('green');
expect(body.metrics.collection_interval_in_millis).to.be.a('number');
diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js
index c00f01d060f4a..73c36c7562e8b 100644
--- a/test/functional/apps/dashboard/embeddable_rendering.js
+++ b/test/functional/apps/dashboard/embeddable_rendering.js
@@ -33,6 +33,7 @@ export default function ({ getService, getPageObjects }) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const pieChart = getService('pieChart');
+ const security = getService('security');
const dashboardExpect = getService('dashboardExpect');
const dashboardAddPanel = getService('dashboardAddPanel');
const PageObjects = getPageObjects([
@@ -100,6 +101,7 @@ export default function ({ getService, getPageObjects }) {
describe('dashboard embeddable rendering', function describeIndexTests() {
before(async () => {
+ await security.testUser.setRoles(['kibana_admin', 'animals', 'test_logstash_reader']);
await esArchiver.load('dashboard/current/kibana');
await kibanaServer.uiSettings.replace({
defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
@@ -118,6 +120,7 @@ export default function ({ getService, getPageObjects }) {
const currentUrl = await browser.getCurrentUrl();
const newUrl = currentUrl.replace(/\?.*$/, '');
await browser.get(newUrl, false);
+ await security.testUser.restoreDefaults();
});
it('adding visualizations', async () => {
diff --git a/test/functional/apps/dashboard/legacy_urls.ts b/test/functional/apps/dashboard/legacy_urls.ts
index e606649c1df9f..6bb8d808e8daa 100644
--- a/test/functional/apps/dashboard/legacy_urls.ts
+++ b/test/functional/apps/dashboard/legacy_urls.ts
@@ -35,6 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const dashboardAddPanel = getService('dashboardAddPanel');
const listingTable = getService('listingTable');
const esArchiver = getService('esArchiver');
+ const security = getService('security');
let kibanaLegacyBaseUrl: string;
let kibanaVisualizeBaseUrl: string;
@@ -42,6 +43,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
describe('legacy urls', function describeIndexTests() {
before(async function () {
+ await security.testUser.setRoles(['kibana_admin', 'animals']);
await esArchiver.load('dashboard/current/kibana');
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
@@ -61,6 +63,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
after(async function () {
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.deleteItem('legacyTest', testDashboardId);
+ await security.testUser.restoreDefaults();
});
describe('kibana link redirect', () => {
diff --git a/test/functional/apps/status_page/index.ts b/test/functional/apps/status_page/index.ts
index 65349aba93b9b..234e61a142a81 100644
--- a/test/functional/apps/status_page/index.ts
+++ b/test/functional/apps/status_page/index.ts
@@ -21,7 +21,6 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const retry = getService('retry');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
@@ -32,13 +31,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.common.navigateToApp('status_page');
});
- it('should show the kibana plugin as ready', async () => {
- await retry.tryForTime(6000, async () => {
- const text = await testSubjects.getVisibleText('statusBreakdown');
- expect(text.indexOf('plugin:kibana')).to.be.above(-1);
- });
- });
-
it('should show the build hash and number', async () => {
const buildNumberText = await testSubjects.getVisibleText('statusBuildNumber');
expect(buildNumberText).to.contain('BUILD ');
diff --git a/test/functional/config.js b/test/functional/config.js
index 95e0c689089ef..15097d9346471 100644
--- a/test/functional/config.js
+++ b/test/functional/config.js
@@ -264,7 +264,7 @@ export default async function ({ readConfigFile }) {
cluster: [],
indices: [
{
- names: ['animals-*'],
+ names: ['animals-*', 'dogbreeds'],
privileges: ['read', 'view_index_metadata'],
field_security: { grant: ['*'], except: [] },
},
diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts
index 2d78de49a4f94..bf0e0dd7f56f2 100644
--- a/test/functional/page_objects/home_page.ts
+++ b/test/functional/page_objects/home_page.ts
@@ -118,6 +118,21 @@ export function HomePageProvider({ getService, getPageObjects }: FtrProviderCont
await testSubjects.click('onCloudTutorial');
}
+ // click on side nav toggle button to see all of side nav
+ async clickOnToggleNavButton() {
+ await testSubjects.click('toggleNavButton');
+ }
+
+ // collapse the observability side nav details
+ async collapseObservabibilitySideNav() {
+ await testSubjects.click('collapsibleNavGroup-observability');
+ }
+
+ // dock the side nav
+ async dockTheSideNav() {
+ await testSubjects.click('collapsible-nav-lock');
+ }
+
async loadSavedObjects() {
await retry.try(async () => {
await testSubjects.click('loadSavedObjects');
diff --git a/test/functional/page_objects/vega_chart_page.ts b/test/functional/page_objects/vega_chart_page.ts
index 1173c35af3384..6c64f9dda2efd 100644
--- a/test/functional/page_objects/vega_chart_page.ts
+++ b/test/functional/page_objects/vega_chart_page.ts
@@ -33,7 +33,6 @@ export function VegaChartPageProvider({
const find = getService('find');
const testSubjects = getService('testSubjects');
const browser = getService('browser');
- const { common } = getPageObjects(['common']);
const retry = getService('retry');
class VegaChartPage {
@@ -49,6 +48,15 @@ export function VegaChartPageProvider({
return find.byCssSelector('div.vgaVis__controls');
}
+ public getYAxisContainer() {
+ return find.byCssSelector('[aria-label^="Y-axis"]');
+ }
+
+ public async getAceGutterContainer() {
+ const editor = await this.getEditor();
+ return editor.findByClassName('ace_gutter');
+ }
+
public async getRawSpec() {
// Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file?
const editor = await this.getEditor();
@@ -83,20 +91,16 @@ export function VegaChartPageProvider({
}
public async typeInSpec(text: string) {
- await this.focusEditor();
+ const aceGutter = await this.getAceGutterContainer();
- let repeats = 20;
- while (--repeats > 0) {
- await browser.pressKeys(Key.ARROW_UP);
- await common.sleep(50);
- }
- await browser.pressKeys(Key.ARROW_RIGHT);
+ await aceGutter.doubleClick();
+ await browser.pressKeys(Key.LEFT);
+ await browser.pressKeys(Key.RIGHT);
await browser.pressKeys(text);
}
public async cleanSpec() {
- const editor = await this.getEditor();
- const aceGutter = await editor.findByClassName('ace_gutter');
+ const aceGutter = await this.getAceGutterContainer();
await retry.try(async () => {
await aceGutter.doubleClick();
@@ -107,11 +111,11 @@ export function VegaChartPageProvider({
}
public async getYAxisLabels() {
- const chart = await testSubjects.find('visualizationLoader');
- const yAxis = await chart.findByCssSelector('[aria-label^="Y-axis"]');
+ const yAxis = await this.getYAxisContainer();
const tickGroup = await yAxis.findByClassName('role-axis-label');
const labels = await tickGroup.findAllByCssSelector('text');
const labelTexts: string[] = [];
+
for (const label of labels) {
labelTexts.push(await label.getVisibleText());
}
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
index b04fbc2bb2136..c5e080e3c8175 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "27.4.0",
+ "@elastic/eui": "27.4.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
diff --git a/test/interpreter_functional/snapshots/baseline/combined_test3.json b/test/interpreter_functional/snapshots/baseline/combined_test3.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/baseline/combined_test3.json
+++ b/test/interpreter_functional/snapshots/baseline/combined_test3.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/final_output_test.json b/test/interpreter_functional/snapshots/baseline/final_output_test.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/baseline/final_output_test.json
+++ b/test/interpreter_functional/snapshots/baseline/final_output_test.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/metric_all_data.json b/test/interpreter_functional/snapshots/baseline/metric_all_data.json
index 9b0122c157481..ae72bcfa6d5ec 100644
--- a/test/interpreter_functional/snapshots/baseline/metric_all_data.json
+++ b/test/interpreter_functional/snapshots/baseline/metric_all_data.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json
index 2d6e756a7f0a3..8568215fd9e1a 100644
--- a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json
+++ b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json
index 37c6885d76cb0..d11e1dfb925f1 100644
--- a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json
+++ b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json
index 60a0e450906a2..b160e05935f17 100644
--- a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json
+++ b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_2.json b/test/interpreter_functional/snapshots/baseline/partial_test_2.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/baseline/partial_test_2.json
+++ b/test/interpreter_functional/snapshots/baseline/partial_test_2.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test3.json b/test/interpreter_functional/snapshots/baseline/step_output_test3.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/baseline/step_output_test3.json
+++ b/test/interpreter_functional/snapshots/baseline/step_output_test3.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/combined_test3.json b/test/interpreter_functional/snapshots/session/combined_test3.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/session/combined_test3.json
+++ b/test/interpreter_functional/snapshots/session/combined_test3.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/final_output_test.json b/test/interpreter_functional/snapshots/session/final_output_test.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/session/final_output_test.json
+++ b/test/interpreter_functional/snapshots/session/final_output_test.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/metric_all_data.json b/test/interpreter_functional/snapshots/session/metric_all_data.json
index 9b0122c157481..ae72bcfa6d5ec 100644
--- a/test/interpreter_functional/snapshots/session/metric_all_data.json
+++ b/test/interpreter_functional/snapshots/session/metric_all_data.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json
index 2d6e756a7f0a3..8568215fd9e1a 100644
--- a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json
+++ b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json
index 37c6885d76cb0..d11e1dfb925f1 100644
--- a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json
+++ b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json
index 60a0e450906a2..b160e05935f17 100644
--- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json
+++ b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/partial_test_2.json b/test/interpreter_functional/snapshots/session/partial_test_2.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/session/partial_test_2.json
+++ b/test/interpreter_functional/snapshots/session/partial_test_2.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/step_output_test3.json b/test/interpreter_functional/snapshots/session/step_output_test3.json
index af9fe198d88ea..2760875119197 100644
--- a/test/interpreter_functional/snapshots/session/step_output_test3.json
+++ b/test/interpreter_functional/snapshots/session/step_output_test3.json
@@ -1 +1 @@
-{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
+{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}}
\ No newline at end of file
diff --git a/test/new_visualize_flow/config.js b/test/new_visualize_flow/config.js
index a6440d16481d5..c4790a35404c9 100644
--- a/test/new_visualize_flow/config.js
+++ b/test/new_visualize_flow/config.js
@@ -37,7 +37,6 @@ export default async function ({ readConfigFile }) {
...commonConfig.get('kbnTestServer.serverArgs'),
'--oss',
'--telemetry.optIn=false',
- '--visualize.showNewVisualizeFlow=true',
],
},
diff --git a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
index 159bb54f50903..9cf81d27e4e8d 100644
--- a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
+++ b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
@@ -20,7 +20,7 @@
import { History } from 'history';
import React from 'react';
import ReactDOM from 'react-dom';
-import { Router, Route, withRouter, RouteComponentProps } from 'react-router-dom';
+import { Router, Route, withRouter, RouteComponentProps, Redirect } from 'react-router-dom';
import {
EuiPage,
@@ -95,7 +95,7 @@ const Nav = withRouter(({ history, navigateToApp }: NavProps) => (
{
id: 'home',
name: 'Home',
- onClick: () => history.push(''),
+ onClick: () => history.push('/home'),
'data-test-subj': 'fooNavHome',
},
{
@@ -122,7 +122,8 @@ const FooApp = ({ history, context }: { history: History; context: AppMountConte
-
+ } />
+
diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
index e4a66fe47d8f2..dac901f496304 100644
--- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
+++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "27.4.0",
+ "@elastic/eui": "27.4.1",
"react": "^16.12.0"
},
"scripts": {
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
index 0e560dd7be7c3..b7c494807672e 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "27.4.0",
+ "@elastic/eui": "27.4.1",
"react": "^16.12.0"
},
"scripts": {
diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts
index 9306b62b9d521..9ecef65f5f09a 100644
--- a/test/plugin_functional/test_suites/core_plugins/applications.ts
+++ b/test/plugin_functional/test_suites/core_plugins/applications.ts
@@ -54,6 +54,9 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
});
};
+ const navigateTo = async (path: string) =>
+ await browser.navigateTo(`${PageObjects.common.getHostPort()}${path}`);
+
describe('ui applications', function describeIndexTests() {
before(async () => {
await PageObjects.common.navigateToApp('foo');
@@ -63,6 +66,15 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
await testSubjects.existOrFail('fooAppHome');
});
+ it('redirects and renders correctly regardless of trailing slash', async () => {
+ await navigateTo(`/app/foo`);
+ await waitForUrlToBe('/app/foo/home');
+ await testSubjects.existOrFail('fooAppHome');
+ await navigateTo(`/app/foo/`);
+ await waitForUrlToBe('/app/foo/home');
+ await testSubjects.existOrFail('fooAppHome');
+ });
+
it('navigates to its own pages', async () => {
// Go to page A
await testSubjects.click('fooNavPageA');
@@ -72,7 +84,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
// Go to home page
await testSubjects.click('fooNavHome');
- await waitForUrlToBe('/app/foo');
+ await waitForUrlToBe('/app/foo/home');
await loadingScreenNotShown();
await testSubjects.existOrFail('fooAppHome');
});
@@ -86,7 +98,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
it('navigates to app root when navlink is clicked', async () => {
await appsMenu.clickLink('Foo');
- await waitForUrlToBe('/app/foo');
+ await waitForUrlToBe('/app/foo/home');
// await loadingScreenNotShown();
await testSubjects.existOrFail('fooAppHome');
});
@@ -105,7 +117,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
it('can use the back button to navigate back to previous app', async () => {
await browser.goBack();
- await waitForUrlToBe('/app/foo');
+ await waitForUrlToBe('/app/foo/home');
await loadingScreenNotShown();
await testSubjects.existOrFail('fooAppHome');
});
diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js
index 0e3f82792a2ba..a0574dbdf36da 100644
--- a/x-pack/dev-tools/jest/create_jest_config.js
+++ b/x-pack/dev-tools/jest/create_jest_config.js
@@ -58,7 +58,6 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector
`${xPackKibanaDirectory}/dev-tools/jest/setup/setup_test.js`,
`${kibanaDirectory}/src/dev/jest/setup/mocks.js`,
`${kibanaDirectory}/src/dev/jest/setup/react_testing_library.js`,
- `${kibanaDirectory}/src/dev/jest/setup/default_timeout.js`,
],
testEnvironment: 'jest-environment-jsdom-thirteen',
testMatch: ['**/*.test.{js,mjs,ts,tsx}'],
diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js
index f2dda63e689b9..ce6e20bd874b2 100644
--- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js
+++ b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js
@@ -14,6 +14,7 @@ const buildRequest = (path = '/app/kibana') => {
const get = sinon.stub();
return {
+ app: {},
path,
route: { settings: {} },
headers: {},
diff --git a/x-pack/package.json b/x-pack/package.json
index 5333c67f6ac0f..a9ffb85924562 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -203,10 +203,10 @@
"@babel/core": "^7.11.1",
"@babel/register": "^7.10.5",
"@babel/runtime": "^7.11.2",
- "@elastic/apm-rum-react": "^1.2.2",
+ "@elastic/apm-rum-react": "^1.2.3",
"@elastic/datemath": "5.0.3",
"@elastic/ems-client": "7.9.3",
- "@elastic/eui": "27.4.0",
+ "@elastic/eui": "27.4.1",
"@elastic/filesaver": "1.1.2",
"@elastic/maki": "6.3.0",
"@elastic/node-crypto": "1.2.1",
diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts
index a5eb371633f1e..99e81344715a5 100644
--- a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts
+++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts
@@ -46,6 +46,7 @@ const alertsClientFactoryParams: jest.Mocked = {
eventLog: eventLogMock.createStart(),
};
const fakeRequest = ({
+ app: {},
headers: {},
getBasePath: () => '',
path: '/',
diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx
new file mode 100644
index 0000000000000..cf3fe2decfa44
--- /dev/null
+++ b/x-pack/plugins/apm/public/application/csmApp.tsx
@@ -0,0 +1,130 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Route, Router } from 'react-router-dom';
+import styled, { ThemeProvider, DefaultTheme } from 'styled-components';
+import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
+import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
+import { CoreStart, AppMountParameters } from 'kibana/public';
+import { ApmPluginSetupDeps } from '../plugin';
+
+import {
+ KibanaContextProvider,
+ useUiSetting$,
+} from '../../../../../src/plugins/kibana_react/public';
+import { px, units } from '../style/variables';
+import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
+import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
+import { history, resetHistory } from '../utils/history';
+import 'react-vis/dist/style.css';
+import { RumHome } from '../components/app/RumDashboard/RumHome';
+import { ConfigSchema } from '../index';
+import { BreadcrumbRoute } from '../components/app/Main/ProvideBreadcrumbs';
+import { RouteName } from '../components/app/Main/route_config/route_names';
+import { renderAsRedirectTo } from '../components/app/Main/route_config';
+import { ApmPluginContext } from '../context/ApmPluginContext';
+import { UrlParamsProvider } from '../context/UrlParamsContext';
+import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext';
+import { createCallApmApi } from '../services/rest/createCallApmApi';
+
+const CsmMainContainer = styled.div`
+ padding: ${px(units.plus)};
+ height: 100%;
+`;
+
+export const rumRoutes: BreadcrumbRoute[] = [
+ {
+ exact: true,
+ path: '/',
+ render: renderAsRedirectTo('/csm'),
+ breadcrumb: 'Client Side Monitoring',
+ name: RouteName.CSM,
+ },
+];
+
+function CsmApp() {
+ const [darkMode] = useUiSetting$('theme:darkMode');
+
+ return (
+ ({
+ ...outerTheme,
+ eui: darkMode ? euiDarkVars : euiLightVars,
+ darkMode,
+ })}
+ >
+
+
+
+
+
+
+ );
+}
+
+export function CsmAppRoot({
+ core,
+ deps,
+ routerHistory,
+ config,
+}: {
+ core: CoreStart;
+ deps: ApmPluginSetupDeps;
+ routerHistory: typeof history;
+ config: ConfigSchema;
+}) {
+ const i18nCore = core.i18n;
+ const plugins = deps;
+ const apmPluginContextValue = {
+ config,
+ core,
+ plugins,
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+/**
+ * This module is rendered asynchronously in the Kibana platform.
+ */
+
+export const renderApp = (
+ core: CoreStart,
+ deps: ApmPluginSetupDeps,
+ { element }: AppMountParameters,
+ config: ConfigSchema
+) => {
+ createCallApmApi(core.http);
+
+ resetHistory();
+ ReactDOM.render(
+ ,
+ element
+ );
+ return () => {
+ ReactDOM.unmountComponentAtNode(element);
+ };
+};
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index 0c9c6eb86225b..5e502f58e5f56 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -20,6 +20,7 @@ import { LocationProvider } from '../context/LocationContext';
import { MatchedRouteProvider } from '../context/MatchedRouteContext';
import { UrlParamsProvider } from '../context/UrlParamsContext';
import { AlertsContextProvider } from '../../../triggers_actions_ui/public';
+import { createStaticIndexPattern } from '../services/rest/index_pattern';
import {
KibanaContextProvider,
useUiSetting$,
@@ -29,6 +30,9 @@ import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
import { routes } from '../components/app/Main/route_config';
import { history, resetHistory } from '../utils/history';
+import { setHelpExtension } from '../setHelpExtension';
+import { setReadonlyBadge } from '../updateBadge';
+import { createCallApmApi } from '../services/rest/createCallApmApi';
import { ConfigSchema } from '..';
import 'react-vis/dist/style.css';
@@ -61,7 +65,7 @@ function App() {
);
}
-function ApmAppRoot({
+export function ApmAppRoot({
core,
deps,
routerHistory,
@@ -116,13 +120,27 @@ function ApmAppRoot({
/**
* This module is rendered asynchronously in the Kibana platform.
*/
+
export const renderApp = (
core: CoreStart,
deps: ApmPluginSetupDeps,
{ element }: AppMountParameters,
config: ConfigSchema
) => {
+ // render APM feedback link in global help menu
+ setHelpExtension(core);
+ setReadonlyBadge(core);
+
+ createCallApmApi(core.http);
+
resetHistory();
+
+ // Automatically creates static index pattern and stores as saved object
+ createStaticIndexPattern().catch((e) => {
+ // eslint-disable-next-line no-console
+ console.log('Error creating static index pattern', e);
+ });
+
ReactDOM.render(
{
+export const renderAsRedirectTo = (to: string) => {
return ({ location }: RouteComponentProps) => (
,
- breadcrumb: i18n.translate('xpack.apm.home.rumOverview.title', {
- defaultMessage: 'Real User Monitoring',
- }),
- name: RouteName.RUM_OVERVIEW,
- },
{
exact: true,
path: '/settings/anomaly-detection',
diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
index 37d96e74d8ee6..1bf798e3b26d7 100644
--- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
+++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx
@@ -26,6 +26,6 @@ export enum RouteName {
SERVICE_NODES = 'nodes',
LINK_TO_TRACE = 'link_to_trace',
CUSTOMIZE_UI = 'customize_ui',
- RUM_OVERVIEW = 'rum_overview',
ANOMALY_DETECTION = 'anomaly_detection',
+ CSM = 'csm',
}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx
index a1b07640b5c17..24da5e9ef3897 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx
@@ -6,6 +6,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import React from 'react';
+import { i18n } from '@kbn/i18n';
import { RumOverview } from '../RumDashboard';
import { RumHeader } from './RumHeader';
@@ -16,7 +17,11 @@ export function RumHome() {
- End User Experience
+
+ {i18n.translate('xpack.apm.csm.title', {
+ defaultMessage: 'Client Side Monitoring',
+ })}
+
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap
index 1fa440ebb794c..30c65e71f8019 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap
@@ -1,10 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TransactionActionMenu component should match the snapshot 1`] = `
-
+
+
`;
exports[`Storyshots components/Assets/AssetManager two assets 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
-
+ Manage workpad assets
+
+
- Manage workpad assets
-
-
+
-
-
-
- Select or drag and drop images
-
+ Select or drag and drop images
+
+
-
-
- Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets.
-
-
+
+ Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets.
+
+
+
+
-
-
+
+
+
+
+
+
-
+ airplane
+
+
+
-
-
-
-
+
+ (
+ 1
+ kb)
+
+
+
+
+
+
-
-
- airplane
-
-
-
-
- (
- 1
- kb)
-
-
-
+
+
+
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+ marker
+
+
+
-
-
-
-
+
+ (
+ 1
+ kb)
+
+
+
+
+
+
-
-
- marker
-
-
-
-
- (
- 1
- kb)
-
-
-
+
+
+
-
+
+
+
+
+
+
-
-
- 0% space used
-
+ 0% space used
-
+
+
-
- Close
-
+ Close
-
-
+
+
-
,
-
,
-]
+
+
`;
diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot
index 0467161c58c34..0903b4f14084f 100644
--- a/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/custom_element_modal/__stories__/__snapshots__/custom_element_modal.stories.storyshot
@@ -1,1424 +1,1208 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots components/Elements/CustomElementModal with description 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
-
-
-
- Edit custom element
-
-
+
+ Edit custom element
+
+
+
-
-
- Name
-
-
+ Name
+
+
+
+
+ 40 characters remaining
+
+
+
-
-
- Description
-
-
+ Description
+
+
+
+
-
-
- 83 characters remaining
-
+ 83 characters remaining
+
+
-
-
- Thumbnail image
-
-
+ Thumbnail image
+
+
+
+
-
-
-
- Select or drag and drop an image
-
+ Select or drag and drop an image
-
-
- Take a screenshot of your element and upload it here. This can also be done after saving.
-
-
+
+ Take a screenshot of your element and upload it here. This can also be done after saving.
+
+
+
+
+
+ Element preview
+
+
+
-
- Element preview
-
-
+
+
+
-
-
-
-
-
- best element ever
-
-
+
+ best element ever
+
-
+
+
+
-
-
-
- Cancel
-
+ Cancel
-
-
-
+
+
+
+
-
-
- Save
-
+ Save
-
-
+
+
-
,
-
,
-]
+
+
`;
exports[`Storyshots components/Elements/CustomElementModal with image 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
-
-
-
- Edit custom element
-
-
+
+ Edit custom element
+
+
+
-
-
- Name
-
-
+ Name
+
+
+
+
+ 40 characters remaining
+
+
+
-
-
- Description
-
-
+ Description
+
+
+
+
-
-
- 100 characters remaining
-
+ 100 characters remaining
+
+
-
-
- Thumbnail image
-
-
+ Thumbnail image
+
+
+
+
-
-
-
- Select or drag and drop an image
-
+ Select or drag and drop an image
-
-
- Take a screenshot of your element and upload it here. This can also be done after saving.
-
-
+
+ Take a screenshot of your element and upload it here. This can also be done after saving.
+
+
+
+
+
+ Element preview
+
+
+
-
- Element preview
-
-
-
-
-
+
+
+
+
+
+
-
-
-
- Cancel
-
+ Cancel
-
-
-
+
+
+
+
-
-
- Save
-
+ Save
-
-
+
+
-
,
-
,
-]
+
+
`;
exports[`Storyshots components/Elements/CustomElementModal with name 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
-
-
-
- Edit custom element
-
-
+
+ Edit custom element
+
+
+
-
-
- Name
-
-
+ Name
+
+
+
+
+ 32 characters remaining
+
+
+
-
-
- Description
-
-
+ Description
+
+
+
+
-
-
- 100 characters remaining
-
+ 100 characters remaining
+
+
-
-
- Thumbnail image
-
-
+ Thumbnail image
+
+
+
+
-
-
-
- Select or drag and drop an image
-
+ Select or drag and drop an image
-
-
- Take a screenshot of your element and upload it here. This can also be done after saving.
-
-
+
+ Take a screenshot of your element and upload it here. This can also be done after saving.
+
+
+
+
+
+ Element preview
+
+
+
-
- Element preview
-
-
+
+ My Chart
+
-
+
+
+
-
-
-
- Cancel
-
+ Cancel
-
-
-
+
+
+
+
-
-
- Save
-
+ Save
-
-
+
+
-
,
-
,
-]
+
+
`;
exports[`Storyshots components/Elements/CustomElementModal with title 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
-
-
-
- Create new element
-
-
+
+ Create new element
+
+
+
-
-
- Name
-
-
+ Name
+
+
+
+
+ 40 characters remaining
+
+
+
-
-
- Description
-
-
+ Description
+
+
+
+
-
-
- 100 characters remaining
-
+ 100 characters remaining
+
+
-
-
- Thumbnail image
-
-
+ Thumbnail image
+
+
+
+
-
-
-
- Select or drag and drop an image
-
+ Select or drag and drop an image
-
-
- Take a screenshot of your element and upload it here. This can also be done after saving.
-
-
+
+ Take a screenshot of your element and upload it here. This can also be done after saving.
+
+
+
+
+
+ Element preview
+
+
+
-
- Element preview
-
-
+
+
+
-
+
+
+
-
-
-
- Cancel
-
+ Cancel
-
-
-
+
+
+
+
-
-
- Save
-
+ Save
-
-
+
+
-
,
-
,
-]
+
+
`;
diff --git a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
index a40265f0353e7..b435bbbb73495 100644
--- a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
@@ -2,1163 +2,1104 @@
exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = `
-
-
-
-
-
-
+
+
+
-
- Keyboard shortcuts
-
-
+ Keyboard shortcuts
+
+
+
-
+ Element controls
+
+
+
-
- Element controls
-
-
-
-
- Cut
-
-
-
-
-
- CTRL
-
-
-
-
-
- X
-
-
-
-
-
- Copy
-
-
-
-
-
- CTRL
-
-
-
-
-
- C
-
-
-
-
-
- Paste
-
-
-
-
-
- CTRL
-
-
-
-
-
- V
-
-
-
-
-
- Clone
-
-
-
-
-
- CTRL
-
-
-
-
-
- D
-
-
-
-
-
- Delete
-
-
-
-
-
- DEL
-
-
-
-
-
- or
-
-
-
-
-
- BACKSPACE
-
-
-
-
-
- Bring to front
-
-
-
-
-
- CTRL
-
-
-
-
-
- ↑
-
-
-
-
-
- Bring forward
-
-
-
-
-
- CTRL
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↑
-
-
-
-
-
- Send backward
-
-
-
-
-
- CTRL
-
-
-
-
-
- ↓
-
-
-
-
-
- Send to back
-
-
-
-
-
- CTRL
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↓
-
-
-
-
-
- Group
-
-
-
-
-
- G
-
-
-
-
-
- Ungroup
-
-
-
-
-
- U
-
-
-
-
-
- Shift up by 10px
-
-
-
-
-
- ↑
-
-
-
-
-
- Shift down by 10px
-
-
-
-
-
- ↓
-
-
-
-
-
- Shift left by 10px
-
-
-
-
-
- ←
-
-
-
-
-
- Shift right by 10px
-
-
-
-
-
- →
-
-
-
-
-
- Shift up by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↑
-
-
-
-
-
- Shift down by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ↓
-
-
-
-
-
- Shift left by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- ←
-
-
-
-
-
- Shift right by 1px
-
-
-
-
-
- SHIFT
-
-
-
-
-
- →
-
-
-
-
-
-
-
+
+ Cut
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ X
+
+
+
+
+
+ Copy
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ C
+
+
+
+
+
+ Paste
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ V
+
+
+
+
+
+ Clone
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ D
+
+
+
+
+
+ Delete
+
+
+
+
+
+ DEL
+
+
+
+
+
+ or
+
+
+
+
+
+ BACKSPACE
+
+
+
+
+
+ Bring to front
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Bring forward
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Send backward
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Send to back
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Group
+
+
+
+
+
+ G
+
+
+
+
+
+ Ungroup
+
+
+
+
+
+ U
+
+
+
+
+
+ Shift up by 10px
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Shift down by 10px
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Shift left by 10px
+
+
+
+
+
+ ←
+
+
+
+
+
+ Shift right by 10px
+
+
+
+
+
+ →
+
+
+
+
+
+ Shift up by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↑
+
+
+
+
+
+ Shift down by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ↓
+
+
+
+
+
+ Shift left by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ ←
+
+
+
+
+
+ Shift right by 1px
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ →
+
+
+
+
+
+
+
+
+ Expression controls
+
+
+
-
- Expression controls
-
-
-
-
- Run whole expression
-
-
-
-
-
- CTRL
-
-
-
-
-
- ENTER
-
-
-
-
-
-
-
+
+ Run whole expression
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ENTER
+
+
+
+
+
+
+
+
-
- Editor controls
-
-
-
-
- Select multiple elements
-
-
-
-
-
- SHIFT
-
-
-
-
-
- CLICK
-
-
-
-
-
- Resize from center
-
-
-
-
-
- ALT
-
-
-
-
-
- DRAG
-
-
-
-
-
- Move, resize, and rotate without snapping
-
-
-
-
-
- CTRL
-
-
-
-
-
- DRAG
-
-
-
-
-
- Select element below
-
-
-
-
-
- CTRL
-
-
-
-
-
- CLICK
-
-
-
-
-
- Undo last action
-
-
-
-
-
- CTRL
-
-
-
-
-
- Z
-
-
-
-
-
- Redo last action
-
-
-
-
-
- CTRL
-
-
-
-
-
- SHIFT
-
-
-
-
-
- Z
-
-
-
-
-
- Go to previous page
-
-
-
-
-
- ALT
-
-
-
-
-
- [
-
-
-
-
-
- Go to next page
-
-
-
-
-
- ALT
-
-
-
-
-
- ]
-
-
-
-
-
- Toggle edit mode
-
-
-
-
-
- ALT
-
-
-
-
-
- E
-
-
-
-
-
- Show grid
-
-
-
-
-
- ALT
-
-
-
-
-
- G
-
-
-
-
-
- Refresh workpad
-
-
-
-
-
- ALT
-
-
-
-
-
- R
-
-
-
-
-
- Zoom in
-
-
-
-
-
- CTRL
-
-
-
-
-
- ALT
-
-
-
-
-
- +
-
-
-
-
-
- Zoom out
-
-
-
-
-
- CTRL
-
-
-
-
-
- ALT
-
-
-
-
-
- -
-
-
-
-
-
- Reset zoom to 100%
-
-
-
-
-
- CTRL
-
-
-
-
-
- ALT
-
-
-
-
-
- [
-
-
-
-
-
- Enter presentation mode
-
-
-
-
-
- ALT
-
-
-
-
-
- F
-
-
-
-
-
- or
-
-
-
-
-
- ALT
-
-
-
-
-
- P
-
-
-
-
-
-
-
+ Editor controls
+
+
+
+
+ Select multiple elements
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ CLICK
+
+
+
+
+
+ Resize from center
+
+
+
+
+
+ ALT
+
+
+
+
+
+ DRAG
+
+
+
+
+
+ Move, resize, and rotate without snapping
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ DRAG
+
+
+
+
+
+ Select element below
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ CLICK
+
+
+
+
+
+ Undo last action
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ Z
+
+
+
+
+
+ Redo last action
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ SHIFT
+
+
+
+
+
+ Z
+
+
+
+
+
+ Go to previous page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ [
+
+
+
+
+
+ Go to next page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ ]
+
+
+
+
+
+ Toggle edit mode
+
+
+
+
+
+ ALT
+
+
+
+
+
+ E
+
+
+
+
+
+ Show grid
+
+
+
+
+
+ ALT
+
+
+
+
+
+ G
+
+
+
+
+
+ Refresh workpad
+
+
+
+
+
+ ALT
+
+
+
+
+
+ R
+
+
+
+
+
+ Zoom in
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ALT
+
+
+
+
+
+ +
+
+
+
+
+
+ Zoom out
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ALT
+
+
+
+
+
+ -
+
+
+
+
+
+ Reset zoom to 100%
+
+
+
+
+
+ CTRL
+
+
+
+
+
+ ALT
+
+
+
+
+
+ [
+
+
+
+
+
+ Enter presentation mode
+
+
+
+
+
+ ALT
+
+
+
+
+
+ F
+
+
+
+
+
+ or
+
+
+
+
+
+ ALT
+
+
+
+
+
+ P
+
+
+
+
+
+
+
+
-
- Presentation controls
-
-
-
-
- Enter presentation mode
-
-
-
-
-
- ALT
-
-
-
-
-
- F
-
-
-
-
-
- or
-
-
-
-
-
- ALT
-
-
-
-
-
- P
-
-
-
-
-
- Exit presentation mode
-
-
-
-
-
- ESC
-
-
-
-
-
- Go to previous page
-
-
-
-
-
- ALT
-
-
-
-
-
- [
-
-
-
-
-
- or
-
-
-
-
-
- BACKSPACE
-
-
-
-
-
- or
-
-
-
-
-
- ←
-
-
-
-
-
- Go to next page
-
-
-
-
-
- ALT
-
-
-
-
-
- ]
-
-
-
-
-
- or
-
-
-
-
-
- SPACE
-
-
-
-
-
- or
-
-
-
-
-
- →
-
-
-
-
-
- Refresh workpad
-
-
-
-
-
- ALT
-
-
-
-
-
- R
-
-
-
-
-
- Toggle page cycling
-
-
-
-
-
- P
-
-
-
-
-
-
-
+ Presentation controls
+
+
+
+
+ Enter presentation mode
+
+
+
+
+
+ ALT
+
+
+
+
+
+ F
+
+
+
+
+
+ or
+
+
+
+
+
+ ALT
+
+
+
+
+
+ P
+
+
+
+
+
+ Exit presentation mode
+
+
+
+
+
+ ESC
+
+
+
+
+
+ Go to previous page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ [
+
+
+
+
+
+ or
+
+
+
+
+
+ BACKSPACE
+
+
+
+
+
+ or
+
+
+
+
+
+ ←
+
+
+
+
+
+ Go to next page
+
+
+
+
+
+ ALT
+
+
+
+
+
+ ]
+
+
+
+
+
+ or
+
+
+
+
+
+ SPACE
+
+
+
+
+
+ or
+
+
+
+
+
+ →
+
+
+
+
+
+ Refresh workpad
+
+
+
+
+
+ ALT
+
+
+
+
+
+ R
+
+
+
+
+
+ Toggle page cycling
+
+
+
+
+
+ P
+
+
+
+
+
+
-
`;
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot
index fcc22cbe78134..0b345849b0a9a 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__stories__/__snapshots__/saved_elements_modal.stories.storyshot
@@ -1,968 +1,806 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots components/SavedElementsModal no custom elements 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
-
-
- My elements
-
+ My elements
-
+
+
+
+
+
+
+ Add new elements
+
-
-
- Add new elements
-
-
-
-
- Group and save workpad elements to create new elements
-
-
-
-
+
+ Group and save workpad elements to create new elements
+
+
+
-
+
+
-
-
- Close
-
+ Close
-
-
+
+
-
,
-
,
-]
+
+
`;
exports[`Storyshots components/SavedElementsModal with custom elements 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
+ className="euiModalHeader__title canvasAssetManager__modalHeaderTitle"
+ >
+ My elements
+
+
-
+
-
+
+
+
+
+
+
+
+ Custom Element 1
+
+
+
+
+ sample description
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- Custom Element 1
-
-
-
-
- sample description
-
-
+
-
-
+ Custom Element 2
+
+
-
-
-
-
-
+
+ Aenean eu justo auctor, placerat felis non, scelerisque dolor.
+
+
-
-
-
-
-
-
+
+
+
+
-
-
- Custom Element 2
-
-
-
-
- Aenean eu justo auctor, placerat felis non, scelerisque dolor.
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
-
+
+
-
-
-
-
-
+ Custom Element 3
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lobortis aliquet arcu ut turpis duis.
+
+
-
-
-
-
-
-
-
-
- Custom Element 3
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lobortis aliquet arcu ut turpis duis.
-
-
-
-
+
+
-
+
+
-
-
- Close
-
+ Close
-
-
+
+
-
,
-
,
-]
+
+
`;
exports[`Storyshots components/SavedElementsModal with text filter 1`] = `
-Array [
+
,
-
,
-
+
+
+
-
-
+ className="euiModalHeader__title canvasAssetManager__modalHeaderTitle"
+ >
+ My elements
+
+
-
+
-
-
-
+
+ Custom Element 2
+
+
+
+
+ Aenean eu justo auctor, placerat felis non, scelerisque dolor.
+
+
+
-
-
-
-
-
-
-
-
-
-
-
- Custom Element 2
-
-
-
-
- Aenean eu justo auctor, placerat felis non, scelerisque dolor.
-
-
-
-
+
+
-
+
+
-
-
- Close
-
+ Close
-
-
+
+
- ,
-
,
-]
+
+
`;
diff --git a/x-pack/plugins/canvas/public/services/stubs/expressions.ts b/x-pack/plugins/canvas/public/services/stubs/expressions.ts
index ee332e20c4ca3..fd9b083964cb6 100644
--- a/x-pack/plugins/canvas/public/services/stubs/expressions.ts
+++ b/x-pack/plugins/canvas/public/services/stubs/expressions.ts
@@ -12,9 +12,7 @@ import { renderFunctions } from '../../../canvas_plugin_src/renderers/core';
const placeholder = {} as any;
const expressionsPlugin = plugin(placeholder);
-const setup = expressionsPlugin.setup(placeholder, {
- inspector: {},
-} as any);
+const setup = expressionsPlugin.setup(placeholder);
export const expressionsService: ExpressionsService = setup.fork();
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap
index 04cc3e0f383b0..75e132863e47e 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap
@@ -2,357 +2,328 @@
exports[` can navigate Autoplay Settings 1`] = `
-
+
-
-
+ class="euiPopover__panelArrow euiPopover__panelArrow--top"
+ style="left: 10px; top: 0px;"
+ />
+
-
+ class="euiContextMenu"
+ style="height: 0px;"
+ >
-
`;
exports[`
can navigate Autoplay Settings 2`] = `
-
-
-
+
+ class="euiPopover__panelArrow euiPopover__panelArrow--top"
+ style="left: 10px; top: 0px;"
+ />
+
-
+ class="euiContextMenu"
+ style="height: 0px;"
+ >
-
`;
exports[`
can navigate Toolbar Settings, closes when activated 1`] = `
-
-
-
+
+ class="euiPopover__panelArrow euiPopover__panelArrow--top"
+ style="left: 10px; top: 0px;"
+ />
+
-
+ class="euiContextMenu"
+ style="height: 0px;"
+ >
-
`;
exports[`
can navigate Toolbar Settings, closes when activated 2`] = `
-
-
+
-
+ class="euiPopover__panelArrow euiPopover__panelArrow--top"
+ style="left: 10px; top: 0px;"
+ />
+
-
+ class="euiContextMenu"
+ style="height: 0px;"
+ >