diff --git a/.browserslistrc b/.browserslistrc index 04395b913c9c5..36298c0f8cb93 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -4,6 +4,8 @@ last 2 Chrome versions last 2 Safari versions > 0.25% not ie 11 +not op_mini all +not samsung 4 [dev] last 1 chrome versions diff --git a/.github/ISSUE_TEMPLATE/v8_breaking_change.md b/.github/ISSUE_TEMPLATE/v8_breaking_change.md index c91b937586a09..42783808e32ed 100644 --- a/.github/ISSUE_TEMPLATE/v8_breaking_change.md +++ b/.github/ISSUE_TEMPLATE/v8_breaking_change.md @@ -7,6 +7,16 @@ assignees: '' --- + + ## Change description **Which release will ship the breaking change?** diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md index 1134994faa9bd..4129662acb2b1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md @@ -30,9 +30,9 @@ export declare class KibanaRequestboolean | Whether or not the request is a "system request" rather than an application-level request. Can be set on the client using the HttpFetchOptions#asSystemRequest option. | | [params](./kibana-plugin-core-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-core-server.kibanarequest.query.md) | | Query | | -| [rewrittenUrl](./kibana-plugin-core-server.kibanarequest.rewrittenurl.md) | | Url | URL rewritten in onPreRouting request interceptor. | +| [rewrittenUrl](./kibana-plugin-core-server.kibanarequest.rewrittenurl.md) | | URL | URL rewritten in onPreRouting request interceptor. | | [route](./kibana-plugin-core-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | | [socket](./kibana-plugin-core-server.kibanarequest.socket.md) | | IKibanaSocket | [IKibanaSocket](./kibana-plugin-core-server.ikibanasocket.md) | -| [url](./kibana-plugin-core-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | +| [url](./kibana-plugin-core-server.kibanarequest.url.md) | | URL | a WHATWG URL standard object. | | [uuid](./kibana-plugin-core-server.kibanarequest.uuid.md) | | string | A UUID to identify this request. | diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md index 10628bafaf1d4..fb547330ee6ea 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md @@ -9,5 +9,5 @@ URL rewritten in onPreRouting request interceptor. Signature: ```typescript -readonly rewrittenUrl?: Url; +readonly rewrittenUrl?: URL; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md index 31d1348197201..b72760e272bb2 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md @@ -9,5 +9,5 @@ a WHATWG URL standard object. Signature: ```typescript -readonly url: Url; +readonly url: URL; ``` diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index cb5bb1e8fc529..ddb19c8cdc3d7 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -108,4 +108,4 @@ exports[`prepares assets for distribution: baz bundle 1`] = ` exports[`prepares assets for distribution: foo async bundle 1`] = `"(window[\\"foo_bundle_jsonpfunction\\"]=window[\\"foo_bundle_jsonpfunction\\"]||[]).push([[1],{3:function(module,__webpack_exports__,__webpack_require__){\\"use strict\\";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,\\"foo\\",(function(){return foo}));function foo(){}}}]);"`; -exports[`prepares assets for distribution: foo bundle 1`] = `"(function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i({ auth = { isAuthenticated: true }, }: RequestFixtureOptions = {}) { const queryString = stringify(query, { sort: false }); + const url = parseUrl(`${path}${queryString ? `?${queryString}` : ''}`); return KibanaRequest.from( createRawRequestMock({ @@ -83,12 +85,7 @@ function createKibanaRequestMock

({ payload: body, path, method, - url: { - path, - pathname: path, - query: queryString, - search: queryString ? `?${queryString}` : queryString, - }, + url, route: { settings: { tags: routeTags, auth: routeAuthRequired, app: kibanaRouteOptions }, }, @@ -121,6 +118,11 @@ interface DeepPartialArray extends Array> {} type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; function createRawRequestMock(customization: DeepPartial = {}) { + const pathname = customization.url?.pathname || '/'; + const path = `${pathname}${customization.url?.search || ''}`; + const url = Object.assign({ pathname, path, href: path }, customization.url); + + // @ts-expect-error _core isn't supposed to be accessed - remove once we upgrade to hapi v18 return merge( {}, { @@ -129,17 +131,21 @@ function createRawRequestMock(customization: DeepPartial = {}) { isAuthenticated: true, }, headers: {}, - path: '/', + path, route: { settings: {} }, - url: { - href: '/', - }, + url, raw: { req: { - url: '/', + url: path, socket: {}, }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, customization ) as Request; diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 2440f2b1da0bd..d94bce12fb439 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -271,7 +271,7 @@ export class HttpServer { } this.registerOnPreRouting((request, response, toolkit) => { - const oldUrl = request.url.href!; + const oldUrl = request.url.pathname + request.url.search; const newURL = basePathService.remove(oldUrl); const shouldRedirect = newURL !== oldUrl; if (shouldRedirect) { diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 01817b29de8ac..37401a2c24ccd 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -124,7 +124,13 @@ describe('OnPreRouting', () => { const router = createRouter('/'); router.get({ path: '/login', validate: false }, (context, req, res) => { - return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + return res.ok({ + body: { + rewrittenUrl: req.rewrittenUrl + ? `${req.rewrittenUrl.pathname}${req.rewrittenUrl.search}` + : undefined, + }, + }); }); registerOnPreRouting((req, res, t) => t.rewriteUrl('/login')); @@ -143,7 +149,13 @@ describe('OnPreRouting', () => { const router = createRouter('/'); router.get({ path: '/reroute-2', validate: false }, (context, req, res) => { - return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + return res.ok({ + body: { + rewrittenUrl: req.rewrittenUrl + ? `${req.rewrittenUrl.pathname}${req.rewrittenUrl.search}` + : undefined, + }, + }); }); registerOnPreRouting((req, res, t) => t.rewriteUrl('/reroute-1')); @@ -163,7 +175,13 @@ describe('OnPreRouting', () => { const router = createRouter('/'); router.get({ path: '/login', validate: false }, (context, req, res) => { - return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + return res.ok({ + body: { + rewrittenUrl: req.rewrittenUrl + ? `${req.rewrittenUrl.pathname}${req.rewrittenUrl.search}` + : undefined, + }, + }); }); registerOnPreRouting((req, res, t) => t.next()); diff --git a/src/core/server/http/lifecycle/on_pre_routing.ts b/src/core/server/http/lifecycle/on_pre_routing.ts index 92ae1f0b7bbdf..e553f113a7cf8 100644 --- a/src/core/server/http/lifecycle/on_pre_routing.ts +++ b/src/core/server/http/lifecycle/on_pre_routing.ts @@ -17,6 +17,7 @@ * under the License. */ +import { URL } from 'url'; import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi'; import { Logger } from '../../logging'; import { @@ -110,10 +111,30 @@ export function adoptToHapiOnRequest(fn: OnPreRoutingHandler, log: Logger) { if (preRoutingResult.isRewriteUrl(result)) { const appState = request.app as KibanaRequestState; - appState.rewrittenUrl = appState.rewrittenUrl ?? request.url; + appState.rewrittenUrl = + // @ts-expect-error request._core isn't supposed to be accessed - remove once we upgrade to hapi v18 + appState.rewrittenUrl ?? new URL(request.url.href!, request._core.info.uri); const { url } = result; - request.setUrl(url); + + // TODO: Remove once we upgrade to Node.js 12! + // + // Warning: The following for-loop took 10 days to write, and is a hack + // to force V8 to make a copy of the string in memory. + // + // The reason why we need this is because of what appears to be a bug + // in V8 that caused some URL paths to not be routed correctly once + // `request.setUrl` was called with the path. + // + // The details can be seen in this discussion on Twitter: + // https://twitter.com/wa7son/status/1319992632366518277 + let urlCopy = ''; + for (let i = 0; i < url.length; i++) { + urlCopy += url[i]; + } + + request.setUrl(urlCopy); + // We should update raw request as well since it can be proxied to the old platform request.raw.req.url = url; return responseToolkit.continue; diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 2d0e8d6c1a6ad..561bf742050c3 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Url } from 'url'; +import { URL } from 'url'; import uuid from 'uuid'; import { Request, RouteOptionsApp, ApplicationState } from 'hapi'; import { Observable, fromEvent, merge } from 'rxjs'; @@ -45,7 +45,7 @@ export interface KibanaRouteOptions extends RouteOptionsApp { export interface KibanaRequestState extends ApplicationState { requestId: string; requestUuid: string; - rewrittenUrl?: Url; + rewrittenUrl?: URL; } /** @@ -163,7 +163,7 @@ export class KibanaRequest< */ public readonly uuid: string; /** a WHATWG URL standard object. */ - public readonly url: Url; + public readonly url: URL; /** matched route details */ public readonly route: RecursiveReadonly>; /** @@ -190,7 +190,7 @@ export class KibanaRequest< /** * URL rewritten in onPreRouting request interceptor. */ - public readonly rewrittenUrl?: Url; + public readonly rewrittenUrl?: URL; /** @internal */ protected readonly [requestSymbol]: Request; @@ -212,7 +212,8 @@ export class KibanaRequest< this.uuid = appState?.requestUuid ?? uuid.v4(); this.rewrittenUrl = appState?.rewrittenUrl; - this.url = request.url; + // @ts-expect-error request._core isn't supposed to be accessed - remove once we upgrade to hapi v18 + this.url = new URL(request.url.href!, request._core.info.uri); this.headers = deepFreeze({ ...request.headers }); this.isSystemRequest = request.headers['kbn-system-request'] === 'true' || @@ -304,8 +305,8 @@ export class KibanaRequest< if (authOptions === false) return false; throw new Error( `unexpected authentication options: ${JSON.stringify(authOptions)} for route: ${ - this.url.href - }` + this.url.pathname + }${this.url.search}` ); } } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d9dc46d2cad99..914b5fbdb5196 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -162,7 +162,7 @@ import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UpdateDocumentByQueryParams } from 'elasticsearch'; import { UpdateDocumentParams } from 'elasticsearch'; -import { Url } from 'url'; +import { URL } from 'url'; // @public export interface AppCategory { @@ -1007,11 +1007,11 @@ export class KibanaRequest>; // (undocumented) readonly socket: IKibanaSocket; - readonly url: Url; + readonly url: URL; readonly uuid: string; } diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 1c679bdb40b59..939226b565f79 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -7,14 +7,17 @@ set_chmod() { chmod -f 660 ${KBN_PATH_CONF}/kibana.yml || true chmod -f 2750 <%= dataDir %> || true chmod -f 2750 ${KBN_PATH_CONF} || true + chmod -f 2750 <%= logDir %> || true } set_chown() { + chown <%= user %>:<%= group %> <%= logDir %> chown -R <%= user %>:<%= group %> <%= dataDir %> chown -R root:<%= group %> ${KBN_PATH_CONF} } -set_access() { +setup() { + [ ! -d "<%= logDir %>" ] && mkdir "<%= logDir %>" set_chmod set_chown } @@ -35,7 +38,7 @@ case $1 in IS_UPGRADE=true fi - set_access + setup ;; abort-deconfigure|abort-upgrade|abort-remove) ;; @@ -55,7 +58,7 @@ case $1 in IS_UPGRADE=true fi - set_access + setup ;; *) diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index 489f385187f2b..e5de760ea11d0 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -109,6 +109,8 @@ export async function runFpm( `pluginsDir=/usr/share/kibana/plugins`, '--template-value', `dataDir=/var/lib/kibana`, + '--template-value', + `logDir=/var/log/kibana`, // config and data directories are copied to /usr/share and /var/lib // below, so exclude them from the main package source located in diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service index 37c6d0ba56452..05724db8799f3 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service @@ -13,12 +13,9 @@ Environment=KBN_HOME=/usr/share/kibana Environment=KBN_PATH_CONF=/etc/kibana EnvironmentFile=-/etc/default/kibana -<<<<<<< HEAD -======= EnvironmentFile=-/etc/sysconfig/kibana ->>>>>>> master -ExecStart=/usr/share/kibana/bin/kibana +ExecStart=/usr/share/kibana/bin/kibana --logging.dest="/var/log/kibana/kibana.log" Restart=on-failure RestartSec=3 diff --git a/src/plugins/vis_type_vega/public/_vega_editor.scss b/src/plugins/vis_type_vega/public/components/vega_editor.scss similarity index 100% rename from src/plugins/vis_type_vega/public/_vega_editor.scss rename to src/plugins/vis_type_vega/public/components/vega_editor.scss diff --git a/src/plugins/vis_type_vega/public/_vega_vis.scss b/src/plugins/vis_type_vega/public/components/vega_vis.scss similarity index 96% rename from src/plugins/vis_type_vega/public/_vega_vis.scss rename to src/plugins/vis_type_vega/public/components/vega_vis.scss index 6a0d20246089e..004eed5a3972b 100644 --- a/src/plugins/vis_type_vega/public/_vega_vis.scss +++ b/src/plugins/vis_type_vega/public/components/vega_vis.scss @@ -1,3 +1,11 @@ +.vgaVis__wrapper { + @include euiScrollBar; + + display: flex; + flex: 1 1 0; + overflow: auto; +} + .vgaVis { display: flex; flex: 1 1 100%; diff --git a/src/plugins/vis_type_vega/public/components/vega_vis_component.tsx b/src/plugins/vis_type_vega/public/components/vega_vis_component.tsx new file mode 100644 index 0000000000000..f1a2f8cbfa8d4 --- /dev/null +++ b/src/plugins/vis_type_vega/public/components/vega_vis_component.tsx @@ -0,0 +1,85 @@ +/* + * 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, { useEffect, useMemo, useRef } from 'react'; +import { EuiResizeObserver } from '@elastic/eui'; +import { throttle } from 'lodash'; + +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { createVegaVisualization } from '../vega_visualization'; +import { VegaVisualizationDependencies } from '../plugin'; +import { VegaParser } from '../data_model/vega_parser'; + +import './vega_vis.scss'; + +interface VegaVisComponentProps { + deps: VegaVisualizationDependencies; + fireEvent: IInterpreterRenderHandlers['event']; + renderComplete: () => void; + visData: VegaParser; +} + +type VegaVisController = InstanceType>; + +const VegaVisComponent = ({ visData, fireEvent, renderComplete, deps }: VegaVisComponentProps) => { + const chartDiv = useRef(null); + const visController = useRef(null); + + useEffect(() => { + if (chartDiv.current) { + const VegaVis = createVegaVisualization(deps); + visController.current = new VegaVis(chartDiv.current, fireEvent); + } + + return () => { + visController.current?.destroy(); + visController.current = null; + }; + }, [deps, fireEvent]); + + useEffect(() => { + if (visController.current) { + visController.current.render(visData).then(renderComplete); + } + }, [visData, renderComplete]); + + const updateChartSize = useMemo( + () => + throttle(() => { + if (visController.current) { + visController.current.render(visData).then(renderComplete); + } + }, 300), + [renderComplete, visData] + ); + + return ( + + {(resizeRef) => ( +

+
+
+ )} + + ); +}; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { VegaVisComponent as default }; diff --git a/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx b/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx index 5e770fcff556d..7d669235c36b8 100644 --- a/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx +++ b/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx @@ -30,6 +30,8 @@ import { VisParams } from '../vega_fn'; import { VegaHelpMenu } from './vega_help_menu'; import { VegaActionsMenu } from './vega_actions_menu'; +import './vega_editor.scss'; + const aceOptions = { maxLines: Infinity, highlightActiveLine: false, @@ -102,4 +104,6 @@ function VegaVisEditor({ stateParams, setValue }: VisOptionsProps) { ); } -export { VegaVisEditor }; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { VegaVisEditor as default }; diff --git a/src/plugins/vis_type_vega/public/components/vega_vis_editor_lazy.tsx b/src/plugins/vis_type_vega/public/components/vega_vis_editor_lazy.tsx new file mode 100644 index 0000000000000..d6c78972410e0 --- /dev/null +++ b/src/plugins/vis_type_vega/public/components/vega_vis_editor_lazy.tsx @@ -0,0 +1,29 @@ +/* + * 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, { lazy } from 'react'; + +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { VisParams } from '../vega_fn'; + +const VegaVisEditor = lazy(() => import('./vega_vis_editor')); + +export const VegaVisEditorComponent = (props: VisOptionsProps) => ( + +); diff --git a/src/plugins/vis_type_vega/public/index.scss b/src/plugins/vis_type_vega/public/index.scss deleted file mode 100644 index 78d9eb61999f7..0000000000000 --- a/src/plugins/vis_type_vega/public/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -// Prefix all styles with "vga" to avoid conflicts. -// Examples -// vgaChart -// vgaChart__legend -// vgaChart__legend--small -// vgaChart__legend-isLoading - -@import './vega_vis'; -@import './vega_editor'; diff --git a/src/plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts index ce5c5130961c6..04481685c841b 100644 --- a/src/plugins/vis_type_vega/public/plugin.ts +++ b/src/plugins/vis_type_vega/public/plugin.ts @@ -35,10 +35,10 @@ import { import { createVegaFn } from './vega_fn'; import { createVegaTypeDefinition } from './vega_type'; import { IServiceSettings } from '../../maps_legacy/public'; -import './index.scss'; import { ConfigSchema } from '../config'; import { getVegaInspectorView } from './vega_inspector'; +import { getVegaVisRenderer } from './vega_vis_renderer'; /** @internal */ export interface VegaVisualizationDependencies { @@ -93,6 +93,7 @@ export class VegaPlugin implements Plugin, void> { inspector.registerView(getVegaInspectorView({ uiSettings: core.uiSettings })); expressions.registerFunction(() => createVegaFn(visualizationDependencies)); + expressions.registerRenderer(getVegaVisRenderer(visualizationDependencies)); visualizations.createBaseVisualization(createVegaTypeDefinition(visualizationDependencies)); } diff --git a/src/plugins/vis_type_vega/public/to_ast.ts b/src/plugins/vis_type_vega/public/to_ast.ts new file mode 100644 index 0000000000000..a5fe8f13c3daf --- /dev/null +++ b/src/plugins/vis_type_vega/public/to_ast.ts @@ -0,0 +1,32 @@ +/* + * 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 { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { Vis } from '../../visualizations/public'; +import { VegaExpressionFunctionDefinition, VisParams } from './vega_fn'; + +export const toExpressionAst = (vis: Vis) => { + const vega = buildExpressionFunction('vega', { + spec: vis.params.spec, + }); + + const ast = buildExpression([vega]); + + return ast.toAst(); +}; diff --git a/src/plugins/vis_type_vega/public/vega_fn.ts b/src/plugins/vis_type_vega/public/vega_fn.ts index c88b78948133c..25d4e76c336b3 100644 --- a/src/plugins/vis_type_vega/public/vega_fn.ts +++ b/src/plugins/vis_type_vega/public/vega_fn.ts @@ -40,21 +40,23 @@ interface Arguments { export type VisParams = Required; -interface RenderValue { +export interface RenderValue { visData: VegaParser; visType: 'vega'; visConfig: VisParams; } -export const createVegaFn = ( - dependencies: VegaVisualizationDependencies -): ExpressionFunctionDefinition< +export type VegaExpressionFunctionDefinition = ExpressionFunctionDefinition< 'vega', Input, Arguments, Output, ExecutionContext -> => ({ +>; + +export const createVegaFn = ( + dependencies: VegaVisualizationDependencies +): VegaExpressionFunctionDefinition => ({ name: 'vega', type: 'render', inputTypes: ['kibana_context', 'null'], @@ -80,7 +82,7 @@ export const createVegaFn = ( return { type: 'render', - as: 'visualization', + as: 'vega_vis', value: { visData: response, visType: 'vega', diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx index 6dfa7a23c4fe8..350e781dc7076 100644 --- a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx +++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx @@ -41,7 +41,7 @@ const specLabel = i18n.translate('visTypeVega.inspector.specLabel', { defaultMessage: 'Spec', }); -export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => { +const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => { const tabs = [ { id: 'data-viewer--id', @@ -75,3 +75,7 @@ export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => { /> ); }; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { VegaDataInspector as default }; diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx b/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx index 83d9e467646a6..4b3e48d6a37a0 100644 --- a/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx +++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx @@ -16,14 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'kibana/public'; -import { VegaAdapter } from './vega_adapter'; -import { VegaDataInspector, VegaDataInspectorProps } from './vega_data_inspector'; import { KibanaContextProvider } from '../../../kibana_react/public'; import { Adapters, RequestAdapter, InspectorViewDescription } from '../../../inspector/public'; +import { VegaAdapter } from './vega_adapter'; +import type { VegaDataInspectorProps } from './vega_data_inspector'; + +const VegaDataInspector = lazy(() => import('./vega_data_inspector')); export interface VegaInspectorAdapters extends Adapters { requests: RequestAdapter; @@ -46,7 +49,9 @@ export const getVegaInspectorView = (dependencies: VegaInspectorViewDependencies }, component: (props) => ( - + }> + + ), } as InspectorViewDescription); diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index 0496f765e5e99..17f35b75f0016 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -21,22 +21,20 @@ import { i18n } from '@kbn/i18n'; import { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; import { DefaultEditorSize } from '../../vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; -import { VegaVisEditor } from './components'; import { createVegaRequestHandler } from './vega_request_handler'; -// @ts-expect-error -import { createVegaVisualization } from './vega_visualization'; import { getDefaultSpec } from './default_spec'; import { createInspectorAdapters } from './vega_inspector'; import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; - +import { toExpressionAst } from './to_ast'; +import { VisParams } from './vega_fn'; import { getInfoMessage } from './components/experimental_map_vis_info'; +import { VegaVisEditorComponent } from './components/vega_vis_editor_lazy'; export const createVegaTypeDefinition = ( dependencies: VegaVisualizationDependencies -): BaseVisTypeOptions => { +): BaseVisTypeOptions => { const requestHandler = createVegaRequestHandler(dependencies); - const visualization = createVegaVisualization(dependencies); return { name: 'vega', @@ -49,13 +47,12 @@ export const createVegaTypeDefinition = ( icon: 'visVega', visConfig: { defaults: { spec: getDefaultSpec() } }, editorConfig: { - optionsTemplate: VegaVisEditor, + optionsTemplate: VegaVisEditorComponent, enableAutoApply: true, defaultSize: DefaultEditorSize.MEDIUM, }, - visualization, requestHandler, - responseHandler: 'none', + toExpressionAst, options: { showIndexSelection: false, showQueryBar: true, diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.d.ts b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.d.ts new file mode 100644 index 0000000000000..54b96813769ba --- /dev/null +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.d.ts @@ -0,0 +1,40 @@ +/* + * 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 { DataPublicPluginStart } from 'src/plugins/data/public'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { IServiceSettings } from 'src/plugins/maps_legacy/public'; +import { VegaParser } from '../data_model/vega_parser'; + +interface VegaViewParams { + parentEl: HTMLDivElement; + fireEvent: IInterpreterRenderHandlers['event']; + vegaParser: VegaParser; + serviceSettings: IServiceSettings; + filterManager: DataPublicPluginStart['query']['filterManager']; + timefilter: DataPublicPluginStart['query']['timefilter']['timefilter']; + // findIndex: (index: string) => Promise<...>; +} + +export class VegaBaseView { + constructor(params: VegaViewParams); + init(): Promise; + onError(error: any): void; + destroy(): Promise; +} diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js index 979432b2aed2a..25ea77ddbccb4 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -63,7 +63,7 @@ export class VegaBaseView { this._parser = opts.vegaParser; this._serviceSettings = opts.serviceSettings; this._filterManager = opts.filterManager; - this._applyFilter = opts.applyFilter; + this._fireEvent = opts.fireEvent; this._timefilter = opts.timefilter; this._findIndex = opts.findIndex; this._view = null; @@ -264,7 +264,7 @@ export class VegaBaseView { const indexId = await this._findIndex(index); const filter = esFilters.buildQueryFilter(query, indexId); - this._applyFilter({ filters: [filter] }); + this._fireEvent({ name: 'applyFilter', data: { filters: [filter] } }); } /** @@ -301,19 +301,22 @@ export class VegaBaseView { setTimeFilterHandler(start, end) { const { from, to, mode } = VegaBaseView._parseTimeRange(start, end); - this._applyFilter({ - timeFieldName: '*', - filters: [ - { - range: { - '*': { - mode, - gte: from, - lte: to, + this._fireEvent({ + name: 'applyFilter', + data: { + timeFieldName: '*', + filters: [ + { + range: { + '*': { + mode, + gte: from, + lte: to, + }, }, }, - }, - ], + ], + }, }); } diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.d.ts b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.d.ts new file mode 100644 index 0000000000000..a1210e05f4507 --- /dev/null +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.d.ts @@ -0,0 +1,22 @@ +/* + * 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 { VegaBaseView } from './vega_base_view'; + +export class VegaMapView extends VegaBaseView {} diff --git a/src/plugins/vis_type_vega/public/components/index.ts b/src/plugins/vis_type_vega/public/vega_view/vega_view.d.ts similarity index 89% rename from src/plugins/vis_type_vega/public/components/index.ts rename to src/plugins/vis_type_vega/public/vega_view/vega_view.d.ts index 90f067c778fd2..c137d8222750c 100644 --- a/src/plugins/vis_type_vega/public/components/index.ts +++ b/src/plugins/vis_type_vega/public/vega_view/vega_view.d.ts @@ -17,4 +17,6 @@ * under the License. */ -export { VegaVisEditor } from './vega_vis_editor'; +import { VegaBaseView } from './vega_base_view'; + +export class VegaView extends VegaBaseView {} diff --git a/src/plugins/vis_type_vega/public/vega_vis_renderer.tsx b/src/plugins/vis_type_vega/public/vega_vis_renderer.tsx new file mode 100644 index 0000000000000..542f59b3dfff9 --- /dev/null +++ b/src/plugins/vis_type_vega/public/vega_vis_renderer.tsx @@ -0,0 +1,51 @@ +/* + * 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, { lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { ExpressionRenderDefinition } from 'src/plugins/expressions'; +import { VisualizationContainer } from '../../visualizations/public'; +import { VegaVisualizationDependencies } from './plugin'; +import { RenderValue } from './vega_fn'; +const VegaVisComponent = lazy(() => import('./components/vega_vis_component')); + +export const getVegaVisRenderer: ( + deps: VegaVisualizationDependencies +) => ExpressionRenderDefinition = (deps) => ({ + name: 'vega_vis', + reuseDomNode: true, + render: (domNode, { visData }, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/vis_type_vega/public/vega_visualization.test.js b/src/plugins/vis_type_vega/public/vega_visualization.test.js index dcf1722768075..837fdf2a9aea3 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.test.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.test.js @@ -30,8 +30,6 @@ import vegaMapGraph from './test_utils/vega_map_test.json'; import { VegaParser } from './data_model/vega_parser'; import { SearchAPI } from './data_model/search_api'; -import { createVegaTypeDefinition } from './vega_type'; - import { setInjectedVars, setData, setSavedObjects, setNotifications } from './services'; import { coreMock } from '../../../core/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; @@ -49,9 +47,7 @@ jest.mock('./lib/vega', () => ({ describe('VegaVisualizations', () => { let domNode; let VegaVisualization; - let vis; let vegaVisualizationDependencies; - let vegaVisType; let mockWidth; let mockedWidthValue; @@ -91,22 +87,12 @@ describe('VegaVisualizations', () => { getServiceSettings: mockGetServiceSettings, }; - vegaVisType = createVegaTypeDefinition(vegaVisualizationDependencies); VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); }); describe('VegaVisualization - basics', () => { beforeEach(async () => { setupDOM(); - - vis = { - type: vegaVisType, - API: { - events: { - applyFilter: jest.fn(), - }, - }, - }; }); afterEach(() => { @@ -117,7 +103,7 @@ describe('VegaVisualizations', () => { test('should show vegalite graph and update on resize (may fail in dev env)', async () => { let vegaVis; try { - vegaVis = new VegaVisualization(domNode, vis); + vegaVis = new VegaVisualization(domNode, jest.fn()); const vegaParser = new VegaParser( JSON.stringify(vegaliteGraph), @@ -137,7 +123,7 @@ describe('VegaVisualizations', () => { mockedWidthValue = 256; mockedHeightValue = 256; - await vegaVis._vegaView.resize(); + await vegaVis.vegaView.resize(); expect(domNode.innerHTML).toMatchSnapshot(); } finally { @@ -148,7 +134,7 @@ describe('VegaVisualizations', () => { test('should show vega graph (may fail in dev env)', async () => { let vegaVis; try { - vegaVis = new VegaVisualization(domNode, vis); + vegaVis = new VegaVisualization(domNode, jest.fn()); const vegaParser = new VegaParser( JSON.stringify(vegaGraph), new SearchAPI({ @@ -172,7 +158,7 @@ describe('VegaVisualizations', () => { test('should show vega blank rectangle on top of a map (vegamap)', async () => { let vegaVis; try { - vegaVis = new VegaVisualization(domNode, vis); + vegaVis = new VegaVisualization(domNode, jest.fn()); const vegaParser = new VegaParser( JSON.stringify(vegaMapGraph), new SearchAPI({ diff --git a/src/plugins/vis_type_vega/public/vega_visualization.js b/src/plugins/vis_type_vega/public/vega_visualization.ts similarity index 70% rename from src/plugins/vis_type_vega/public/vega_visualization.js rename to src/plugins/vis_type_vega/public/vega_visualization.ts index 2d58e9cda60cd..58c436bcd4be4 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.ts @@ -17,28 +17,34 @@ * under the License. */ import { i18n } from '@kbn/i18n'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { VegaParser } from './data_model/vega_parser'; +import { VegaVisualizationDependencies } from './plugin'; import { getNotifications, getData, getSavedObjects } from './services'; +import type { VegaView } from './vega_view/vega_view'; -export const createVegaVisualization = ({ getServiceSettings }) => +export const createVegaVisualization = ({ getServiceSettings }: VegaVisualizationDependencies) => class VegaVisualization { - constructor(el, vis) { - this._el = el; - this._vis = vis; + private readonly dataPlugin = getData(); + private readonly savedObjectsClient = getSavedObjects(); + private vegaView: InstanceType | null = null; - this.savedObjectsClient = getSavedObjects(); - this.dataPlugin = getData(); - } + constructor( + private el: HTMLDivElement, + private fireEvent: IInterpreterRenderHandlers['event'] + ) {} /** * Find index pattern by its title, of if not given, gets default * @param {string} [index] * @returns {Promise} index id */ - async findIndex(index) { + async findIndex(index: string) { const { indexPatterns } = this.dataPlugin; let idxObj; if (index) { + // @ts-expect-error idxObj = indexPatterns.findByTitle(this.savedObjectsClient, index); if (!idxObj) { throw new Error( @@ -61,16 +67,10 @@ export const createVegaVisualization = ({ getServiceSettings }) => return idxObj.id; } - /** - * - * @param {VegaParser} visData - * @param {*} status - * @returns {Promise} - */ - async render(visData) { + async render(visData: VegaParser) { const { toasts } = getNotifications(); - if (!visData && !this._vegaView) { + if (!visData && !this.vegaView) { toasts.addWarning( i18n.translate('visTypeVega.visualization.unableToRenderWithoutDataWarningMessage', { defaultMessage: 'Unable to render without data', @@ -82,8 +82,8 @@ export const createVegaVisualization = ({ getServiceSettings }) => try { await this._render(visData); } catch (error) { - if (this._vegaView) { - this._vegaView.onError(error); + if (this.vegaView) { + this.vegaView.onError(error); } else { toasts.addError(error, { title: i18n.translate('visTypeVega.visualization.renderErrorTitle', { @@ -94,20 +94,20 @@ export const createVegaVisualization = ({ getServiceSettings }) => } } - async _render(vegaParser) { + async _render(vegaParser: VegaParser) { if (vegaParser) { // New data received, rebuild the graph - if (this._vegaView) { - await this._vegaView.destroy(); - this._vegaView = null; + if (this.vegaView) { + await this.vegaView.destroy(); + this.vegaView = null; } const serviceSettings = await getServiceSettings(); const { filterManager } = this.dataPlugin.query; const { timefilter } = this.dataPlugin.query.timefilter; const vegaViewParams = { - parentEl: this._el, - applyFilter: this._vis.API.events.applyFilter, + parentEl: this.el, + fireEvent: this.fireEvent, vegaParser, serviceSettings, filterManager, @@ -116,18 +116,17 @@ export const createVegaVisualization = ({ getServiceSettings }) => }; if (vegaParser.useMap) { - const services = { toastService: getNotifications().toasts }; const { VegaMapView } = await import('./vega_view/vega_map_view'); - this._vegaView = new VegaMapView(vegaViewParams, services); + this.vegaView = new VegaMapView(vegaViewParams); } else { - const { VegaView } = await import('./vega_view/vega_view'); - this._vegaView = new VegaView(vegaViewParams); + const { VegaView: VegaViewClass } = await import('./vega_view/vega_view'); + this.vegaView = new VegaViewClass(vegaViewParams); } - await this._vegaView.init(); + await this.vegaView?.init(); } } destroy() { - return this._vegaView && this._vegaView.destroy(); + this.vegaView?.destroy(); } }; diff --git a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap index cbdecd4aac747..959d9031853af 100644 --- a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -13,5 +13,3 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"geohash\\":1,\\"geocentroid\\":3}}' "`; - -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles vega function 1`] = `"vega spec='this is a test' "`; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index c744043ed155b..501a69080d93f 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -94,14 +94,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { uiState = {}; }); - it('handles vega function', () => { - const vis = { - params: { spec: 'this is a test' }, - }; - const actual = buildPipelineVisFunction.vega(vis.params, schemasDef, uiState); - expect(actual).toMatchSnapshot(); - }); - it('handles input_control_vis function', () => { const params = { some: 'nested', diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index eb431212166a3..b08583c376b36 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -254,9 +254,6 @@ const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): vo }; export const buildPipelineVisFunction: BuildPipelineVisFunction = { - vega: (params) => { - return `vega ${prepareString('spec', params.spec)}`; - }, input_control_vis: (params) => { return `input_control_vis ${prepareJson('visConfig', params)}`; }, diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 7fc120f9ea474..d3c0fe834958d 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -88,7 +88,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await footer.getVisibleText()).to.have.string(rowsHardLimit); }); - describe('expand a document row', function () { + // FLAKY: https://github.com/elastic/kibana/issues/81632 + describe.skip('expand a document row', function () { const rowToInspect = 1; beforeEach(async function () { // close the toggle if open diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 18cbd9f9c5fad..c8e2684651598 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -145,6 +145,12 @@ test('executes the task by calling the executor with proper parameters', async ( url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, }); }); @@ -271,6 +277,12 @@ test('uses API key when provided', async () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, }); }); @@ -310,6 +322,12 @@ test(`doesn't use API key when not provided`, async () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, }); }); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index aeeeb4ed7d520..93ae5a2c807f9 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -102,6 +102,12 @@ export class TaskRunnerFactory { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, } as unknown) as KibanaRequest; let executorResult: ActionTypeExecutorResult; 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 55c2f3ddd18a4..4e457cdb12bd3 100644 --- a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts @@ -60,6 +60,12 @@ const fakeRequest = ({ url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as Request; diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index b13a1c62f6602..ece7d31d2f7fd 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -149,6 +149,12 @@ describe('Alerting Plugin', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: jest.fn(), } as unknown) as KibanaRequest; await startContract.getAlertsClientWithRequest(fakeRequest); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 8e345d6ff66a8..17d5fcd31b745 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -364,6 +364,12 @@ describe('Task Runner', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` @@ -662,6 +668,12 @@ describe('Task Runner', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }); }); @@ -694,6 +706,12 @@ describe('Task Runner', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }); }); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 954c5675df89c..76125da20d552 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -101,6 +101,12 @@ export class TaskRunner { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index 2baddbe572a52..d8f54c7bfc94f 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -28,6 +28,7 @@ import { ConfigSchema } from '../index'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { px, units } from '../style/variables'; +import { createStaticIndexPattern } from '../services/rest/index_pattern'; const CsmMainContainer = styled.div` padding: ${px(units.plus)}; @@ -114,6 +115,12 @@ export const renderApp = ( ) => { createCallApmApi(core.http); + // 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( { test('should not redirects for not app/* requests', async () => { const request = ({ url: { - path: 'api/test', + pathname: 'api/test', }, } as unknown) as KibanaRequest; @@ -57,7 +58,7 @@ describe('DashboardOnlyModeRequestInterceptor', () => { test('should not redirects not authenticated users', async () => { const request = ({ url: { - path: '/app/home', + pathname: '/app/home', }, } as unknown) as KibanaRequest; @@ -70,10 +71,9 @@ describe('DashboardOnlyModeRequestInterceptor', () => { function testRedirectToDashboardModeApp(url: string) { describe(`requests to url:"${url}"`, () => { test('redirects to the dashboard_mode app instead', async () => { + const { pathname, search, hash } = parseUrl(url); const request = ({ - url: { - path: url, - }, + url: { pathname, search, hash }, credentials: { roles: [DASHBOARD_ONLY_MODE_ROLE], }, diff --git a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts index 4378c818f087c..9978d18142ff5 100644 --- a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts +++ b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts @@ -22,7 +22,7 @@ export const setupDashboardModeRequestInterceptor = ({ getUiSettingsClient, }: DashboardModeRequestInterceptorDependencies) => (async (request, response, toolkit) => { - const path = request.url.path || ''; + const path = request.url.pathname; const isAppRequest = path.startsWith('/app/'); if (!isAppRequest) { diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index 2bddc9f1c80bd..5bd15ce411002 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -21,7 +21,6 @@ describe('callEnterpriseSearchConfigAPI', () => { accessCheckTimeoutWarning: 100, }; const mockRequest = { - url: { path: '/app/kibana' }, headers: { authorization: '==someAuth' }, }; const mockDependencies = { diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts index 3273fe847080f..d9846428b9488 100644 --- a/x-pack/plugins/event_log/server/event_log_client.test.ts +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -114,7 +114,7 @@ describe('EventLogStart', () => { ).toEqual(result); expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( - esContext.esNames.alias, + esContext.esNames.indexPattern, undefined, 'saved-object-type', 'saved-object-id', @@ -195,7 +195,7 @@ describe('EventLogStart', () => { ).toEqual(result); expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( - esContext.esNames.alias, + esContext.esNames.indexPattern, undefined, 'saved-object-type', 'saved-object-id', @@ -322,6 +322,12 @@ function FakeRequest(): KibanaRequest { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectGetter, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/event_log_client.ts b/x-pack/plugins/event_log/server/event_log_client.ts index 32fd99d170026..b7de4acb9428c 100644 --- a/x-pack/plugins/event_log/server/event_log_client.ts +++ b/x-pack/plugins/event_log/server/event_log_client.ts @@ -92,7 +92,7 @@ export class EventLogClient implements IEventLogClient { await this.savedObjectGetter(type, id); return await this.esContext.esAdapter.queryEventsBySavedObject( - this.esContext.esNames.alias, + this.esContext.esNames.indexPattern, namespace, type, id, diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts index 0a5b169e87d4d..db6f4a1ad0f27 100644 --- a/x-pack/plugins/event_log/server/event_log_start_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.ts @@ -56,6 +56,12 @@ function fakeRequest(): KibanaRequest { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts index 6a02d54c87514..076260ab2fe53 100644 --- a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts +++ b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts @@ -93,6 +93,12 @@ function fakeRequest(): KibanaRequest { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts b/x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts index fe06de765bbff..1ad710ba70e29 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts @@ -23,6 +23,12 @@ const fakeRequest = ({ url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, } as unknown) as KibanaRequest; export async function agentPolicyUpdateEventHandler( diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts index 994ecc64c82a7..b9ef36ecaae54 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts @@ -23,6 +23,12 @@ function getInternalUserSOClient() { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, } as unknown) as KibanaRequest; return appContextService.getInternalUserSOClient(fakeRequest); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts index aa48d8fe18e9f..c0e8540004930 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts @@ -58,6 +58,12 @@ function getInternalUserSOClient() { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, } as unknown) as KibanaRequest; return appContextService.getInternalUserSOClient(fakeRequest); diff --git a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property.ts index fad6d6b84f77f..efdede82a7449 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/join_tooltip_property.ts @@ -40,7 +40,8 @@ export class JoinTooltipProperty implements ITooltipProperty { async getESFilters(): Promise { const esFilters = []; if (this._tooltipProperty.isFilterable()) { - esFilters.push(...(await this._tooltipProperty.getESFilters())); + const filters = await this._tooltipProperty.getESFilters(); + esFilters.push(...filters); } for (let i = 0; i < this._leftInnerJoins.length; i++) { @@ -51,7 +52,8 @@ export class JoinTooltipProperty implements ITooltipProperty { this._tooltipProperty.getRawValue() ); if (esTooltipProperty) { - esFilters.push(...(await esTooltipProperty.getESFilters())); + const filters = await esTooltipProperty.getESFilters(); + esFilters.push(...filters); } } catch (e) { // eslint-disable-next-line no-console diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts index 782d645dc230a..067f213603fe3 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts @@ -15,7 +15,7 @@ export * from '../../actions'; export * from '../../selectors/map_selectors'; export * from '../../routing/bootstrap/get_initial_layers'; export * from '../../embeddable/merge_input_with_saved_map'; -export * from '../../routing/maps_router'; +export { renderApp } from '../../routing/render_app'; export * from '../../classes/layers/solution_layers/security'; export { registerLayerWizard } from '../../classes/layers/layer_wizard_registry'; export { registerSource } from '../../classes/sources/source_registry'; diff --git a/x-pack/plugins/maps/public/routing/maps_router.tsx b/x-pack/plugins/maps/public/routing/render_app.tsx similarity index 81% rename from x-pack/plugins/maps/public/routing/maps_router.tsx rename to x-pack/plugins/maps/public/routing/render_app.tsx index d7e6e6e079953..65cccc53f5047 100644 --- a/x-pack/plugins/maps/public/routing/maps_router.tsx +++ b/x-pack/plugins/maps/public/routing/render_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Router, Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { Provider } from 'react-redux'; import { AppMountParameters } from 'kibana/public'; @@ -24,13 +24,12 @@ import { } from '../../../../../src/plugins/kibana_utils/public'; import { getStore } from './store_operations'; import { LoadListAndRender } from './routes/list/load_list_and_render'; -import { LoadMapAndRender } from './routes/maps_app/load_map_and_render'; +import { MapApp } from './routes/map_app'; export let goToSpecifiedPath: (path: string) => void; export let kbnUrlStateStorage: IKbnUrlStateStorage; export async function renderApp({ - appBasePath, element, history, onAppLeave, @@ -43,29 +42,6 @@ export async function renderApp({ ...withNotifyOnErrors(getToasts()), }); - render( - , - element - ); - - return () => { - unmountComponentAtNode(element); - }; -} - -interface Props { - history: AppMountParameters['history'] | RouteComponentProps['history']; - appBasePath: AppMountParameters['appBasePath']; - onAppLeave: AppMountParameters['onAppLeave']; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; -} - -const App: React.FC = ({ history, appBasePath, onAppLeave, setHeaderActionMenu }) => { const store = getStore(); const I18nContext = getCoreI18n().Context; @@ -88,7 +64,7 @@ const App: React.FC = ({ history, appBasePath, onAppLeave, setHeaderActio }); } - return ( + render( @@ -96,7 +72,7 @@ const App: React.FC = ({ history, appBasePath, onAppLeave, setHeaderActio ( - = ({ history, appBasePath, onAppLeave, setHeaderActio exact path={`/map`} render={() => ( - = ({ history, appBasePath, onAppLeave, setHeaderActio - + , + element ); -}; + + return () => { + unmountComponentAtNode(element); + }; +} diff --git a/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.tsx b/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.tsx index 3b0891a4fd44d..ca92442ae93e6 100644 --- a/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.tsx @@ -30,7 +30,7 @@ import { EuiBasicTableColumn, } from '@elastic/eui/src/components/basic_table/basic_table'; import { EuiTableSortingType } from '@elastic/eui'; -import { goToSpecifiedPath } from '../../maps_router'; +import { goToSpecifiedPath } from '../../render_app'; // @ts-expect-error import { addHelpMenuToAppChrome } from '../../../help_menu_util'; import { APP_ID, MAP_PATH } from '../../../../common/constants'; diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/get_breadcrumbs.test.tsx b/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.test.tsx similarity index 96% rename from x-pack/plugins/maps/public/routing/routes/maps_app/get_breadcrumbs.test.tsx rename to x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.test.tsx index e8e0e583a7c6d..3516daf526968 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/get_breadcrumbs.test.tsx +++ b/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.test.tsx @@ -7,7 +7,7 @@ import { getBreadcrumbs } from './get_breadcrumbs'; jest.mock('../../../kibana_services', () => {}); -jest.mock('../../maps_router', () => {}); +jest.mock('../../render_app', () => {}); const getHasUnsavedChanges = () => { return false; diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/get_breadcrumbs.tsx b/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.tsx similarity index 96% rename from x-pack/plugins/maps/public/routing/routes/maps_app/get_breadcrumbs.tsx rename to x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.tsx index d9b60b670b93e..88dba0f83ec2f 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/get_breadcrumbs.tsx +++ b/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { getCoreOverlays, getNavigateToApp } from '../../../kibana_services'; -import { goToSpecifiedPath } from '../../maps_router'; +import { goToSpecifiedPath } from '../../render_app'; import { getAppTitle } from '../../../../common/i18n_getters'; export const unsavedChangesWarning = i18n.translate( diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/index.ts b/x-pack/plugins/maps/public/routing/routes/map_app/index.ts similarity index 94% rename from x-pack/plugins/maps/public/routing/routes/maps_app/index.ts rename to x-pack/plugins/maps/public/routing/routes/map_app/index.ts index 812d7fcf30981..0b9f0cfe33e44 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/index.ts +++ b/x-pack/plugins/maps/public/routing/routes/map_app/index.ts @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { ThunkDispatch } from 'redux-thunk'; import { AnyAction } from 'redux'; import { Filter, Query, TimeRange } from 'src/plugins/data/public'; -import { MapsAppView } from './maps_app_view'; +import { MapApp } from './map_app'; import { getFlyoutDisplay, getIsFullScreen } from '../../../selectors/ui_selectors'; import { getFilters, @@ -99,5 +99,5 @@ function mapDispatchToProps(dispatch: ThunkDispatch { +export class MapApp extends React.Component { _globalSyncUnsubscribe: (() => void) | null = null; _globalSyncChangeMonitorSubscription: Subscription | null = null; _appSyncUnsubscribe: (() => void) | null = null; @@ -134,8 +139,6 @@ export class MapsAppView extends React.Component { this._initMap(); - this._setBreadcrumbs(); - this.props.onAppLeave((actions) => { if (this._hasUnsavedChanges()) { return actions.confirm(unsavedChangesWarning, unsavedChangesTitle); @@ -165,7 +168,11 @@ export class MapsAppView extends React.Component { } _hasUnsavedChanges = () => { - const savedLayerList = this.props.savedMap.getLayerList(); + if (!this.state.savedMap) { + return false; + } + + const savedLayerList = this.state.savedMap.getLayerList(); return !savedLayerList ? !_.isEqual(this.props.layerListConfigOnly, this.state.initialLayerListConfig) : // savedMap stores layerList as a JSON string using JSON.stringify. @@ -176,9 +183,9 @@ export class MapsAppView extends React.Component { !_.isEqual(JSON.parse(JSON.stringify(this.props.layerListConfigOnly)), savedLayerList); }; - _setBreadcrumbs = () => { + _setBreadcrumbs = (title: string) => { const breadcrumbs = getBreadcrumbs({ - title: this.props.savedMap.title, + title, getHasUnsavedChanges: this._hasUnsavedChanges, originatingApp: this.state.originatingApp, getAppNameFromId: this.props.stateTransfer.getAppNameFromId, @@ -255,13 +262,12 @@ export class MapsAppView extends React.Component { updateGlobalState(updatedGlobalState, !this.state.initialized); }; - _initMapAndLayerSettings() { + _initMapAndLayerSettings(savedMap: ISavedGisMap) { const globalState: MapsGlobalState = getGlobalState(); - const mapStateJSON = this.props.savedMap.mapStateJSON; let savedObjectFilters = []; - if (mapStateJSON) { - const mapState = JSON.parse(mapStateJSON); + if (savedMap.mapStateJSON) { + const mapState = JSON.parse(savedMap.mapStateJSON); if (mapState.filters) { savedObjectFilters = mapState.filters; } @@ -269,7 +275,7 @@ export class MapsAppView extends React.Component { const appFilters = this._appStateManager.getFilters() || []; const query = getInitialQuery({ - mapStateJSON, + mapStateJSON: savedMap.mapStateJSON, appState: this._appStateManager.getAppState(), }); if (query) { @@ -280,22 +286,19 @@ export class MapsAppView extends React.Component { filters: [..._.get(globalState, 'filters', []), ...appFilters, ...savedObjectFilters], query, time: getInitialTimeFilters({ - mapStateJSON, + mapStateJSON: savedMap.mapStateJSON, globalState, }), }); this._onRefreshConfigChange( getInitialRefreshConfig({ - mapStateJSON, + mapStateJSON: savedMap.mapStateJSON, globalState, }) ); - const layerList = getInitialLayers( - this.props.savedMap.layerListJSON, - getInitialLayersFromUrlParam() - ); + const layerList = getInitialLayers(savedMap.layerListJSON, getInitialLayersFromUrlParam()); this.props.replaceLayerList(layerList); this.setState({ initialLayerListConfig: copyPersistentState(layerList), @@ -345,13 +348,43 @@ export class MapsAppView extends React.Component { }); }; - _initMap() { - this._initMapAndLayerSettings(); + async _loadSavedMap(): Promise { + let savedMap: ISavedGisMap | null = null; + try { + savedMap = await getMapsSavedObjectLoader().get(this.props.savedMapId); + } catch (err) { + if (this._isMounted) { + getToasts().addWarning({ + title: i18n.translate('xpack.maps.loadMap.errorAttemptingToLoadSavedMap', { + defaultMessage: `Unable to load map`, + }), + text: `${err.message}`, + }); + goToSpecifiedPath('/'); + } + } + + return savedMap; + } + + async _initMap() { + const savedMap = await this._loadSavedMap(); + if (!this._isMounted || !savedMap) { + return; + } + + this._setBreadcrumbs(savedMap.title); + getCoreChrome().docTitle.change(savedMap.title); + if (this.props.savedMapId) { + getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, savedMap.id!); + } + + this._initMapAndLayerSettings(savedMap); this.props.clearUi(); - if (this.props.savedMap.mapStateJSON) { - const mapState = JSON.parse(this.props.savedMap.mapStateJSON); + if (savedMap.mapStateJSON) { + const mapState = JSON.parse(savedMap.mapStateJSON); this.props.setGotoWithCenter({ lat: mapState.center.lat, lon: mapState.center.lon, @@ -362,22 +395,22 @@ export class MapsAppView extends React.Component { } } - if (this.props.savedMap.uiStateJSON) { - const uiState = JSON.parse(this.props.savedMap.uiStateJSON); + if (savedMap.uiStateJSON) { + const uiState = JSON.parse(savedMap.uiStateJSON); this.props.setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN)); this.props.setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])); } - this.setState({ initialized: true }); + this.setState({ initialized: true, savedMap }); } _renderTopNav() { - if (this.props.isFullScreen) { + if (this.props.isFullScreen || !this.state.savedMap) { return null; } const topNavConfig = getTopNavConfig({ - savedMap: this.props.savedMap, + savedMap: this.state.savedMap, isOpenSettingsDisabled: this.props.isOpenSettingsDisabled, isSaveDisabled: this.props.isSaveDisabled, enableFullScreen: this.props.enableFullScreen, @@ -452,18 +485,22 @@ export class MapsAppView extends React.Component { }; render() { - return this.state.initialized ? ( + if (!this.state.initialized || !this.state.savedMap) { + return null; + } + + return (
{this._renderTopNav()}

{`screenTitle placeholder`}

- ) : null; + ); } } diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx b/x-pack/plugins/maps/public/routing/routes/map_app/top_nav_config.tsx similarity index 98% rename from x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx rename to x-pack/plugins/maps/public/routing/routes/map_app/top_nav_config.tsx index 917abebfb6b25..c60f44093541f 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routing/routes/map_app/top_nav_config.tsx @@ -21,7 +21,7 @@ import { showSaveModal, } from '../../../../../../../src/plugins/saved_objects/public'; import { MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants'; -import { goToSpecifiedPath } from '../../maps_router'; +import { goToSpecifiedPath } from '../../render_app'; import { ISavedGisMap } from '../../bootstrap/services/saved_gis_map'; import { EmbeddableStateTransfer } from '../../../../../../../src/plugins/embeddable/public'; @@ -43,7 +43,7 @@ export function getTopNavConfig({ enableFullScreen: () => void; openMapSettings: () => void; inspectorAdapters: Adapters; - setBreadcrumbs: () => void; + setBreadcrumbs: (title: string) => void; stateTransfer?: EmbeddableStateTransfer; originatingApp?: string; cutOriginatingAppConnection: () => void; @@ -104,7 +104,7 @@ export function getTopNavConfig({ }); getCoreChrome().docTitle.change(savedMap.title); - setBreadcrumbs(); + setBreadcrumbs(savedMap.title); goToSpecifiedPath(`/map/${savedObjectId}${window.location.hash}`); const newlyCreated = newCopyOnSave || isNewMap; diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/load_map_and_render.tsx b/x-pack/plugins/maps/public/routing/routes/maps_app/load_map_and_render.tsx deleted file mode 100644 index b980756daad20..0000000000000 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/load_map_and_render.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { Redirect } from 'react-router-dom'; -import { AppMountParameters } from 'kibana/public'; -import { EmbeddableStateTransfer } from 'src/plugins/embeddable/public'; -import { getCoreChrome, getToasts } from '../../../kibana_services'; -import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader'; -import { MapsAppView } from '.'; -import { ISavedGisMap } from '../../bootstrap/services/saved_gis_map'; - -interface Props { - savedMapId?: string; - onAppLeave: AppMountParameters['onAppLeave']; - stateTransfer: EmbeddableStateTransfer; - originatingApp?: string; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; -} - -interface State { - savedMap?: ISavedGisMap; - failedToLoad: boolean; -} - -export const LoadMapAndRender = class extends React.Component { - _isMounted: boolean = false; - state: State = { - savedMap: undefined, - failedToLoad: false, - }; - - componentDidMount() { - this._isMounted = true; - this._loadSavedMap(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - async _loadSavedMap() { - try { - const savedMap = await getMapsSavedObjectLoader().get(this.props.savedMapId); - if (this._isMounted) { - getCoreChrome().docTitle.change(savedMap.title); - if (this.props.savedMapId) { - getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, savedMap.id); - } - this.setState({ savedMap }); - } - } catch (err) { - if (this._isMounted) { - this.setState({ failedToLoad: true }); - getToasts().addWarning({ - title: i18n.translate('xpack.maps.loadMap.errorAttemptingToLoadSavedMap', { - defaultMessage: `Unable to load map`, - }), - text: `${err.message}`, - }); - } - } - } - - render() { - const { savedMap, failedToLoad } = this.state; - - if (failedToLoad) { - return ; - } - - return savedMap ? ( - - ) : null; - } -}; diff --git a/x-pack/plugins/maps/public/routing/state_syncing/app_sync.ts b/x-pack/plugins/maps/public/routing/state_syncing/app_sync.ts index b346822913bec..498442040681c 100644 --- a/x-pack/plugins/maps/public/routing/state_syncing/app_sync.ts +++ b/x-pack/plugins/maps/public/routing/state_syncing/app_sync.ts @@ -8,7 +8,7 @@ import { map } from 'rxjs/operators'; import { connectToQueryState, esFilters } from '../../../../../../src/plugins/data/public'; import { syncState, BaseStateContainer } from '../../../../../../src/plugins/kibana_utils/public'; import { getData } from '../../kibana_services'; -import { kbnUrlStateStorage } from '../maps_router'; +import { kbnUrlStateStorage } from '../render_app'; import { AppStateManager } from './app_state_manager'; export function startAppStateSyncing(appStateManager: AppStateManager) { diff --git a/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts b/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts index 1e779831c5e0c..3f370d9aa99b2 100644 --- a/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts +++ b/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts @@ -6,7 +6,7 @@ import { TimeRange, RefreshInterval, Filter } from 'src/plugins/data/public'; import { syncQueryStateWithUrl } from '../../../../../../src/plugins/data/public'; import { getData } from '../../kibana_services'; -import { kbnUrlStateStorage } from '../maps_router'; +import { kbnUrlStateStorage } from '../render_app'; export interface MapsGlobalState { time?: TimeRange; diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index e0f1b01dafa13..48aed19ea9050 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -456,16 +456,10 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat }, mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const options: { id: string; force?: boolean | undefined } = { + const { body } = await client.asInternalUser.ml.stopDataFrameAnalytics({ id: request.params.analyticsId, - }; - // @ts-expect-error TODO: update types - if (request.url?.query?.force !== undefined) { - // @ts-expect-error TODO: update types - options.force = request.url.query.force; - } - - const { body } = await client.asInternalUser.ml.stopDataFrameAnalytics(options); + force: request.query.force, + }); return response.ok({ body, }); diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index abd86d51fb6b6..d62adc62bc9aa 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -210,11 +210,18 @@ export class ReportingCore { } public getFakeRequest(baseRequest: object, spaceId: string | undefined, logger = this.logger) { + // @ts-expect-error _core isn't supposed to be accessed - remove once we upgrade to hapi v18 const fakeRequest = KibanaRequest.from({ path: '/', route: { settings: {} }, url: { href: '/' }, raw: { req: { url: '/' } }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, ...baseRequest, } as Hapi.Request); diff --git a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts index cee8a88000e29..cce002a0e6935 100644 --- a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -27,7 +27,7 @@ const getMockContext = () => const getMockRequest = () => ({ - url: { port: '5601', query: '', path: '/foo' }, + url: { port: '5601', search: '', pathname: '/foo' }, route: { path: '/foo', options: {} }, } as KibanaRequest); diff --git a/x-pack/plugins/security/server/audit/audit_events.test.ts b/x-pack/plugins/security/server/audit/audit_events.test.ts index 1978795f82a24..f153b9efb9d43 100644 --- a/x-pack/plugins/security/server/audit/audit_events.test.ts +++ b/x-pack/plugins/security/server/audit/audit_events.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { URL } from 'url'; import { EventOutcome, SavedObjectAction, @@ -192,11 +193,11 @@ describe('#httpRequestEvent', () => { }, "message": "User is requesting [/path] endpoint", "url": Object { - "domain": undefined, + "domain": "localhost", "path": "/path", "port": undefined, "query": undefined, - "scheme": undefined, + "scheme": "http:", }, } `); @@ -211,12 +212,7 @@ describe('#httpRequestEvent', () => { kibanaRequestState: { requestId: '123', requestUuid: '123e4567-e89b-12d3-a456-426614174000', - rewrittenUrl: { - path: '/original/path', - pathname: '/original/path', - query: 'query=param', - search: '?query=param', - }, + rewrittenUrl: new URL('http://localhost/original/path?query=param'), }, }), }) @@ -234,11 +230,11 @@ describe('#httpRequestEvent', () => { }, "message": "User is requesting [/original/path] endpoint", "url": Object { - "domain": undefined, + "domain": "localhost", "path": "/original/path", "port": undefined, "query": "query=param", - "scheme": undefined, + "scheme": "http:", }, } `); diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 1ff9da3a95ff4..d91c18bf82e02 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -105,10 +105,10 @@ export interface HttpRequestParams { } export function httpRequestEvent({ request }: HttpRequestParams): AuditEvent { - const { pathname, search } = request.rewrittenUrl ?? request.url; + const url = request.rewrittenUrl ?? request.url; return { - message: `User is requesting [${pathname}] endpoint`, + message: `User is requesting [${url.pathname}] endpoint`, event: { action: 'http_request', category: EventCategory.WEB, @@ -120,11 +120,11 @@ export function httpRequestEvent({ request }: HttpRequestParams): AuditEvent { }, }, url: { - domain: request.url.hostname, - path: pathname, - port: request.url.port ? parseInt(request.url.port, 10) : undefined, - query: search?.slice(1) || undefined, - scheme: request.url.protocol, + domain: url.hostname, + path: url.pathname, + port: url.port ? parseInt(url.port, 10) : undefined, + query: url.search ? url.search.slice(1) : undefined, + scheme: url.protocol, }, }; } diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 3b587182c491c..80aeb4f8b2959 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -333,7 +333,7 @@ export class Authenticator { this.logger.debug('Redirecting request to Login Selector.'); return AuthenticationResult.redirectTo( `${this.options.basePath.serverBasePath}/login?next=${encodeURIComponent( - `${this.options.basePath.get(request)}${request.url.path}` + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` )}` ); } @@ -728,7 +728,7 @@ export class Authenticator { preAccessRedirectURL = `${preAccessRedirectURL}?next=${encodeURIComponent( authenticationResult.redirectURL || redirectURL || - `${this.options.basePath.get(request)}${request.url.path}` + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` )}`; } else if (redirectURL && !authenticationResult.redirectURL) { preAccessRedirectURL = redirectURL; diff --git a/x-pack/plugins/security/server/authentication/providers/basic.test.ts b/x-pack/plugins/security/server/authentication/providers/basic.test.ts index 2481844abb389..87002ebed5672 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.test.ts @@ -101,13 +101,13 @@ describe('BasicAuthenticationProvider', () => { await expect( provider.authenticate( httpServerMock.createKibanaRequest({ - path: '/s/foo/some-path # that needs to be encoded', + path: '/s/foo/some path that needs to be encoded', }), null ) ).resolves.toEqual( AuthenticationResult.redirectTo( - '/mock-server-basepath/login?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded' + '/mock-server-basepath/login?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome%2520path%2520that%2520needs%2520to%2520be%2520encoded' ) ); }); diff --git a/x-pack/plugins/security/server/authentication/providers/basic.ts b/x-pack/plugins/security/server/authentication/providers/basic.ts index 35ab2d242659a..28b671346ee7f 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.ts @@ -90,7 +90,9 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { * @param [state] Optional state object associated with the provider. */ public async authenticate(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + this.logger.debug( + `Trying to authenticate user request to ${request.url.pathname}${request.url.search}.` + ); if (HTTPAuthorizationHeader.parseFromRequest(request) != null) { this.logger.debug('Cannot authenticate requests with `Authorization` header.'); @@ -106,7 +108,9 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Redirecting request to Login page.'); const basePath = this.options.basePath.get(request); return AuthenticationResult.redirectTo( - `${basePath}/login?next=${encodeURIComponent(`${basePath}${request.url.path}`)}` + `${basePath}/login?next=${encodeURIComponent( + `${basePath}${request.url.pathname}${request.url.search}` + )}` ); } @@ -119,7 +123,7 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { * @param [state] Optional state object associated with the provider. */ public async logout(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to log user out via ${request.url.path}.`); + this.logger.debug(`Trying to log user out via ${request.url.pathname}${request.url.search}.`); // Having a `null` state means that provider was specifically called to do a logout, but when // session isn't defined then provider is just being probed whether or not it can perform logout. diff --git a/x-pack/plugins/security/server/authentication/providers/http.ts b/x-pack/plugins/security/server/authentication/providers/http.ts index 3e33a52cbbc6b..933685d68978f 100644 --- a/x-pack/plugins/security/server/authentication/providers/http.ts +++ b/x-pack/plugins/security/server/authentication/providers/http.ts @@ -56,7 +56,9 @@ export class HTTPAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. */ public async authenticate(request: KibanaRequest) { - this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + this.logger.debug( + `Trying to authenticate user request to ${request.url.pathname}${request.url.search}.` + ); const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request); if (authorizationHeader == null) { @@ -72,12 +74,12 @@ export class HTTPAuthenticationProvider extends BaseAuthenticationProvider { try { const user = await this.getUser(request); this.logger.debug( - `Request to ${request.url.path} has been authenticated via authorization header with "${authorizationHeader.scheme}" scheme.` + `Request to ${request.url.pathname}${request.url.search} has been authenticated via authorization header with "${authorizationHeader.scheme}" scheme.` ); return AuthenticationResult.succeeded(user); } catch (err) { this.logger.debug( - `Failed to authenticate request to ${request.url.path} via authorization header with "${authorizationHeader.scheme}" scheme: ${err.message}` + `Failed to authenticate request to ${request.url.pathname}${request.url.search} via authorization header with "${authorizationHeader.scheme}" scheme: ${err.message}` ); return AuthenticationResult.failed(err); } diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 5b593851cc2f2..d7de71f4da9ed 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -65,7 +65,9 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { * @param [state] Optional state object associated with the provider. */ public async authenticate(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + this.logger.debug( + `Trying to authenticate user request to ${request.url.pathname}${request.url.search}.` + ); const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request); if (authorizationHeader && authorizationHeader.scheme.toLowerCase() !== 'negotiate') { @@ -100,7 +102,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { * @param state State value previously stored by the provider. */ public async logout(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to log user out via ${request.url.path}.`); + this.logger.debug(`Trying to log user out via ${request.url.pathname}${request.url.search}.`); // Having a `null` state means that provider was specifically called to do a logout, but when // session isn't defined then provider is just being probed whether or not it can perform logout. diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index 75c909cdcd94b..9570c59f8ea1d 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -166,7 +166,9 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { * @param [state] Optional state object associated with the provider. */ public async authenticate(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + this.logger.debug( + `Trying to authenticate user request to ${request.url.pathname}${request.url.search}.` + ); if (HTTPAuthorizationHeader.parseFromRequest(request) != null) { this.logger.debug('Cannot authenticate requests with `Authorization` header.'); @@ -418,7 +420,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { * @param state State value previously stored by the provider. */ public async logout(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to log user out via ${request.url.path}.`); + this.logger.debug(`Trying to log user out via ${request.url.pathname}${request.url.search}.`); // Having a `null` state means that provider was specifically called to do a logout, but when // session isn't defined then provider is just being probed whether or not it can perform logout. @@ -477,7 +479,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { `${ this.options.basePath.serverBasePath }/internal/security/capture-url?next=${encodeURIComponent( - `${this.options.basePath.get(request)}${request.url.path}` + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` )}&providerType=${encodeURIComponent(this.type)}&providerName=${encodeURIComponent( this.options.name )}`, diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index f3cc21500df26..6dcb448e08150 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -61,7 +61,9 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { * @param [state] Optional state object associated with the provider. */ public async authenticate(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + this.logger.debug( + `Trying to authenticate user request to ${request.url.pathname}${request.url.search}.` + ); if (HTTPAuthorizationHeader.parseFromRequest(request) != null) { this.logger.debug('Cannot authenticate requests with `Authorization` header.'); @@ -105,7 +107,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { * @param state State value previously stored by the provider. */ public async logout(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to log user out via ${request.url.path}.`); + this.logger.debug(`Trying to log user out via ${request.url.pathname}${request.url.search}.`); // Having a `null` state means that provider was specifically called to do a logout, but when // session isn't defined then provider is just being probed whether or not it can perform logout. diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index cf6772332b8b6..59a1782c1f1fd 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -193,7 +193,9 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { * @param [state] Optional state object associated with the provider. */ public async authenticate(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + this.logger.debug( + `Trying to authenticate user request to ${request.url.pathname}${request.url.search}` + ); if (HTTPAuthorizationHeader.parseFromRequest(request) != null) { this.logger.debug('Cannot authenticate requests with `Authorization` header.'); @@ -232,7 +234,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { * @param state State value previously stored by the provider. */ public async logout(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to log user out via ${request.url.path}.`); + this.logger.debug(`Trying to log user out via ${request.url.pathname}${request.url.search}.`); // Normally when there is no active session in Kibana, `logout` method shouldn't do anything // and user will eventually be redirected to the home page to log in. But when SAML SLO is @@ -631,7 +633,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { `${ this.options.basePath.serverBasePath }/internal/security/capture-url?next=${encodeURIComponent( - `${this.options.basePath.get(request)}${request.url.path}` + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` )}&providerType=${encodeURIComponent(this.type)}&providerName=${encodeURIComponent( this.options.name )}`, diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 0264edf4fc082..ffb1c89b24e47 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -173,13 +173,13 @@ describe('TokenAuthenticationProvider', () => { await expect( provider.authenticate( httpServerMock.createKibanaRequest({ - path: '/s/foo/some-path # that needs to be encoded', + path: '/s/foo/some path that needs to be encoded', }), null ) ).resolves.toEqual( AuthenticationResult.redirectTo( - '/mock-server-basepath/login?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded' + '/mock-server-basepath/login?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome%2520path%2520that%2520needs%2520to%2520be%2520encoded' ) ); }); diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index 869fd69173e2e..7dace488bc95a 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -92,7 +92,9 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { * @param [state] Optional state object associated with the provider. */ public async authenticate(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + this.logger.debug( + `Trying to authenticate user request to ${request.url.pathname}${request.url.search}.` + ); if (HTTPAuthorizationHeader.parseFromRequest(request) != null) { this.logger.debug('Cannot authenticate requests with `Authorization` header.'); @@ -126,7 +128,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { * @param state State value previously stored by the provider. */ public async logout(request: KibanaRequest, state?: ProviderState | null) { - this.logger.debug(`Trying to log user out via ${request.url.path}.`); + this.logger.debug(`Trying to log user out via ${request.url.pathname}${request.url.search}.`); // Having a `null` state means that provider was specifically called to do a logout, but when // session isn't defined then provider is just being probed whether or not it can perform logout. @@ -241,7 +243,9 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. */ private getLoginPageURL(request: KibanaRequest) { - const nextURL = encodeURIComponent(`${this.options.basePath.get(request)}${request.url.path}`); + const nextURL = encodeURIComponent( + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` + ); return `${this.options.basePath.get(request)}/login?next=${nextURL}`; } } diff --git a/x-pack/plugins/security/server/authorization/api_authorization.ts b/x-pack/plugins/security/server/authorization/api_authorization.ts index 813ed8d064d94..9cf090ab271ae 100644 --- a/x-pack/plugins/security/server/authorization/api_authorization.ts +++ b/x-pack/plugins/security/server/authorization/api_authorization.ts @@ -33,11 +33,13 @@ export function initAPIAuthorization( // we've actually authorized the request if (checkPrivilegesResponse.hasAllRequested) { - logger.debug(`User authorized for "${request.url.path}"`); + logger.debug(`User authorized for "${request.url.pathname}${request.url.search}"`); return toolkit.next(); } - logger.warn(`User not authorized for "${request.url.path}": responding with 403`); + logger.warn( + `User not authorized for "${request.url.pathname}${request.url.search}": responding with 403` + ); return response.forbidden(); }); } diff --git a/x-pack/plugins/security/server/authorization/authorization_service.tsx b/x-pack/plugins/security/server/authorization/authorization_service.tsx index 9547295af4dfb..a45bca90d8b56 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.tsx +++ b/x-pack/plugins/security/server/authorization/authorization_service.tsx @@ -168,7 +168,7 @@ export class AuthorizationService { http.registerOnPreResponse((request, preResponse, toolkit) => { if (preResponse.statusCode === 403 && canRedirectRequest(request)) { const basePath = http.basePath.get(request); - const next = `${basePath}${request.url.path}`; + const next = `${basePath}${request.url.pathname}${request.url.search}`; const regularBundlePath = `${basePath}/${buildNumber}/bundles`; const logoutUrl = http.basePath.prepend( diff --git a/x-pack/plugins/security/server/routes/authentication/oidc.ts b/x-pack/plugins/security/server/routes/authentication/oidc.ts index 5d8a7ae7bdfea..7eaa619b330e0 100644 --- a/x-pack/plugins/security/server/routes/authentication/oidc.ts +++ b/x-pack/plugins/security/server/routes/authentication/oidc.ts @@ -135,7 +135,7 @@ export function defineOIDCRoutes({ loginAttempt = { type: OIDCLogin.LoginWithAuthorizationCodeFlow, // We pass the path only as we can't be sure of the full URL and Elasticsearch doesn't need it anyway. - authenticationResponseURI: request.url.path!, + authenticationResponseURI: request.url.pathname + request.url.search, }; } else if (request.query.iss) { logger.warn( diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index fee3adbb19f97..b90a44be7aade 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -100,7 +100,7 @@ describe('Login view routes', () => { auth: { isAuthenticated: true }, }); (request as any).url = new URL( - `${request.url.path}${request.url.search}`, + `${request.url.pathname}${request.url.search}`, 'https://kibana.co' ); license.getFeatures.mockReturnValue({ showLogin: true } as any); @@ -114,7 +114,7 @@ describe('Login view routes', () => { // Redirect if `showLogin` is `false` even if user is not authenticated. request = httpServerMock.createKibanaRequest({ query, auth: { isAuthenticated: false } }); (request as any).url = new URL( - `${request.url.path}${request.url.search}`, + `${request.url.pathname}${request.url.search}`, 'https://kibana.co' ); license.getFeatures.mockReturnValue({ showLogin: false } as any); diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index f4ff5a8bddb74..cba63412bf502 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -155,6 +155,17 @@ describe('Session index', () => { await sessionIndex.initialize(); }); + + it('works properly after failure', async () => { + const unexpectedError = new Error('Uh! Oh!'); + mockClusterClient.callAsInternalUser.mockImplementationOnce(() => + Promise.reject(unexpectedError) + ); + mockClusterClient.callAsInternalUser.mockImplementationOnce(() => Promise.resolve(true)); + + await expect(sessionIndex.initialize()).rejects.toBe(unexpectedError); + await expect(sessionIndex.initialize()).resolves.toBe(undefined); + }); }); describe('cleanUp', () => { diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 191e71f14d66d..ee503acc0d3a4 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -276,7 +276,7 @@ export class SessionIndex { } const sessionIndexTemplateName = `${this.options.kibanaIndexName}_security_session_index_template_${SESSION_INDEX_TEMPLATE_VERSION}`; - return (this.indexInitialization = new Promise(async (resolve) => { + return (this.indexInitialization = new Promise(async (resolve, reject) => { // Check if required index template exists. let indexTemplateExists = false; try { @@ -288,7 +288,7 @@ export class SessionIndex { this.options.logger.error( `Failed to check if session index template exists: ${err.message}` ); - throw err; + return reject(err); } // Create index template if it doesn't exist. @@ -303,7 +303,7 @@ export class SessionIndex { this.options.logger.debug('Successfully created session index template.'); } catch (err) { this.options.logger.error(`Failed to create session index template: ${err.message}`); - throw err; + return reject(err); } } @@ -316,7 +316,7 @@ export class SessionIndex { }); } catch (err) { this.options.logger.error(`Failed to check if session index exists: ${err.message}`); - throw err; + return reject(err); } // Create index if it doesn't exist. @@ -334,13 +334,14 @@ export class SessionIndex { this.options.logger.debug('Session index already exists.'); } else { this.options.logger.error(`Failed to create session index: ${err.message}`); - throw err; + return reject(err); } } } // Notify any consumers that are awaiting on this promise and immediately reset it. resolve(); + }).finally(() => { this.indexInitialization = undefined; })); } diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts index e3a724e153688..1aa2011a15b35 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts @@ -28,7 +28,7 @@ export function initSpacesOnPostAuthRequestInterceptor({ http.registerOnPostAuth(async (request, response, toolkit) => { const serverBasePath = http.basePath.serverBasePath; - const path = request.url.pathname!; + const path = request.url.pathname; const spaceId = spacesService.getSpaceId(request); diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts index 6408803c2114b..a3335b1e075f2 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts @@ -9,8 +9,6 @@ import { LifecycleResponseFactory, CoreSetup, } from 'src/core/server'; -import { format } from 'url'; -import { modifyUrl } from '../utils/url'; import { getSpaceIdFromPath } from '../../../common'; export interface OnRequestInterceptorDeps { @@ -34,16 +32,9 @@ export function initSpacesOnRequestInterceptor({ http }: OnRequestInterceptorDep http.basePath.set(request, reqBasePath); - const newLocation = (path && path.substr(reqBasePath.length)) || '/'; + const newPathname = path.substr(reqBasePath.length) || '/'; - const newUrl = modifyUrl(format(request.url), (parts) => { - return { - ...parts, - pathname: newLocation, - }; - }); - - return toolkit.rewriteUrl(newUrl); + return toolkit.rewriteUrl(`${newPathname}${request.url.search}`); } return toolkit.next(); diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts index b48bf971d0c1b..d1e1d81134940 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts @@ -58,7 +58,7 @@ const createService = async (serverBasePath: string = '') => { serverBasePath, } as HttpServiceSetup['basePath']; httpSetup.basePath.get = jest.fn().mockImplementation((request: KibanaRequest) => { - const { spaceId } = getSpaceIdFromPath(request.url.path); + const { spaceId } = getSpaceIdFromPath(request.url.pathname); if (spaceId !== DEFAULT_SPACE_ID) { return `/s/${spaceId}`; @@ -83,7 +83,7 @@ describe('SpacesService', () => { const spacesServiceSetup = await createService(); const request: KibanaRequest = { - url: { path: '/app/kibana' }, + url: { pathname: '/app/kibana' }, } as KibanaRequest; expect(spacesServiceSetup.getSpaceId(request)).toEqual(DEFAULT_SPACE_ID); @@ -93,7 +93,7 @@ describe('SpacesService', () => { const spacesServiceSetup = await createService(); const request: KibanaRequest = { - url: { path: '/s/foo/app/kibana' }, + url: { pathname: '/s/foo/app/kibana' }, } as KibanaRequest; expect(spacesServiceSetup.getSpaceId(request)).toEqual('foo'); @@ -140,7 +140,7 @@ describe('SpacesService', () => { const spacesServiceSetup = await createService(); const request: KibanaRequest = { - url: { path: '/app/kibana' }, + url: { pathname: '/app/kibana' }, } as KibanaRequest; expect(spacesServiceSetup.isInDefaultSpace(request)).toEqual(true); @@ -150,7 +150,7 @@ describe('SpacesService', () => { const spacesServiceSetup = await createService(); const request: KibanaRequest = { - url: { path: '/s/foo/app/kibana' }, + url: { pathname: '/s/foo/app/kibana' }, } as KibanaRequest; expect(spacesServiceSetup.isInDefaultSpace(request)).toEqual(false); diff --git a/x-pack/plugins/task_manager/server/monitoring/index.ts b/x-pack/plugins/task_manager/server/monitoring/index.ts index 8e71ce2519a7c..0a4c8c56a5a79 100644 --- a/x-pack/plugins/task_manager/server/monitoring/index.ts +++ b/x-pack/plugins/task_manager/server/monitoring/index.ts @@ -28,12 +28,20 @@ export { export function createMonitoringStats( taskPollingLifecycle: TaskPollingLifecycle, taskStore: TaskStore, + elasticsearchAndSOAvailability$: Observable, config: TaskManagerConfig, managedConfig: ManagedConfiguration, logger: Logger ): Observable { return createMonitoringStatsStream( - createAggregators(taskPollingLifecycle, taskStore, config, managedConfig, logger), + createAggregators( + taskPollingLifecycle, + taskStore, + elasticsearchAndSOAvailability$, + config, + managedConfig, + logger + ), config ); } diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts index 374660a257c59..524afb8d78e21 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts @@ -63,6 +63,7 @@ export interface RawMonitoringStats { export function createAggregators( taskPollingLifecycle: TaskPollingLifecycle, taskStore: TaskStore, + elasticsearchAndSOAvailability$: Observable, config: TaskManagerConfig, managedConfig: ManagedConfiguration, logger: Logger @@ -72,6 +73,7 @@ export function createAggregators( createTaskRunAggregator(taskPollingLifecycle, config.monitored_stats_running_average_window), createWorkloadAggregator( taskStore, + elasticsearchAndSOAvailability$, config.monitored_aggregated_stats_refresh_rate, config.poll_interval, logger diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index d9af3307e75cb..cb6e48530b027 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -17,6 +17,8 @@ import { ESSearchResponse } from '../../../apm/typings/elasticsearch'; import { AggregationResultOf } from '../../../apm/typings/elasticsearch/aggregations'; import { times } from 'lodash'; import { taskStoreMock } from '../task_store.mock'; +import { of, Subject } from 'rxjs'; +import { sleep } from '../test_utils'; type MockESResult = ESSearchResponse< ConcreteTaskInstance, @@ -75,6 +77,7 @@ describe('Workload Statistics Aggregator', () => { const workloadAggregator = createWorkloadAggregator( taskStore, + of(true), 10, 3000, loggingSystemMock.create().get() @@ -231,6 +234,7 @@ describe('Workload Statistics Aggregator', () => { const workloadAggregator = createWorkloadAggregator( taskStore, + of(true), 10, 3000, loggingSystemMock.create().get() @@ -252,12 +256,51 @@ describe('Workload Statistics Aggregator', () => { }); }); + test('skips summary of the workload when services are unavailable', async () => { + const taskStore = taskStoreMock.create({}); + taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); + + const availability$ = new Subject(); + + const workloadAggregator = createWorkloadAggregator( + taskStore, + availability$, + 10, + 3000, + loggingSystemMock.create().get() + ); + + return new Promise(async (resolve) => { + workloadAggregator.pipe(first()).subscribe((result) => { + expect(result.key).toEqual('workload'); + expect(result.value).toMatchObject({ + count: 4, + task_types: { + actions_telemetry: { count: 2, status: { idle: 2 } }, + alerting_telemetry: { count: 1, status: { idle: 1 } }, + session_cleanup: { count: 1, status: { idle: 1 } }, + }, + }); + resolve(); + }); + + availability$.next(false); + + await sleep(10); + expect(taskStore.aggregate).not.toHaveBeenCalled(); + await sleep(10); + expect(taskStore.aggregate).not.toHaveBeenCalled(); + availability$.next(true); + }); + }); + test('returns a count of the overdue workload', async () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); const workloadAggregator = createWorkloadAggregator( taskStore, + of(true), 10, 3000, loggingSystemMock.create().get() @@ -280,6 +323,7 @@ describe('Workload Statistics Aggregator', () => { const workloadAggregator = createWorkloadAggregator( taskStore, + of(true), 10, 3000, loggingSystemMock.create().get() @@ -307,6 +351,7 @@ describe('Workload Statistics Aggregator', () => { const workloadAggregator = createWorkloadAggregator( taskStore, + of(true), 60 * 1000, 3000, loggingSystemMock.create().get() @@ -344,6 +389,7 @@ describe('Workload Statistics Aggregator', () => { const workloadAggregator = createWorkloadAggregator( taskStore, + of(true), 15 * 60 * 1000, 3000, loggingSystemMock.create().get() @@ -392,7 +438,7 @@ describe('Workload Statistics Aggregator', () => { }) ); const logger = loggingSystemMock.create().get(); - const workloadAggregator = createWorkloadAggregator(taskStore, 10, 3000, logger); + const workloadAggregator = createWorkloadAggregator(taskStore, of(true), 10, 3000, logger); return new Promise((resolve, reject) => { workloadAggregator.pipe(take(2), bufferCount(2)).subscribe((results) => { diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index fe70f24684ad9..17448ea412ae6 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { timer } from 'rxjs'; -import { mergeMap, map, catchError } from 'rxjs/operators'; +import { combineLatest, Observable, timer } from 'rxjs'; +import { mergeMap, map, filter, catchError } from 'rxjs/operators'; import { Logger } from 'src/core/server'; import { JsonObject } from 'src/plugins/kibana_utils/common'; import { keyBy, mapValues } from 'lodash'; @@ -94,6 +94,7 @@ const MAX_SHCEDULE_DENSITY_BUCKETS = 50; export function createWorkloadAggregator( taskStore: TaskStore, + elasticsearchAndSOAvailability$: Observable, refreshInterval: number, pollInterval: number, logger: Logger @@ -105,7 +106,8 @@ export function createWorkloadAggregator( MAX_SHCEDULE_DENSITY_BUCKETS ); - return timer(0, refreshInterval).pipe( + return combineLatest([timer(0, refreshInterval), elasticsearchAndSOAvailability$]).pipe( + filter(([, areElasticsearchAndSOAvailable]) => areElasticsearchAndSOAvailable), mergeMap(() => taskStore.aggregate({ aggs: { diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts index 8388468164a4f..9a1d83f6195ab 100644 --- a/x-pack/plugins/task_manager/server/plugin.test.ts +++ b/x-pack/plugins/task_manager/server/plugin.test.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TaskManagerPlugin } from './plugin'; +import { TaskManagerPlugin, getElasticsearchAndSOAvailability } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; import { TaskManagerConfig } from './config'; +import { Subject } from 'rxjs'; +import { bufferCount, take } from 'rxjs/operators'; +import { CoreStatus, ServiceStatusLevels } from 'src/core/server'; describe('TaskManagerPlugin', () => { describe('setup', () => { @@ -88,4 +91,99 @@ describe('TaskManagerPlugin', () => { ); }); }); + + describe('getElasticsearchAndSOAvailability', () => { + test('returns true when both services are available', async () => { + const core$ = new Subject(); + + const availability = getElasticsearchAndSOAvailability(core$) + .pipe(take(1), bufferCount(1)) + .toPromise(); + + core$.next(mockCoreStatusAvailability({ elasticsearch: true, savedObjects: true })); + + expect(await availability).toEqual([true]); + }); + + test('returns false when both services are unavailable', async () => { + const core$ = new Subject(); + + const availability = getElasticsearchAndSOAvailability(core$) + .pipe(take(1), bufferCount(1)) + .toPromise(); + + core$.next(mockCoreStatusAvailability({ elasticsearch: false, savedObjects: false })); + + expect(await availability).toEqual([false]); + }); + + test('returns false when one service is unavailable but the other is available', async () => { + const core$ = new Subject(); + + const availability = getElasticsearchAndSOAvailability(core$) + .pipe(take(1), bufferCount(1)) + .toPromise(); + + core$.next(mockCoreStatusAvailability({ elasticsearch: true, savedObjects: false })); + + expect(await availability).toEqual([false]); + }); + + test('shift back and forth between values as status changes', async () => { + const core$ = new Subject(); + + const availability = getElasticsearchAndSOAvailability(core$) + .pipe(take(3), bufferCount(3)) + .toPromise(); + + core$.next(mockCoreStatusAvailability({ elasticsearch: true, savedObjects: false })); + + core$.next(mockCoreStatusAvailability({ elasticsearch: true, savedObjects: true })); + + core$.next(mockCoreStatusAvailability({ elasticsearch: false, savedObjects: false })); + + expect(await availability).toEqual([false, true, false]); + }); + + test(`skips values when the status hasn't changed`, async () => { + const core$ = new Subject(); + + const availability = getElasticsearchAndSOAvailability(core$) + .pipe(take(3), bufferCount(3)) + .toPromise(); + + core$.next(mockCoreStatusAvailability({ elasticsearch: true, savedObjects: false })); + + // still false, so shouldn't emit a second time + core$.next(mockCoreStatusAvailability({ elasticsearch: false, savedObjects: true })); + + core$.next(mockCoreStatusAvailability({ elasticsearch: true, savedObjects: true })); + + // shouldn't emit as already true + core$.next(mockCoreStatusAvailability({ elasticsearch: true, savedObjects: true })); + + core$.next(mockCoreStatusAvailability({ elasticsearch: false, savedObjects: false })); + + expect(await availability).toEqual([false, true, false]); + }); + }); }); + +function mockCoreStatusAvailability({ + elasticsearch, + savedObjects, +}: { + elasticsearch: boolean; + savedObjects: boolean; +}) { + return { + elasticsearch: { + level: elasticsearch ? ServiceStatusLevels.available : ServiceStatusLevels.unavailable, + summary: '', + }, + savedObjects: { + level: savedObjects ? ServiceStatusLevels.available : ServiceStatusLevels.unavailable, + summary: '', + }, + }; +} diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 0e7abb817490a..70688cd169d7e 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -3,9 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext, Plugin, CoreSetup, Logger, CoreStart } from 'src/core/server'; -import { combineLatest, Subject } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { combineLatest, Observable, Subject } from 'rxjs'; +import { first, map, distinctUntilChanged } from 'rxjs/operators'; +import { + PluginInitializerContext, + Plugin, + CoreSetup, + Logger, + CoreStart, + ServiceStatusLevels, + CoreStatus, +} from '../../../../src/core/server'; import { TaskDefinition } from './task'; import { TaskPollingLifecycle } from './polling_lifecycle'; import { TaskManagerConfig } from './config'; @@ -37,6 +45,7 @@ export class TaskManagerPlugin private logger: Logger; private definitions: TaskTypeDictionary; private middleware: Middleware = createInitialMiddleware(); + private elasticsearchAndSOAvailability$?: Observable; private monitoringStats$ = new Subject(); constructor(private readonly initContext: PluginInitializerContext) { @@ -51,6 +60,8 @@ export class TaskManagerPlugin .pipe(first()) .toPromise(); + this.elasticsearchAndSOAvailability$ = getElasticsearchAndSOAvailability(core.status.core$); + setupSavedObjects(core.savedObjects, this.config); this.taskManagerId = this.initContext.env.instanceUuid; @@ -115,19 +126,20 @@ export class TaskManagerPlugin startingPollInterval: this.config!.poll_interval, }); - const taskPollingLifecycle = new TaskPollingLifecycle({ + this.taskPollingLifecycle = new TaskPollingLifecycle({ config: this.config!, definitions: this.definitions, logger: this.logger, taskStore, middleware: this.middleware, + elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, ...managedConfiguration, }); - this.taskPollingLifecycle = taskPollingLifecycle; createMonitoringStats( - taskPollingLifecycle, + this.taskPollingLifecycle, taskStore, + this.elasticsearchAndSOAvailability$!, this.config!, managedConfiguration, this.logger @@ -137,12 +149,9 @@ export class TaskManagerPlugin logger: this.logger, taskStore, middleware: this.middleware, - taskPollingLifecycle, + taskPollingLifecycle: this.taskPollingLifecycle, }); - // start polling for work - taskPollingLifecycle.start(); - return { fetch: (opts: SearchOpts): Promise => taskStore.fetch(opts), get: (id: string) => taskStore.get(id), @@ -153,12 +162,6 @@ export class TaskManagerPlugin }; } - public stop() { - if (this.taskPollingLifecycle) { - this.taskPollingLifecycle.stop(); - } - } - /** * Ensures task manager hasn't started * @@ -171,3 +174,16 @@ export class TaskManagerPlugin } } } + +export function getElasticsearchAndSOAvailability( + core$: Observable +): Observable { + return core$.pipe( + map( + ({ elasticsearch, savedObjects }) => + elasticsearch.level === ServiceStatusLevels.available && + savedObjects.level === ServiceStatusLevels.available + ), + distinctUntilChanged() + ); +} diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts index 9df1e06165bc6..286e29194d6e6 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts @@ -10,7 +10,6 @@ import { of, Observable } from 'rxjs'; export const taskPollingLifecycleMock = { create(opts: { isStarted?: boolean; events$?: Observable }) { return ({ - start: jest.fn(), attemptToRun: jest.fn(), get isStarted() { return opts.isStarted ?? true; @@ -18,7 +17,6 @@ export const taskPollingLifecycleMock = { get events() { return opts.events$ ?? of(); }, - stop: jest.fn(), } as unknown) as jest.Mocked; }, }; diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts index 5f2e774177fd4..0f807976970cf 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts @@ -6,7 +6,7 @@ import _ from 'lodash'; import sinon from 'sinon'; -import { of } from 'rxjs'; +import { of, Subject } from 'rxjs'; import { TaskPollingLifecycle, claimAvailableTasks } from './polling_lifecycle'; import { createInitialMiddleware } from './lib/middleware'; @@ -55,15 +55,64 @@ describe('TaskPollingLifecycle', () => { afterEach(() => clock.restore()); describe('start', () => { - test('begins polling once start is called', () => { - const taskManager = new TaskPollingLifecycle(taskManagerOpts); + test('begins polling once the ES and SavedObjects services are available', () => { + const elasticsearchAndSOAvailability$ = new Subject(); + new TaskPollingLifecycle({ + elasticsearchAndSOAvailability$, + ...taskManagerOpts, + }); + + clock.tick(150); + expect(mockTaskStore.claimAvailableTasks).not.toHaveBeenCalled(); + + elasticsearchAndSOAvailability$.next(true); + + clock.tick(150); + expect(mockTaskStore.claimAvailableTasks).toHaveBeenCalled(); + }); + }); + + describe('stop', () => { + test('stops polling once the ES and SavedObjects services become unavailable', () => { + const elasticsearchAndSOAvailability$ = new Subject(); + new TaskPollingLifecycle({ + elasticsearchAndSOAvailability$, + ...taskManagerOpts, + }); + + elasticsearchAndSOAvailability$.next(true); + + clock.tick(150); + expect(mockTaskStore.claimAvailableTasks).toHaveBeenCalled(); + elasticsearchAndSOAvailability$.next(false); + + mockTaskStore.claimAvailableTasks.mockClear(); clock.tick(150); expect(mockTaskStore.claimAvailableTasks).not.toHaveBeenCalled(); + }); + + test('restarts polling once the ES and SavedObjects services become available again', () => { + const elasticsearchAndSOAvailability$ = new Subject(); + new TaskPollingLifecycle({ + elasticsearchAndSOAvailability$, + ...taskManagerOpts, + }); + + elasticsearchAndSOAvailability$.next(true); - taskManager.start(); + clock.tick(150); + expect(mockTaskStore.claimAvailableTasks).toHaveBeenCalled(); + elasticsearchAndSOAvailability$.next(false); + mockTaskStore.claimAvailableTasks.mockClear(); clock.tick(150); + + expect(mockTaskStore.claimAvailableTasks).not.toHaveBeenCalled(); + + elasticsearchAndSOAvailability$.next(true); + clock.tick(150); + expect(mockTaskStore.claimAvailableTasks).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index ba19cb63fffa2..ccba750401f28 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -48,6 +48,7 @@ export type TaskPollingLifecycleOpts = { taskStore: TaskStore; config: TaskManagerConfig; middleware: Middleware; + elasticsearchAndSOAvailability$: Observable; } & ManagedConfiguration; export type TaskLifecycleEvent = @@ -72,8 +73,6 @@ export class TaskPollingLifecycle { private events$ = new Subject(); // all on-demand requests we wish to pipe into the poller private claimRequests$ = new Subject>(); - // the task poller that polls for work on fixed intervals and on demand - private poller$: Observable>>; // our subscription to the poller private pollingSubscription: Subscription = Subscription.EMPTY; @@ -84,36 +83,50 @@ export class TaskPollingLifecycle { * enabling the task manipulation methods, and beginning the background polling * mechanism. */ - constructor(opts: TaskPollingLifecycleOpts) { - const { logger, middleware, maxWorkersConfiguration$, pollIntervalConfiguration$ } = opts; + constructor({ + logger, + middleware, + maxWorkersConfiguration$, + pollIntervalConfiguration$, + // Elasticsearch and SavedObjects availability status + elasticsearchAndSOAvailability$, + config, + taskStore, + definitions, + }: TaskPollingLifecycleOpts) { this.logger = logger; this.middleware = middleware; + this.definitions = definitions; + this.store = taskStore; - this.definitions = opts.definitions; - this.store = opts.taskStore; // pipe store events into the lifecycle event stream this.store.events.subscribe((event) => this.events$.next(event)); this.bufferedStore = new BufferedTaskStore(this.store, { - bufferMaxOperations: opts.config.max_workers, - logger: this.logger, + bufferMaxOperations: config.max_workers, + logger, }); this.pool = new TaskPool({ - logger: this.logger, + logger, maxWorkers$: maxWorkersConfiguration$, }); const { max_poll_inactivity_cycles: maxPollInactivityCycles, poll_interval: pollInterval, - } = opts.config; - this.poller$ = createObservableMonitor>, Error>( + } = config; + + // the task poller that polls for work on fixed intervals and on demand + const poller$: Observable + >> = createObservableMonitor>, Error>( () => createTaskPoller({ - logger: this.logger, + logger, pollInterval$: pollIntervalConfiguration$, - bufferCapacity: opts.config.request_capacity, + bufferCapacity: config.request_capacity, getCapacity: () => this.pool.availableWorkers, pollRequests$: this.claimRequests$, work: this.pollForWork, @@ -133,10 +146,20 @@ export class TaskPollingLifecycle { // operation than just timing out the `work` internally) inactivityTimeout: pollInterval * (maxPollInactivityCycles + 1), onError: (error) => { - this.logger.error(`[Task Poller Monitor]: ${error.message}`); + logger.error(`[Task Poller Monitor]: ${error.message}`); }, } ); + + elasticsearchAndSOAvailability$.subscribe((areESAndSOAvailable) => { + if (areESAndSOAvailable && !this.isStarted) { + // start polling for work + this.pollingSubscription = this.subscribeToPoller(poller$); + } else if (!areESAndSOAvailable && this.isStarted) { + this.pollingSubscription.unsubscribe(); + this.pool.cancelRunningTasks(); + } + }); } public get events(): Observable { @@ -184,39 +207,24 @@ export class TaskPollingLifecycle { ); }; - /** - * Starts up the task manager and starts picking up tasks. - */ - public start() { - if (!this.isStarted) { - this.pollingSubscription = this.poller$ - .pipe( - tap( - mapErr((error: PollingError) => { - if (error.type === PollingErrorType.RequestCapacityReached) { - pipe( - error.data, - mapOptional((id) => this.emitEvent(asTaskRunRequestEvent(id, asErr(error)))) - ); - } - this.logger.error(error.message); - }) - ) + private subscribeToPoller(poller$: Observable>>) { + return poller$ + .pipe( + tap( + mapErr((error: PollingError) => { + if (error.type === PollingErrorType.RequestCapacityReached) { + pipe( + error.data, + mapOptional((id) => this.emitEvent(asTaskRunRequestEvent(id, asErr(error)))) + ); + } + this.logger.error(error.message); + }) ) - .subscribe((event: Result>) => { - this.emitEvent(asTaskPollingCycleEvent(event)); - }); - } - } - - /** - * Stops the task manager and cancels running tasks. - */ - public stop() { - if (this.isStarted) { - this.pollingSubscription.unsubscribe(); - this.pool.cancelRunningTasks(); - } + ) + .subscribe((event: Result>) => { + this.emitEvent(asTaskPollingCycleEvent(event)); + }); } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 58ee36507cb73..1492c8a03906a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20199,7 +20199,6 @@ "xpack.triggersActionsUI.geoThreshold.whenEntityLabel": "エンティティ", "xpack.triggersActionsUI.home.alertsTabTitle": "アラート", "xpack.triggersActionsUI.home.appTitle": "アラートとアクション", - "xpack.triggersActionsUI.home.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.home.breadcrumbTitle": "アラートとアクション", "xpack.triggersActionsUI.home.connectorsTabTitle": "コネクター", "xpack.triggersActionsUI.home.sectionDescription": "アラートを使用して条件を検出し、コネクターを使用してアクションを実行します。", @@ -20250,18 +20249,14 @@ "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText": "時間フィールドが必要です。", "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText": "時間ウィンドウサイズが必要です。", "xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText": "用語フィールドが必要です。", - "xpack.triggersActionsUI.sections.addConnectorForm.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.addConnectorForm.flyoutTitle": "{actionTypeName} コネクタ", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "コネクターを選択", "xpack.triggersActionsUI.sections.addConnectorForm.updateErrorNotificationText": "コネクターを作成できません。", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "「{connectorName}」を作成しました", - "xpack.triggersActionsUI.sections.addFlyout.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", - "xpack.triggersActionsUI.sections.addModalConnectorForm.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.addModalConnectorForm.flyoutTitle": "{actionTypeName} コネクター", "xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.addModalConnectorForm.updateSuccessNotificationText": "「{connectorName}」を作成しました", - "xpack.triggersActionsUI.sections.alertAdd.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.alertAdd.conditionPrompt": "条件を定義してください", "xpack.triggersActionsUI.sections.alertAdd.errorLoadingAlertVisualizationTitle": "アラートビジュアライゼーションを読み込めません", "xpack.triggersActionsUI.sections.alertAdd.flyoutTitle": "アラートの作成", @@ -20291,7 +20286,6 @@ "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.columns.status": "ステータス", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.status.active": "アクティブ", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.status.inactive": "OK", - "xpack.triggersActionsUI.sections.alertDetails.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.alertDetails.collapsedItemActons.disableTitle": "無効にする", "xpack.triggersActionsUI.sections.alertDetails.collapsedItemActons.muteTitle": "ミュート", "xpack.triggersActionsUI.sections.alertDetails.dismissButtonTitle": "閉じる", @@ -20299,7 +20293,6 @@ "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertInstanceSummaryMessage": "アラートインスタンス概要を読み込めません:{message}", "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertMessage": "アラートを読み込めません: {message}", "xpack.triggersActionsUI.sections.alertDetails.viewAlertInAppButtonLabel": "アプリで表示", - "xpack.triggersActionsUI.sections.alertEdit.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.alertEdit.disabledActionsWarningTitle": "このアラートには無効なアクションがあります", "xpack.triggersActionsUI.sections.alertEdit.flyoutTitle": "アラートを編集", @@ -20412,7 +20405,6 @@ "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.subjectTextFieldLabel": "件名", "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "ユーザー名", "xpack.triggersActionsUI.sections.editConnectorForm.actionTypeDescription": "{actionDescription}", - "xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.editConnectorForm.descriptionText": "このコネクターは読み取り専用です。", "xpack.triggersActionsUI.sections.editConnectorForm.flyoutPreconfiguredTitle": "コネクターを編集", @@ -20422,7 +20414,6 @@ "xpack.triggersActionsUI.sections.editConnectorForm.tabText": "構成", "xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "コネクターを更新できません。", "xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText": "「{connectorName}」を更新しました", - "xpack.triggersActionsUI.sections.preconfiguredConnectorForm.betaBadgeTooltipContent": "{pluginName}はベータ段階で、変更される可能性があります。デザインとコードはオフィシャルGA機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャルGA機能のSLAが適用されません。", "xpack.triggersActionsUI.sections.preconfiguredConnectorForm.flyoutTitle": "{connectorName}", "xpack.triggersActionsUI.sections.preconfiguredConnectorForm.tooltipContent": "このコネクターはあらかじめ構成されているため、編集できません。", "xpack.triggersActionsUI.sections.testConnectorForm.awaitingExecutionDescription": "アクションを実行すると、結果がここに表示されます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 832b716934bca..be5bcd3cf0543 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20218,7 +20218,6 @@ "xpack.triggersActionsUI.geoThreshold.whenEntityLabel": "当实体", "xpack.triggersActionsUI.home.alertsTabTitle": "告警", "xpack.triggersActionsUI.home.appTitle": "告警和操作", - "xpack.triggersActionsUI.home.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.home.breadcrumbTitle": "告警和操作", "xpack.triggersActionsUI.home.connectorsTabTitle": "连接器", "xpack.triggersActionsUI.home.sectionDescription": "使用告警检测条件,并使用连接器采取操作。", @@ -20270,18 +20269,14 @@ "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText": "时间字段必填。", "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText": "“时间窗大小”必填。", "xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText": "词字段必填。", - "xpack.triggersActionsUI.sections.addConnectorForm.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.addConnectorForm.flyoutTitle": "{actionTypeName} 连接器", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "选择连接器", "xpack.triggersActionsUI.sections.addConnectorForm.updateErrorNotificationText": "无法创建连接器。", "xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText": "已创建“{connectorName}”", - "xpack.triggersActionsUI.sections.addFlyout.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", - "xpack.triggersActionsUI.sections.addModalConnectorForm.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.addModalConnectorForm.flyoutTitle": "{actionTypeName} 连接器", "xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.addModalConnectorForm.updateSuccessNotificationText": "已创建“{connectorName}”", - "xpack.triggersActionsUI.sections.alertAdd.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.alertAdd.conditionPrompt": "定义条件", "xpack.triggersActionsUI.sections.alertAdd.errorLoadingAlertVisualizationTitle": "无法加载告警可视化", "xpack.triggersActionsUI.sections.alertAdd.flyoutTitle": "创建告警", @@ -20311,7 +20306,6 @@ "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.columns.status": "状态", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.status.active": "活动", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.status.inactive": "确定", - "xpack.triggersActionsUI.sections.alertDetails.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.alertDetails.collapsedItemActons.disableTitle": "禁用", "xpack.triggersActionsUI.sections.alertDetails.collapsedItemActons.muteTitle": "静音", "xpack.triggersActionsUI.sections.alertDetails.dismissButtonTitle": "关闭", @@ -20319,7 +20313,6 @@ "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertInstanceSummaryMessage": "无法加载告警实例摘要:{message}", "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertMessage": "无法加载告警:{message}", "xpack.triggersActionsUI.sections.alertDetails.viewAlertInAppButtonLabel": "在应用中查看", - "xpack.triggersActionsUI.sections.alertEdit.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.alertEdit.disabledActionsWarningTitle": "此告警具有已禁用的操作", "xpack.triggersActionsUI.sections.alertEdit.flyoutTitle": "编辑告警", @@ -20432,7 +20425,6 @@ "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.subjectTextFieldLabel": "主题", "xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel": "用户名", "xpack.triggersActionsUI.sections.editConnectorForm.actionTypeDescription": "{actionDescription}", - "xpack.triggersActionsUI.sections.editConnectorForm.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.editConnectorForm.descriptionText": "此连接器为只读。", "xpack.triggersActionsUI.sections.editConnectorForm.flyoutPreconfiguredTitle": "编辑连接器", @@ -20442,7 +20434,6 @@ "xpack.triggersActionsUI.sections.editConnectorForm.tabText": "配置", "xpack.triggersActionsUI.sections.editConnectorForm.updateErrorNotificationText": "无法更新连接器。", "xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText": "已更新“{connectorName}”", - "xpack.triggersActionsUI.sections.preconfiguredConnectorForm.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.preconfiguredConnectorForm.flyoutTitle": "{connectorName}", "xpack.triggersActionsUI.sections.preconfiguredConnectorForm.tooltipContent": "这是预配置连接器,无法编辑", "xpack.triggersActionsUI.sections.testConnectorForm.awaitingExecutionDescription": "执行该操作时,结果将显示在此处。", diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index c9187096821d8..a4446e0a75120 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -3,9 +3,9 @@ "version": "kibana", "server": true, "ui": true, - "optionalPlugins": ["alerts", "stackAlerts"], + "optionalPlugins": ["home", "alerts", "stackAlerts"], "requiredPlugins": ["management", "charts", "data", "kibanaReact"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], - "requiredBundles": ["alerts", "esUiShared"] + "requiredBundles": ["home", "alerts", "esUiShared"] } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index f009a04d40978..482b38ffc0d68 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -16,11 +16,9 @@ import { EuiTab, EuiTabs, EuiTitle, - EuiBetaBadge, EuiText, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { Section, routeToConnectors, routeToAlerts } from './constants'; import { getAlertingSectionBreadcrumb } from './lib/breadcrumb'; import { getCurrentDocTitle } from './lib/doc_title'; @@ -29,7 +27,6 @@ import { hasShowActionsCapability } from './lib/capabilities'; import { ActionsConnectorsList } from './sections/actions_connectors_list/components/actions_connectors_list'; import { AlertsList } from './sections/alerts_list/components/alerts_list'; -import { PLUGIN } from './constants/plugin'; import { HealthCheck } from './components/health_check'; import { HealthContextProvider } from './context/health_context'; @@ -91,20 +88,6 @@ export const TriggersActionsUIHome: React.FunctionComponent -   - diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 060a751677de0..2e222884dab50 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -17,7 +17,6 @@ import { EuiButtonEmpty, EuiButton, EuiFlyoutBody, - EuiBetaBadge, EuiCallOut, EuiSpacer, } from '@elastic/eui'; @@ -31,7 +30,6 @@ import { hasSaveActionsCapability } from '../../lib/capabilities'; import { createActionConnector } from '../../lib/action_connector_api'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; -import { PLUGIN } from '../../constants/plugin'; export interface ConnectorAddFlyoutProps { addFlyoutVisible: boolean; @@ -189,20 +187,6 @@ export const ConnectorAddFlyout = ({ actionTypeName: actionType.name, }} /> -   - @@ -216,20 +200,6 @@ export const ConnectorAddFlyout = ({ defaultMessage="Select a connector" id="xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle" /> -   - )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 90abb986517d4..d7ca91218d4dd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -5,7 +5,7 @@ */ import React, { useCallback, useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup, EuiBetaBadge } from '@elastic/eui'; +import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup } from '@elastic/eui'; import { EuiModal, EuiButton, @@ -24,7 +24,6 @@ import { connectorReducer } from './connector_reducer'; import { createActionConnector } from '../../lib/action_connector_api'; import { TypeRegistry } from '../../type_registry'; import './connector_add_modal.scss'; -import { PLUGIN } from '../../constants/plugin'; import { hasSaveActionsCapability } from '../../lib/capabilities'; interface ConnectorAddModalProps { @@ -135,20 +134,6 @@ export const ConnectorAddModal = ({ actionTypeName: actionType.name, }} /> -   - diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index 4d8981f25aedc..e89eb8c95fbab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -31,7 +31,6 @@ import { connectorReducer } from './connector_reducer'; import { updateActionConnector, executeAction } from '../../lib/action_connector_api'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; -import { PLUGIN } from '../../constants/plugin'; import { ActionTypeExecutorResult, isActionTypeExecutorResult, @@ -156,20 +155,6 @@ export const ConnectorEditFlyout = ({ } )} /> -   - @@ -187,20 +172,6 @@ export const ConnectorEditFlyout = ({ defaultMessage="Edit connector" id="xpack.triggersActionsUI.sections.editConnectorForm.flyoutPreconfiguredTitle" /> -   - ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 51c3e030f44eb..662db81101eee 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -8,18 +8,8 @@ import uuid from 'uuid'; import { shallow } from 'enzyme'; import { AlertDetails } from './alert_details'; import { Alert, ActionType, ValidationResult } from '../../../../types'; -import { - EuiTitle, - EuiBadge, - EuiFlexItem, - EuiSwitch, - EuiBetaBadge, - EuiButtonEmpty, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiTitle, EuiBadge, EuiFlexItem, EuiSwitch, EuiButtonEmpty, EuiText } from '@elastic/eui'; import { ViewInApp } from './view_in_app'; -import { PLUGIN } from '../../../constants/plugin'; import { coreMock } from 'src/core/public/mocks'; import { ALERTS_FEATURE_ID } from '../../../../../../alerts/common'; @@ -104,20 +94,6 @@ describe('alert_details', () => {

{alert.name} -   -

) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 0af01114731a3..1272024557bb6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -22,12 +22,10 @@ import { EuiSwitch, EuiCallOut, EuiSpacer, - EuiBetaBadge, EuiButtonEmpty, EuiButton, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { useAppDependencies } from '../../../app_context'; import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities'; import { getAlertingSectionBreadcrumb, getAlertDetailsBreadcrumb } from '../../../lib/breadcrumb'; @@ -39,7 +37,6 @@ import { } from '../../common/components/with_bulk_alert_api_operations'; import { AlertInstancesRouteWithApi } from './alert_instances_route'; import { ViewInApp } from './view_in_app'; -import { PLUGIN } from '../../../constants/plugin'; import { AlertEdit } from '../../alert_form'; import { AlertsContextProvider } from '../../../context/alerts_context'; import { routeToAlertDetails } from '../../../constants'; @@ -130,20 +127,6 @@ export const AlertDetails: React.FunctionComponent = ({

{alert.name} -   -

diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index 763462ba6ebf4..89deb4b26f012 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -6,14 +6,7 @@ import React, { useCallback, useReducer, useState, useEffect } from 'react'; import { isObject } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiTitle, - EuiFlyoutHeader, - EuiFlyout, - EuiFlyoutBody, - EuiPortal, - EuiBetaBadge, -} from '@elastic/eui'; +import { EuiTitle, EuiFlyoutHeader, EuiFlyout, EuiFlyoutBody, EuiPortal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useAlertsContext } from '../../context/alerts_context'; import { Alert, AlertAction, IErrorObject } from '../../../types'; @@ -21,7 +14,6 @@ import { AlertForm, validateBaseProperties } from './alert_form'; import { alertReducer } from './alert_reducer'; import { createAlert } from '../../lib/alert_api'; import { HealthCheck } from '../../components/health_check'; -import { PLUGIN } from '../../constants/plugin'; import { ConfirmAlertSave } from './confirm_alert_save'; import { hasShowActionsCapability } from '../../lib/capabilities'; import AlertAddFooter from './alert_add_footer'; @@ -163,20 +155,6 @@ export const AlertAdd = ({ defaultMessage="Create alert" id="xpack.triggersActionsUI.sections.alertAdd.flyoutTitle" /> -   - diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 0435a4cc33cb8..5eadc742a9dc8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -16,7 +16,6 @@ import { EuiButton, EuiFlyoutBody, EuiPortal, - EuiBetaBadge, EuiCallOut, EuiSpacer, } from '@elastic/eui'; @@ -27,7 +26,6 @@ import { AlertForm, validateBaseProperties } from './alert_form'; import { alertReducer } from './alert_reducer'; import { updateAlert } from '../../lib/alert_api'; import { HealthCheck } from '../../components/health_check'; -import { PLUGIN } from '../../constants/plugin'; import { HealthContextProvider } from '../../context/health_context'; interface AlertEditProps { @@ -119,20 +117,6 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { defaultMessage="Edit alert" id="xpack.triggersActionsUI.sections.alertEdit.flyoutTitle" /> -   - diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 874a380f56b5f..393ac5bc1b74d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -20,6 +20,10 @@ import { ManagementAppMountParams, ManagementSetup, } from '../../../../src/plugins/management/public'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../src/plugins/home/public'; import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; import { PluginStartContract as AlertingStart } from '../../alerts/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; @@ -40,6 +44,7 @@ export interface TriggersAndActionsUIPublicPluginStart { interface PluginsSetup { management: ManagementSetup; + home?: HomePublicPluginSetup; } interface PluginsStart { @@ -73,11 +78,31 @@ export class Plugin const actionTypeRegistry = this.actionTypeRegistry; const alertTypeRegistry = this.alertTypeRegistry; + const featureTitle = i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { + defaultMessage: 'Alerts and Actions', + }); + const featureDescription = i18n.translate( + 'xpack.triggersActionsUI.managementSection.displayDescription', + { + defaultMessage: 'Detect conditions using alerts, and take actions using connectors.', + } + ); + + if (plugins.home) { + plugins.home.featureCatalogue.register({ + id: 'triggersActions', + title: featureTitle, + description: featureDescription, + icon: 'watchesApp', + path: '/app/management/insightsAndAlerting/triggersActions', + showOnHomePage: false, + category: FeatureCatalogueCategory.ADMIN, + }); + } + plugins.management.sections.section.insightsAndAlerting.registerApp({ id: 'triggersActions', - title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { - defaultMessage: 'Alerts and Actions', - }), + title: featureTitle, order: 0, async mount(params: ManagementAppMountParams) { const [coreStart, pluginsStart] = (await core.getStartServices()) as [ diff --git a/x-pack/test/functional/es_archives/event_log_multiple_indicies/data.json b/x-pack/test/functional/es_archives/event_log_multiple_indicies/data.json new file mode 100644 index 0000000000000..4e871f6308b77 --- /dev/null +++ b/x-pack/test/functional/es_archives/event_log_multiple_indicies/data.json @@ -0,0 +1,274 @@ +{ + "type": "doc", + "value": { + "id": "config:8.0.0", + "index": ".kibana_1", + "source": { + "config": { + "buildNum": 9007199254740991 + }, + "migrationVersion": { + "config": "7.9.0" + }, + "references": [ + ], + "type": "config", + "updated_at": "2020-10-28T15:19:15.795Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "space:default", + "index": ".kibana_1", + "source": { + "migrationVersion": { + "space": "6.6.0" + }, + "references": [ + ], + "space": { + "_reserved": true, + "color": "#00bfb3", + "description": "This is your default space!", + "disabledFeatures": [ + ], + "name": "Default" + }, + "type": "space", + "updated_at": "2020-10-28T15:19:15.857Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "space:namespace-a", + "index": ".kibana_1", + "source": { + "migrationVersion": { + "space": "6.6.0" + }, + "references": [ + ], + "space": { + "disabledFeatures": [ + ], + "name": "Space A" + }, + "type": "space", + "updated_at": "2020-10-28T15:19:52.887Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "event_log_test:421f2511-5cd1-44fd-95df-e0df83e354d5", + "index": ".kibana_1", + "source": { + "event_log_test": { + }, + "references": [ + ], + "type": "event_log_test", + "updated_at": "2020-10-28T15:19:53.861Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "XKbLb3UBt6Z_MVvSSPbe", + "index": ".kibana-event-log-7.9.0-000001", + "source": { + "@timestamp": "2020-10-28T15:19:54.841Z", + "ecs": { + "version": "1.5.0" + }, + "event": { + "action": "test", + "duration": 0, + "end": "2020-10-28T15:19:54.841Z", + "provider": "event_log_fixture", + "start": "2020-10-28T15:19:54.841Z" + }, + "kibana": { + "saved_objects": [ + { + "id": "421f2511-5cd1-44fd-95df-e0df83e354d5", + "rel": "primary", + "type": "event_log_test" + } + ], + "server_uuid": "5b2de169-2785-441b-ae8c-186a1936b17d" + }, + "message": "test 2020-10-28T15:19:53.825Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "XabLb3UBt6Z_MVvSSfYD", + "index": ".kibana-event-log-7.9.0-000001", + "source": { + "@timestamp": "2020-10-28T15:19:54.879Z", + "ecs": { + "version": "1.5.0" + }, + "event": { + "action": "test", + "duration": 0, + "end": "2020-10-28T15:19:54.879Z", + "provider": "event_log_fixture", + "start": "2020-10-28T15:19:54.879Z" + }, + "kibana": { + "saved_objects": [ + { + "id": "421f2511-5cd1-44fd-95df-e0df83e354d5", + "rel": "primary", + "type": "event_log_test" + } + ], + "server_uuid": "5b2de169-2785-441b-ae8c-186a1936b17d" + }, + "message": "test 2020-10-28T15:19:54.849Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "XqbLb3UBt6Z_MVvSSfYe", + "index": ".kibana-event-log-7.9.0-000001", + "source": { + "@timestamp": "2020-10-28T15:19:54.905Z", + "ecs": { + "version": "1.5.0" + }, + "event": { + "action": "test", + "duration": 0, + "end": "2020-10-28T15:19:54.905Z", + "provider": "event_log_fixture", + "start": "2020-10-28T15:19:54.905Z" + }, + "kibana": { + "saved_objects": [ + { + "id": "421f2511-5cd1-44fd-95df-e0df83e354d5", + "rel": "primary", + "type": "event_log_test" + } + ], + "server_uuid": "5b2de169-2785-441b-ae8c-186a1936b17d" + }, + "message": "test 2020-10-28T15:19:54.881Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "X6bLb3UBt6Z_MVvSTfYk", + "index": ".kibana-event-log-8.0.0-000001", + "source": { + "@timestamp": "2020-10-28T15:19:55.933Z", + "ecs": { + "version": "1.5.0" + }, + "event": { + "action": "test", + "duration": 0, + "end": "2020-10-28T15:19:55.933Z", + "provider": "event_log_fixture", + "start": "2020-10-28T15:19:55.933Z" + }, + "kibana": { + "saved_objects": [ + { + "id": "421f2511-5cd1-44fd-95df-e0df83e354d5", + "rel": "primary", + "type": "event_log_test" + } + ], + "server_uuid": "5b2de169-2785-441b-ae8c-186a1936b17d" + }, + "message": "test 2020-10-28T15:19:55.913Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "YKbLb3UBt6Z_MVvSTfY8", + "index": ".kibana-event-log-8.0.0-000001", + "source": { + "@timestamp": "2020-10-28T15:19:55.957Z", + "ecs": { + "version": "1.5.0" + }, + "event": { + "action": "test", + "duration": 0, + "end": "2020-10-28T15:19:55.957Z", + "provider": "event_log_fixture", + "start": "2020-10-28T15:19:55.957Z" + }, + "kibana": { + "saved_objects": [ + { + "id": "421f2511-5cd1-44fd-95df-e0df83e354d5", + "rel": "primary", + "type": "event_log_test" + } + ], + "server_uuid": "5b2de169-2785-441b-ae8c-186a1936b17d" + }, + "message": "test 2020-10-28T15:19:55.938Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "YabLb3UBt6Z_MVvSTfZc", + "index": ".kibana-event-log-8.0.0-000001", + "source": { + "@timestamp": "2020-10-28T15:19:55.991Z", + "ecs": { + "version": "1.5.0" + }, + "event": { + "action": "test", + "duration": 0, + "end": "2020-10-28T15:19:55.991Z", + "provider": "event_log_fixture", + "start": "2020-10-28T15:19:55.991Z" + }, + "kibana": { + "saved_objects": [ + { + "id": "421f2511-5cd1-44fd-95df-e0df83e354d5", + "rel": "primary", + "type": "event_log_test" + } + ], + "server_uuid": "5b2de169-2785-441b-ae8c-186a1936b17d" + }, + "message": "test 2020-10-28T15:19:55.962Z" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/event_log_multiple_indicies/mappings.json b/x-pack/test/functional/es_archives/event_log_multiple_indicies/mappings.json new file mode 100644 index 0000000000000..b418ccc1343a2 --- /dev/null +++ b/x-pack/test/functional/es_archives/event_log_multiple_indicies/mappings.json @@ -0,0 +1,576 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "eaf6f5841dbf4cb5e3045860f75f53ca", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "477f214ff61acc3af26a7b7818e380c1", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "epm-packages": "2b83397e3eaaaa8ef15e38813f3721c3", + "event_log_test": "bef808d4a9c27f204ffbda3359233931", + "exception-list": "67f055ab8c10abd7b2ebfd969b836788", + "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9", + "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", + "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683", + "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "45915a1ad866812242df474eb0479052", + "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", + "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", + "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e", + "ingest-package-policies": "f74dfe498e1849267cda41580b2be110", + "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f", + "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "52346cfec69ff7b47d5f0c12361a2797", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "43012c7ebc4cb57054e0a490e4b43023", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "d12c5474364d737d17252acf1dc4585c", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "event_log_test": { + "type": "object" + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "monitoring-telemetry": { + "properties": { + "reportedClusterUuids": { + "type": "keyword" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana-event-log-7.9.0": { + "is_write_index": true + } + }, + "index": ".kibana-event-log-7.9.0-000001", + "mappings": { + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "norms": false, + "type": "text" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + } + } + }, + "kibana": { + "properties": { + "alerting": { + "properties": { + "instance_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_objects": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "rel": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "server_uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "tags": { + "ignore_above": 1024, + "meta": { + "isArray": "true" + }, + "type": "keyword" + }, + "user": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "lifecycle": { + "name": "kibana-event-log-policy", + "rollover_alias": ".kibana-event-log-7.9.0" + }, + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana-event-log-8.0.0": { + "is_write_index": true + } + }, + "index": ".kibana-event-log-8.0.0-000001", + "mappings": { + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "norms": false, + "type": "text" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + } + } + }, + "kibana": { + "properties": { + "alerting": { + "properties": { + "instance_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved_objects": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "rel": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "server_uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "tags": { + "ignore_above": 1024, + "meta": { + "isArray": "true" + }, + "type": "keyword" + }, + "user": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "lifecycle": { + "name": "kibana-event-log-policy", + "rollover_alias": ".kibana-event-log-8.0.0" + }, + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts index 3b93607832670..bd799947256d6 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts @@ -23,7 +23,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await log.debug('Checking for section heading to say Triggers and Actions.'); const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText(); - expect(headingText).to.be('Alerts and Actions BETA'); + expect(headingText).to.be('Alerts and Actions'); }); describe('Connectors tab', () => { diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts index eea18863e3be8..dff10daafbdb8 100644 --- a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts @@ -157,6 +157,32 @@ export default function ({ getService }: FtrProviderContext) { }); }); } + + describe(`Index Lifecycle`, () => { + it('should query across indicies matching the Event Log index pattern', async () => { + await esArchiver.load('event_log_multiple_indicies'); + + const id = `421f2511-5cd1-44fd-95df-e0df83e354d5`; + + const { + body: { data, total }, + } = await findEvents(undefined, id, {}); + + expect(data.length).to.be(6); + expect(total).to.be(6); + + expect(data.map((foundEvent: IEvent) => foundEvent?.message)).to.eql([ + 'test 2020-10-28T15:19:53.825Z', + 'test 2020-10-28T15:19:54.849Z', + 'test 2020-10-28T15:19:54.881Z', + 'test 2020-10-28T15:19:55.913Z', + 'test 2020-10-28T15:19:55.938Z', + 'test 2020-10-28T15:19:55.962Z', + ]); + + await esArchiver.unload('event_log_multiple_indicies'); + }); + }); }); async function findEvents( diff --git a/yarn.lock b/yarn.lock index 8de8e0a8c0eb2..b2216537bbd7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8153,9 +8153,9 @@ camelize@^1.0.0: integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001097: - version "1.0.30001114" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001114.tgz#2e88119afb332ead5eaa330e332e951b1c4bfea9" - integrity sha512-ml/zTsfNBM+T1+mjglWRPgVsu2L76GAaADKX5f4t0pbhttEp0WMawJsHDYlFkVZkoA+89uvBRrVrEE4oqenzXQ== + version "1.0.30001150" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001150.tgz" + integrity sha512-kiNKvihW0m36UhAFnl7bOAv0i1K1f6wpfVtTF5O5O82XzgtBnb05V0XeV3oZ968vfg2sRNChsHw8ASH2hDfoYQ== capture-exit@^2.0.0: version "2.0.0"