From bdb49661db64b85220d38b3fd36e448022141f04 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Mon, 23 May 2022 13:13:23 +0100 Subject: [PATCH 01/71] styling (#132539) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/common/components/charts/areachart.tsx | 7 ++++--- .../public/common/components/charts/barchart.tsx | 7 ++++--- .../public/common/components/charts/common.tsx | 5 +++++ .../common/components/visualization_actions/index.tsx | 1 + 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx index 313b216eb19ea..8da0b0b707be4 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx @@ -19,7 +19,7 @@ import { import { getOr, get, isNull, isNumber } from 'lodash/fp'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; import { useThrottledResizeObserver } from '../utils'; import { ChartPlaceHolder } from './chart_place_holder'; import { useTimeZone } from '../../lib/kibana'; @@ -32,6 +32,7 @@ import { WrappedByAutoSizer, useTheme, Wrapper, + ChartWrapper, } from './common'; import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions'; import { VisualizationActionsProps } from '../visualization_actions/types'; @@ -165,7 +166,7 @@ export const AreaChartComponent: React.FC = ({ {isValidSeriesExist && areaChart && ( - + = ({ /> - + )} {!isValidSeriesExist && ( diff --git a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx index fcea5c8d77dc9..91e328c876775 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; import React, { useMemo } from 'react'; import { Chart, BarSeries, Axis, Position, ScaleType, Settings } from '@elastic/charts'; import { getOr, get, isNumber } from 'lodash/fp'; @@ -32,6 +32,7 @@ import { WrappedByAutoSizer, useTheme, Wrapper, + ChartWrapper, } from './common'; import { DraggableLegend } from './draggable_legend'; import { LegendItem } from './draggable_legend_item'; @@ -210,7 +211,7 @@ export const BarChartComponent: React.FC = ({ {isValidSeriesExist && barChart && ( - + = ({ - + )} {!isValidSeriesExist && ( diff --git a/x-pack/plugins/security_solution/public/common/components/charts/common.tsx b/x-pack/plugins/security_solution/public/common/components/charts/common.tsx index b96d016d9b186..cc24da4f27eb7 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/common.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/common.tsx @@ -20,6 +20,7 @@ import { AxisStyle, BarSeriesStyle, } from '@elastic/charts'; +import { EuiFlexGroup } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; @@ -152,3 +153,7 @@ export const checkIfAllValuesAreZero = (data: ChartSeriesData[] | null | undefin export const Wrapper = styled.div` position: relative; `; + +export const ChartWrapper = styled(EuiFlexGroup)` + z-index: 0; +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx index 4ee0034ed4d02..e22865c18bd99 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx @@ -30,6 +30,7 @@ const Wrapper = styled.div` position: absolute; top: 0; right: 0; + z-index: 1; } &.histogram-viz-actions { padding: ${({ theme }) => theme.eui.paddingSizes.s}; From 1135ee71b4da3a9eea715abe04b26d59e0ee1a03 Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Mon, 23 May 2022 14:24:13 +0200 Subject: [PATCH 02/71] [Security Solution] Implement generic approach to execution context propagation (#131805) --- .eslintrc.js | 7 ++ src/core/public/apm_system.ts | 23 ++++- src/plugins/kibana_react/public/index.ts | 2 + .../kibana_react/public/router/index.ts | 9 ++ .../kibana_react/public/router/router.tsx | 86 +++++++++++++++++++ .../use_execution_context.ts | 6 +- .../cases/public/components/app/routes.tsx | 3 +- .../security_solution/public/app/index.tsx | 3 +- .../security_solution/public/app/routes.tsx | 3 +- .../ml_host_conditional_container.tsx | 3 +- .../ml_network_conditional_container.tsx | 3 +- .../public/detections/pages/alerts/index.tsx | 3 +- .../public/exceptions/routes.tsx | 3 +- .../hosts/pages/details/details_tabs.tsx | 3 +- .../public/hosts/pages/hosts_tabs.tsx | 3 +- .../public/hosts/pages/index.tsx | 3 +- .../management/pages/blocklist/index.tsx | 3 +- .../management/pages/endpoint_hosts/index.tsx | 3 +- .../management/pages/event_filters/index.tsx | 3 +- .../pages/host_isolation_exceptions/index.tsx | 3 +- .../public/management/pages/index.tsx | 3 +- .../public/management/pages/policy/index.tsx | 3 +- .../management/pages/trusted_apps/index.tsx | 3 +- .../public/network/pages/index.tsx | 3 +- .../pages/navigation/network_routes.tsx | 3 +- .../security_solution/public/rules/routes.tsx | 3 +- .../public/timelines/pages/index.tsx | 3 +- .../users/pages/details/details_tabs.tsx | 3 +- .../public/users/pages/index.tsx | 3 +- .../public/users/pages/users_tabs.tsx | 3 +- 30 files changed, 176 insertions(+), 29 deletions(-) create mode 100644 src/plugins/kibana_react/public/router/index.ts create mode 100644 src/plugins/kibana_react/public/router/router.tsx diff --git a/.eslintrc.js b/.eslintrc.js index a921718a97f79..3ec2fe38b4d6f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -956,6 +956,13 @@ module.exports = { // to help deprecation and prevent accidental re-use/continued use of code we plan on removing. If you are // finding yourself turning this off a lot for "new code" consider renaming the file and functions if it is has valid uses. patterns: ['*legacy*'], + paths: [ + { + name: 'react-router-dom', + importNames: ['Route'], + message: "import { Route } from '@kbn/kibana-react-plugin/public'", + }, + ], }, ], }, diff --git a/src/core/public/apm_system.ts b/src/core/public/apm_system.ts index 4e116c0a0182d..81d2c5ec0896f 100644 --- a/src/core/public/apm_system.ts +++ b/src/core/public/apm_system.ts @@ -36,6 +36,7 @@ export class ApmSystem { private pageLoadTransaction?: Transaction; private resourceObserver: CachedResourceObserver; private apm?: ApmBase; + private executionContext?: ExecutionContextStart; /** * `apmConfig` would be populated with relevant APM RUM agent @@ -56,6 +57,7 @@ export class ApmSystem { } this.addHttpRequestNormalization(apm); + this.addRouteChangeNormalization(apm); init(apmConfig); // hold page load transaction blocks a transaction implicitly created by init. @@ -65,6 +67,7 @@ export class ApmSystem { async start(start?: StartDeps) { if (!this.enabled || !start) return; + this.executionContext = start.executionContext; this.markPageLoadStart(); start.executionContext.context$.subscribe((c) => { @@ -126,7 +129,7 @@ export class ApmSystem { /** * Adds an observer to the APM configuration for normalizing transactions of the 'http-request' type to remove the - * hostname, protocol, port, and base path. Allows for coorelating data cross different deployments. + * hostname, protocol, port, and base path. Allows for correlating data cross different deployments. */ private addHttpRequestNormalization(apm: ApmBase) { apm.observe('transaction:end', (t) => { @@ -154,7 +157,7 @@ export class ApmSystem { return; } - // Strip the protocol, hostnname, port, and protocol slashes to normalize + // Strip the protocol, hostname, port, and protocol slashes to normalize parts.protocol = null; parts.hostname = null; parts.port = null; @@ -171,4 +174,20 @@ export class ApmSystem { t.name = `${method} ${normalizedUrl}`; }); } + + /** + * Set route-change transaction name to the destination page name taken from + * the execution context. Otherwise, all route change transactions would have + * default names, like 'Click - span' or 'Click - a' instead of more + * descriptive '/security/rules/:id/edit'. + */ + private addRouteChangeNormalization(apm: ApmBase) { + apm.observe('transaction:end', (t) => { + const executionContext = this.executionContext?.get(); + if (executionContext && t.type === 'route-change') { + const { name, page } = executionContext; + t.name = `${name} ${page || 'unknown'}`; + } + }); + } } diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index c1f0a520a1e9c..4b4c0b6e5ab20 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -47,6 +47,8 @@ export { TableListView } from './table_list_view'; export type { ToolbarButtonProps } from './toolbar_button'; export { POSITIONS, WEIGHTS, TOOLBAR_BUTTON_SIZES, ToolbarButton } from './toolbar_button'; +export { Route } from './router'; + export { reactRouterNavigate, reactRouterOnClickHandler } from './react_router_navigate'; export type { diff --git a/src/plugins/kibana_react/public/router/index.ts b/src/plugins/kibana_react/public/router/index.ts new file mode 100644 index 0000000000000..8659ff73ced36 --- /dev/null +++ b/src/plugins/kibana_react/public/router/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { Route } from './router'; diff --git a/src/plugins/kibana_react/public/router/router.tsx b/src/plugins/kibana_react/public/router/router.tsx new file mode 100644 index 0000000000000..15e2c1df30ced --- /dev/null +++ b/src/plugins/kibana_react/public/router/router.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo } from 'react'; +import { + Route as ReactRouterRoute, + RouteComponentProps, + RouteProps, + useRouteMatch, +} from 'react-router-dom'; +import { useKibana } from '../context'; +import { useExecutionContext } from '../use_execution_context'; + +/** + * It's a wrapper around the react-router-dom Route component that inserts + * MatchPropagator in every application route. It helps track all route changes + * and send them to the execution context, later used to enrich APM + * 'route-change' transactions. + */ +export const Route = ({ children, component: Component, render, ...rest }: RouteProps) => { + const component = useMemo(() => { + if (!Component) { + return undefined; + } + return (props: RouteComponentProps) => ( + <> + + + + ); + }, [Component]); + + if (component) { + return ; + } + if (render) { + return ( + ( + <> + + {render(props)} + + )} + /> + ); + } + if (typeof children === 'function') { + return ( + ( + <> + + {children(props)} + + )} + /> + ); + } + return ( + + + {children} + + ); +}; + +const MatchPropagator = () => { + const { executionContext } = useKibana().services; + const match = useRouteMatch(); + + useExecutionContext(executionContext, { + type: 'application', + page: match.path, + id: Object.keys(match.params).length > 0 ? JSON.stringify(match.params) : undefined, + }); + + return null; +}; diff --git a/src/plugins/kibana_react/public/use_execution_context/use_execution_context.ts b/src/plugins/kibana_react/public/use_execution_context/use_execution_context.ts index 9614176bb262e..6e925ff971a6f 100644 --- a/src/plugins/kibana_react/public/use_execution_context/use_execution_context.ts +++ b/src/plugins/kibana_react/public/use_execution_context/use_execution_context.ts @@ -15,14 +15,14 @@ import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect'; * @param context */ export function useExecutionContext( - executionContext: CoreStart['executionContext'], + executionContext: CoreStart['executionContext'] | undefined, context: KibanaExecutionContext ) { useDeepCompareEffect(() => { - executionContext.set(context); + executionContext?.set(context); return () => { - executionContext.clear(); + executionContext?.clear(); }; }, [context]); } diff --git a/x-pack/plugins/cases/public/components/app/routes.tsx b/x-pack/plugins/cases/public/components/app/routes.tsx index 6fc87f691b2a2..031f86cf4d697 100644 --- a/x-pack/plugins/cases/public/components/app/routes.tsx +++ b/x-pack/plugins/cases/public/components/app/routes.tsx @@ -6,7 +6,8 @@ */ import React, { useCallback } from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Redirect, Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { AllCases } from '../all_cases'; import { CaseView } from '../case_view'; import { CreateCase } from '../create'; diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 4e0824f527d1e..1e4817307c227 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -7,7 +7,8 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { NotFoundPage } from './404'; import { SecurityApp } from './app'; diff --git a/x-pack/plugins/security_solution/public/app/routes.tsx b/x-pack/plugins/security_solution/public/app/routes.tsx index 587c4dd230191..a5a82a68d06ef 100644 --- a/x-pack/plugins/security_solution/public/app/routes.tsx +++ b/x-pack/plugins/security_solution/public/app/routes.tsx @@ -7,7 +7,8 @@ import { History } from 'history'; import React, { FC, memo, useEffect } from 'react'; -import { Route, Router, Switch } from 'react-router-dom'; +import { Router, Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { useDispatch } from 'react-redux'; import { AppLeaveHandler, AppMountParameters } from '@kbn/core/public'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx index 7dcaa1de58d10..a18d51cb63773 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx @@ -8,7 +8,8 @@ import { parse, stringify } from 'query-string'; import React from 'react'; -import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'; +import { Redirect, Switch, useRouteMatch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { url as urlUtils } from '@kbn/kibana-utils-plugin/public'; import { addEntitiesToKql } from './add_entities_to_kql'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx index a8dcf18702b97..7261ef7335a99 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx @@ -8,7 +8,8 @@ import { parse, stringify } from 'query-string'; import React from 'react'; -import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'; +import { Redirect, Switch, useRouteMatch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { url as urlUtils } from '@kbn/kibana-utils-plugin/public'; import { addEntitiesToKql } from './add_entities_to_kql'; import { replaceKQLParts } from './replace_kql_parts'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx index 9945d5225ea02..288b389215e48 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import { ALERTS_PATH, SecurityPageName } from '../../../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/exceptions/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx index 5e89ec8e4d40b..9dad970be0fe9 100644 --- a/x-pack/plugins/security_solution/public/exceptions/routes.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -5,7 +5,8 @@ * 2.0. */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx index 7baa72b31ae07..5b1f84fe5373c 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx @@ -6,7 +6,8 @@ */ import React, { useCallback } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index af619db5e9fa0..6f593b676d5d3 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -6,7 +6,8 @@ */ import React, { memo, useCallback } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { HostsTabsProps } from './types'; import { scoreIntervalToDateTime } from '../../common/components/ml/score/score_interval_to_datetime'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx index 00afec5da3756..764281a48b737 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { Route, Switch, Redirect } from 'react-router-dom'; +import { Switch, Redirect } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { HOSTS_PATH } from '../../../common/constants'; import { HostDetails } from './details'; import { HostsTableType } from '../store/model'; diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/index.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/index.tsx index 59638f2e529ab..0a57b49f0c31b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/blocklist/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { Switch, Route } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import React, { memo } from 'react'; import { MANAGEMENT_ROUTING_BLOCKLIST_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx index ac361ff8f16ad..14aa195e0be81 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { Switch, Route } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import React, { memo } from 'react'; import { EndpointList } from './view'; import { MANAGEMENT_ROUTING_ENDPOINTS_PATH } from '../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx index 54d18f85b739a..9cb825bd06a69 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import React from 'react'; import { NotFoundPage } from '../../../app/404'; import { MANAGEMENT_ROUTING_EVENT_FILTERS_PATH } from '../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx index 7ed2adf8c94fa..bf1966d93dda3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { Switch, Route } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import React, { memo } from 'react'; import { MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 2da77bb24c919..f4a83860ceed4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -6,7 +6,8 @@ */ import React, { memo } from 'react'; -import { Route, Switch, Redirect } from 'react-router-dom'; +import { Switch, Redirect } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { EuiLoadingSpinner } from '@elastic/eui'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx index e6680af5c7daf..8157cc65e3fba 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx @@ -6,7 +6,8 @@ */ import React, { memo } from 'react'; -import { Route, Switch, Redirect } from 'react-router-dom'; +import { Switch, Redirect } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { PolicyDetails, PolicyList } from './view'; import { MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx index d9edd9986e156..6d1d56ea25e2d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { Switch, Route } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import React, { memo } from 'react'; import { TrustedAppsList } from './view/trusted_apps_list'; import { MANAGEMENT_ROUTING_TRUSTED_APPS_PATH } from '../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/network/pages/index.tsx b/x-pack/plugins/security_solution/public/network/pages/index.tsx index e5c130fbe761d..30510e3269f5c 100644 --- a/x-pack/plugins/security_solution/public/network/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/index.tsx @@ -6,7 +6,8 @@ */ import React, { useMemo } from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Redirect, Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { useMlCapabilities } from '../../common/components/ml/hooks/use_ml_capabilities'; import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx index ea026664ce1e4..fc4f9d3831ad2 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx @@ -6,7 +6,8 @@ */ import React, { useCallback } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FlowTargetSourceDest } from '../../../../common/search_strategy/security_solution/network'; diff --git a/x-pack/plugins/security_solution/public/rules/routes.tsx b/x-pack/plugins/security_solution/public/rules/routes.tsx index 468e2b41e9290..60f37ee5a1dbe 100644 --- a/x-pack/plugins/security_solution/public/rules/routes.tsx +++ b/x-pack/plugins/security_solution/public/rules/routes.tsx @@ -5,8 +5,9 @@ * 2.0. */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; import { RULES_PATH, SecurityPageName } from '../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx index 5ad969adba5cd..8cd417f904023 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { Switch, Route, Redirect } from 'react-router-dom'; +import { Switch, Redirect } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { TimelineType } from '../../../common/types/timeline'; diff --git a/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx index 22b394f41bfaf..3cc2970b9d650 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx @@ -6,7 +6,8 @@ */ import React, { useCallback, useMemo } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { UsersTableType } from '../../store/model'; import { AnomaliesUserTable } from '../../../common/components/ml/tables/anomalies_user_table'; diff --git a/x-pack/plugins/security_solution/public/users/pages/index.tsx b/x-pack/plugins/security_solution/public/users/pages/index.tsx index 04b1122a39b54..055bb2bb71ab2 100644 --- a/x-pack/plugins/security_solution/public/users/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/index.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { Route, Switch, Redirect } from 'react-router-dom'; +import { Switch, Redirect } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { USERS_PATH } from '../../../common/constants'; import { UsersTableType } from '../store/model'; import { Users } from './users'; diff --git a/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx index fb2cecee75ea6..4d154ee5e3e7c 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx @@ -6,7 +6,8 @@ */ import React, { memo, useCallback } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/kibana-react-plugin/public'; import { UsersTabsProps } from './types'; import { UsersTableType } from '../store/model'; From 3e8e89069f9c244cf7b077e3f74d04a27fb68073 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 23 May 2022 14:51:32 +0200 Subject: [PATCH 03/71] Rename the menu group title, file names and variable. (#132679) --- .../security_solution/common/constants.ts | 7 +- .../public/app/deep_links/index.ts | 18 +- .../public/app/translations.ts | 4 - .../public/common/components/link_to/index.ts | 2 +- .../navigation/breadcrumbs/index.test.ts | 237 +++++++----------- .../solution_grouped_nav.test.tsx | 16 +- .../common/components/url_state/constants.ts | 2 +- .../utils/timeline/use_show_timeline.tsx | 2 +- .../public/landing_pages/links.ts | 14 +- .../pages/{threat_hunting.tsx => explore.tsx} | 12 +- .../landing_pages/pages/translations.ts | 4 +- .../public/landing_pages/routes.tsx | 10 +- 12 files changed, 124 insertions(+), 204 deletions(-) rename x-pack/plugins/security_solution/public/landing_pages/pages/{threat_hunting.tsx => explore.tsx} (67%) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index f8c159241d00e..deac55e74c0fb 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -89,7 +89,6 @@ export enum SecurityPageName { endpoints = 'endpoints', eventFilters = 'event_filters', exceptions = 'exceptions', - explore = 'explore', hostIsolationExceptions = 'host_isolation_exceptions', hosts = 'hosts', hostsAnomalies = 'hosts-anomalies', @@ -120,11 +119,11 @@ export enum SecurityPageName { sessions = 'sessions', usersEvents = 'users-events', usersExternalAlerts = 'users-external_alerts', - threatHuntingLanding = 'threat_hunting', + exploreLanding = 'explore', dashboardsLanding = 'dashboards', } -export const THREAT_HUNTING_PATH = '/threat_hunting' as const; +export const EXPLORE_PATH = '/explore' as const; export const DASHBOARDS_PATH = '/dashboards' as const; export const MANAGE_PATH = '/manage' as const; export const TIMELINES_PATH = '/timelines' as const; @@ -153,7 +152,7 @@ export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const; export const APP_LANDING_PATH = `${APP_PATH}${LANDING_PATH}` as const; export const APP_DETECTION_RESPONSE_PATH = `${APP_PATH}${DETECTION_RESPONSE_PATH}` as const; export const APP_MANAGEMENT_PATH = `${APP_PATH}${MANAGEMENT_PATH}` as const; -export const APP_THREAT_HUNTING_PATH = `${APP_PATH}${THREAT_HUNTING_PATH}` as const; +export const APP_EXPLORE_PATH = `${APP_PATH}${EXPLORE_PATH}` as const; export const APP_DASHBOARDS_PATH = `${APP_PATH}${DASHBOARDS_PATH}` as const; export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}` as const; diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 6598e0dc29426..b265eff2dff19 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -34,7 +34,6 @@ import { POLICIES, ENDPOINTS, GETTING_STARTED, - THREAT_HUNTING, DASHBOARDS, CREATE_NEW_RULE, } from '../translations'; @@ -58,7 +57,7 @@ import { HOST_ISOLATION_EXCEPTIONS_PATH, SERVER_APP_ID, USERS_PATH, - THREAT_HUNTING_PATH, + EXPLORE_PATH, DASHBOARDS_PATH, MANAGE_PATH, RULES_CREATE_PATH, @@ -115,18 +114,6 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ }), ], }, - { - id: SecurityPageName.threatHuntingLanding, - title: THREAT_HUNTING, - path: THREAT_HUNTING_PATH, - navLinkStatus: AppNavLinkStatus.hidden, - features: [FEATURE.general], - keywords: [ - i18n.translate('xpack.securitySolution.search.threatHunting', { - defaultMessage: 'Threat Hunting', - }), - ], - }, { id: SecurityPageName.dashboardsLanding, title: DASHBOARDS, @@ -213,8 +200,9 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ ], }, { - id: SecurityPageName.explore, + id: SecurityPageName.exploreLanding, title: EXPLORE, + path: EXPLORE_PATH, navLinkStatus: AppNavLinkStatus.hidden, features: [FEATURE.general], keywords: [ diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 354ba438ff52a..7586cff6e0da0 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -26,10 +26,6 @@ export const GETTING_STARTED = i18n.translate('xpack.securitySolution.navigation defaultMessage: 'Get started', }); -export const THREAT_HUNTING = i18n.translate('xpack.securitySolution.navigation.threatHunting', { - defaultMessage: 'Threat Hunting', -}); - export const DASHBOARDS = i18n.translate('xpack.securitySolution.navigation.dashboards', { defaultMessage: 'Dashboards', }); diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts index ba86842106e23..fe0b8fbdbfba7 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts @@ -90,7 +90,7 @@ function formatPath(path: string, search: string, skipSearch?: boolean) { function needsUrlState(pageId: SecurityPageName) { return ( pageId !== SecurityPageName.dashboardsLanding && - pageId !== SecurityPageName.threatHuntingLanding && + pageId !== SecurityPageName.exploreLanding && pageId !== SecurityPageName.administration && pageId !== SecurityPageName.rules && pageId !== SecurityPageName.exceptions && diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 05dd7145ba785..e545d4f19bbb9 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -132,6 +132,36 @@ jest.mock('../../../lib/kibana/kibana_react', () => { }; }); +const securityBreadCrumb = { + href: 'securitySolutionUI/get_started', + text: 'Security', +}; + +const hostsBreadcrumbs = { + href: 'securitySolutionUI/hosts', + text: 'Hosts', +}; + +const networkBreadcrumb = { + text: 'Network', + href: 'securitySolutionUI/network', +}; + +const exploreBreadcrumbs = { + href: 'securitySolutionUI/explore', + text: 'Explore', +}; + +const rulesBReadcrumb = { + text: 'Rules', + href: 'securitySolutionUI/rules', +}; + +const manageBreadcrumbs = { + text: 'Manage', + href: 'securitySolutionUI/administration', +}; + describe('Navigation Breadcrumbs', () => { beforeAll(async () => { const appLinks = await getAppLinks(coreMock.createStart(), {} as StartPlugins); @@ -169,10 +199,7 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { - href: 'securitySolutionUI/get_started', - text: 'Security', - }, + securityBreadCrumb, { href: '', text: 'Overview', @@ -187,14 +214,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { - href: 'securitySolutionUI/get_started', - text: 'Security', - }, - { - href: 'securitySolutionUI/hosts', - text: 'Hosts', - }, + securityBreadCrumb, + hostsBreadcrumbs, { href: '', text: 'Authentications', @@ -209,11 +230,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Network', - href: 'securitySolutionUI/network', - }, + securityBreadCrumb, + networkBreadcrumb, { text: 'Flows', href: '', @@ -228,7 +246,7 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, + securityBreadCrumb, { text: 'Timelines', href: '', @@ -243,11 +261,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Hosts', - href: 'securitySolutionUI/hosts', - }, + securityBreadCrumb, + hostsBreadcrumbs, { text: 'siem-kibana', href: 'securitySolutionUI/hosts/siem-kibana', @@ -263,11 +278,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Network', - href: 'securitySolutionUI/network', - }, + securityBreadCrumb, + networkBreadcrumb, { text: ipv4, href: `securitySolutionUI/network/ip/${ipv4}/source`, @@ -283,11 +295,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Network', - href: 'securitySolutionUI/network', - }, + securityBreadCrumb, + networkBreadcrumb, { text: ipv6, href: `securitySolutionUI/network/ip/${ipv6Encoded}/source`, @@ -303,7 +312,7 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, + securityBreadCrumb, { text: 'Alerts', href: '', @@ -318,7 +327,7 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, + securityBreadCrumb, { text: 'Exception lists', href: '', @@ -333,7 +342,7 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, + securityBreadCrumb, { text: 'Rules', href: '', @@ -348,11 +357,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Rules', - href: 'securitySolutionUI/rules', - }, + securityBreadCrumb, + rulesBReadcrumb, { text: 'Create', href: '', @@ -375,11 +381,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Rules', - href: 'securitySolutionUI/rules', - }, + securityBreadCrumb, + rulesBReadcrumb, { text: mockRuleName, href: ``, @@ -402,11 +405,8 @@ describe('Navigation Breadcrumbs', () => { false ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Rules', - href: 'securitySolutionUI/rules', - }, + securityBreadCrumb, + rulesBReadcrumb, { text: 'ALERT_RULE_NAME', href: `securitySolutionUI/rules/id/${mockDetailName}`, @@ -451,7 +451,7 @@ describe('Navigation Breadcrumbs', () => { ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, + securityBreadCrumb, { text: 'Endpoints', href: '', @@ -504,10 +504,7 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { - href: 'securitySolutionUI/get_started', - text: 'Security', - }, + securityBreadCrumb, { href: 'securitySolutionUI/dashboards', text: 'Dashboards', @@ -526,18 +523,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { - href: 'securitySolutionUI/get_started', - text: 'Security', - }, - { - href: 'securitySolutionUI/threat_hunting', - text: 'Threat Hunting', - }, - { - href: 'securitySolutionUI/hosts', - text: 'Hosts', - }, + securityBreadCrumb, + exploreBreadcrumbs, + hostsBreadcrumbs, { href: '', text: 'Authentications', @@ -552,15 +540,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - href: 'securitySolutionUI/threat_hunting', - text: 'Threat Hunting', - }, - { - text: 'Network', - href: 'securitySolutionUI/network', - }, + securityBreadCrumb, + exploreBreadcrumbs, + networkBreadcrumb, { text: 'Flows', href: '', @@ -575,7 +557,7 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, + securityBreadCrumb, { text: 'Timelines', href: '', @@ -590,15 +572,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - href: 'securitySolutionUI/threat_hunting', - text: 'Threat Hunting', - }, - { - text: 'Hosts', - href: 'securitySolutionUI/hosts', - }, + securityBreadCrumb, + exploreBreadcrumbs, + hostsBreadcrumbs, { text: 'siem-kibana', href: 'securitySolutionUI/hosts/siem-kibana', @@ -614,15 +590,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - href: 'securitySolutionUI/threat_hunting', - text: 'Threat Hunting', - }, - { - text: 'Network', - href: 'securitySolutionUI/network', - }, + securityBreadCrumb, + exploreBreadcrumbs, + networkBreadcrumb, { text: ipv4, href: `securitySolutionUI/network/ip/${ipv4}/source`, @@ -638,15 +608,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - href: 'securitySolutionUI/threat_hunting', - text: 'Threat Hunting', - }, - { - text: 'Network', - href: 'securitySolutionUI/network', - }, + securityBreadCrumb, + exploreBreadcrumbs, + networkBreadcrumb, { text: ipv6, href: `securitySolutionUI/network/ip/${ipv6Encoded}/source`, @@ -662,7 +626,7 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, + securityBreadCrumb, { text: 'Alerts', href: '', @@ -677,11 +641,8 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Manage', - href: 'securitySolutionUI/administration', - }, + securityBreadCrumb, + manageBreadcrumbs, { text: 'Exception lists', href: '', @@ -696,11 +657,8 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Manage', - href: 'securitySolutionUI/administration', - }, + securityBreadCrumb, + manageBreadcrumbs, { text: 'Rules', href: '', @@ -715,15 +673,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Manage', - href: 'securitySolutionUI/administration', - }, - { - text: 'Rules', - href: 'securitySolutionUI/rules', - }, + securityBreadCrumb, + manageBreadcrumbs, + rulesBReadcrumb, { text: 'Create', href: '', @@ -746,15 +698,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Manage', - href: 'securitySolutionUI/administration', - }, - { - text: 'Rules', - href: 'securitySolutionUI/rules', - }, + securityBreadCrumb, + manageBreadcrumbs, + rulesBReadcrumb, { text: mockRuleName, href: ``, @@ -777,15 +723,9 @@ describe('Navigation Breadcrumbs', () => { true ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Manage', - href: 'securitySolutionUI/administration', - }, - { - text: 'Rules', - href: 'securitySolutionUI/rules', - }, + securityBreadCrumb, + manageBreadcrumbs, + rulesBReadcrumb, { text: 'ALERT_RULE_NAME', href: `securitySolutionUI/rules/id/${mockDetailName}`, @@ -830,11 +770,8 @@ describe('Navigation Breadcrumbs', () => { ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionUI/get_started' }, - { - text: 'Manage', - href: 'securitySolutionUI/administration', - }, + securityBreadCrumb, + manageBreadcrumbs, { text: 'Endpoints', href: '', @@ -858,8 +795,8 @@ describe('Navigation Breadcrumbs', () => { onClick: expect.any(Function), }), expect.objectContaining({ - text: 'Threat Hunting', - href: `securitySolutionUI/threat_hunting`, + text: 'Explore', + href: `securitySolutionUI/explore`, onClick: expect.any(Function), }), expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx index e41b566bbc7c8..5f5fd14605643 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav.test.tsx @@ -74,14 +74,14 @@ describe('SolutionGroupedNav', () => { const items = [ ...mockItems, { - id: SecurityPageName.threatHuntingLanding, - label: 'Threat Hunting', - href: '/threat_hunting', + id: SecurityPageName.exploreLanding, + label: 'Explore', + href: '/explore', onClick: mockOnClick, }, ]; const result = renderNav({ items }); - result.getByTestId(`groupedNavItemLink-${SecurityPageName.threatHuntingLanding}`).click(); + result.getByTestId(`groupedNavItemLink-${SecurityPageName.exploreLanding}`).click(); expect(mockOnClick).toHaveBeenCalled(); }); }); @@ -122,9 +122,9 @@ describe('SolutionGroupedNav', () => { const items = [ ...mockItems, { - id: SecurityPageName.threatHuntingLanding, - label: 'Threat Hunting', - href: '/threat_hunting', + id: SecurityPageName.exploreLanding, + label: 'Explore', + href: '/explore', items: [ { id: SecurityPageName.users, @@ -141,7 +141,7 @@ describe('SolutionGroupedNav', () => { expect(result.getByTestId('groupedNavPanel')).toBeInTheDocument(); expect(result.getByText('Overview')).toBeInTheDocument(); - result.getByTestId(`groupedNavItemButton-${SecurityPageName.threatHuntingLanding}`).click(); + result.getByTestId(`groupedNavItemButton-${SecurityPageName.exploreLanding}`).click(); expect(result.queryByTestId('groupedNavPanel')).toBeInTheDocument(); expect(result.getByText('Users')).toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts index dcedec945575d..0595428fa83af 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts @@ -38,5 +38,5 @@ export type UrlStateType = | 'overview' | 'rules' | 'timeline' - | 'threat_hunting' + | 'explore' | 'dashboards'; diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx index bb9eb075d735f..c2dad5ff8f065 100644 --- a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx @@ -16,7 +16,7 @@ const DEPRECATED_HIDDEN_TIMELINE_ROUTES: readonly string[] = [ '/administration', '/rules/create', '/get_started', - '/threat_hunting', + '/explore', '/dashboards', '/manage', ]; diff --git a/x-pack/plugins/security_solution/public/landing_pages/links.ts b/x-pack/plugins/security_solution/public/landing_pages/links.ts index 48cd31485ea7f..3c8ec59632deb 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/links.ts +++ b/x-pack/plugins/security_solution/public/landing_pages/links.ts @@ -8,11 +8,11 @@ import { i18n } from '@kbn/i18n'; import { DASHBOARDS_PATH, + EXPLORE_PATH, SecurityPageName, SERVER_APP_ID, - THREAT_HUNTING_PATH, } from '../../common/constants'; -import { DASHBOARDS, THREAT_HUNTING } from '../app/translations'; +import { DASHBOARDS, EXPLORE } from '../app/translations'; import { LinkItem } from '../common/links/types'; import { overviewLinks, detectionResponseLinks } from '../overview/links'; import { links as hostsLinks } from '../hosts/links'; @@ -36,14 +36,14 @@ export const dashboardsLandingLinks: LinkItem = { }; export const threatHuntingLandingLinks: LinkItem = { - id: SecurityPageName.threatHuntingLanding, - title: THREAT_HUNTING, - path: THREAT_HUNTING_PATH, + id: SecurityPageName.exploreLanding, + title: EXPLORE, + path: EXPLORE_PATH, globalNavEnabled: false, capabilities: [`${SERVER_APP_ID}.show`], globalSearchKeywords: [ - i18n.translate('xpack.securitySolution.appLinks.threatHunting', { - defaultMessage: 'Threat hunting', + i18n.translate('xpack.securitySolution.appLinks.explore', { + defaultMessage: 'Explore', }), ], links: [hostsLinks, networkLinks, usersLinks], diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/threat_hunting.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx similarity index 67% rename from x-pack/plugins/security_solution/public/landing_pages/pages/threat_hunting.tsx rename to x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx index 605a1baeedbd6..17a0d7569b965 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/threat_hunting.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx @@ -11,16 +11,16 @@ import { useAppRootNavLink } from '../../common/components/navigation/nav_links' import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { LandingLinksImages } from '../components/landing_links_images'; -import { THREAT_HUNTING_PAGE_TITLE } from './translations'; +import { EXPLORE_PAGE_TITLE } from './translations'; -export const ThreatHuntingLandingPage = () => { - const threatHuntinglinks = useAppRootNavLink(SecurityPageName.threatHuntingLanding)?.links ?? []; +export const ExploreLandingPage = () => { + const exploreLinks = useAppRootNavLink(SecurityPageName.exploreLanding)?.links ?? []; return ( - - - + + + ); }; diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/translations.ts b/x-pack/plugins/security_solution/public/landing_pages/pages/translations.ts index 13a2396201cc5..4986c6b5f31ec 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/translations.ts +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/translations.ts @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; -export const THREAT_HUNTING_PAGE_TITLE = i18n.translate( +export const EXPLORE_PAGE_TITLE = i18n.translate( 'xpack.securitySolution.landing.threatHunting.pageTitle', { - defaultMessage: 'Threat hunting', + defaultMessage: 'Explore', } ); diff --git a/x-pack/plugins/security_solution/public/landing_pages/routes.tsx b/x-pack/plugins/security_solution/public/landing_pages/routes.tsx index 3fbe33cc0ec88..038100cda463f 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/routes.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/routes.tsx @@ -9,14 +9,14 @@ import React from 'react'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; -import { DASHBOARDS_PATH, MANAGE_PATH, THREAT_HUNTING_PATH } from '../../common/constants'; -import { ThreatHuntingLandingPage } from './pages/threat_hunting'; +import { DASHBOARDS_PATH, MANAGE_PATH, EXPLORE_PATH } from '../../common/constants'; +import { ExploreLandingPage } from './pages/explore'; import { DashboardsLandingPage } from './pages/dashboards'; import { ManageLandingPage } from './pages/manage'; export const ThreatHuntingRoutes = () => ( - - + + ); @@ -34,7 +34,7 @@ export const ManageRoutes = () => ( export const routes: SecuritySubPluginRoutes = [ { - path: THREAT_HUNTING_PATH, + path: EXPLORE_PATH, render: ThreatHuntingRoutes, }, { From c16bcdc15dcda785aba3bc2ac173359e0ccc5a68 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 23 May 2022 15:27:09 +0200 Subject: [PATCH 04/71] [APM] Trace explorer (#131897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [APM] Trace explorer * Make sure tabs work on trace explorer * Add links to trace explorer from error detail view & service map * Add API tests * Fix failing E2E test * don't select edges when explorer is disabled * Fix lint error * Update x-pack/plugins/observability/server/ui_settings.ts Co-authored-by: Søren Louv-Jansen * Review feedback * Rename const in API tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Søren Louv-Jansen --- .../components/code_editor/code_editor.tsx | 3 + ...rn_constants.ts => data_view_constants.ts} | 3 +- x-pack/plugins/apm/common/trace_explorer.ts | 16 + .../errors/error_details.spec.ts | 2 +- x-pack/plugins/apm/kibana.json | 11 +- .../plugins/apm/public/application/index.tsx | 2 + .../__snapshots__/index.test.tsx.snap | 78 ----- .../detail_view/index.test.tsx | 82 +++-- .../error_group_details/detail_view/index.tsx | 111 +++++-- .../components/app/service_map/cytoscape.tsx | 4 +- .../app/service_map/cytoscape_options.ts | 24 +- .../service_map/popover/backend_contents.tsx | 5 +- .../app/service_map/popover/edge_contents.tsx | 84 +++++ .../popover/externals_list_contents.tsx | 4 +- .../app/service_map/popover/index.tsx | 81 +++-- .../service_map/popover/resource_contents.tsx | 4 +- .../service_map/popover/service_contents.tsx | 4 +- .../app/top_traces_overview/index.tsx | 61 ++++ .../trace_list.tsx | 17 +- .../components/app/trace_explorer/index.tsx | 157 +++++++++ .../trace_explorer/trace_search_box/index.tsx | 186 +++++++++++ .../components/app/trace_overview/index.tsx | 122 +++---- .../distribution/index.tsx | 56 +++- .../use_waterfall_fetcher.ts | 24 +- .../waterfall_with_summary/index.tsx | 40 ++- .../maybe_view_trace_link.tsx | 15 +- .../transaction_tabs.tsx | 60 ++-- .../waterfall_container/index.tsx | 18 +- .../waterfall/flyout_top_level_properties.tsx | 17 +- .../span_flyout/sticky_span_properties.tsx | 18 +- .../waterfall/waterfall_item.tsx | 8 +- .../waterfall_container.stories.tsx | 70 +++- .../public/components/routing/app_root.tsx | 5 +- .../public/components/routing/home/index.tsx | 71 +++- .../shared/date_picker/apm_date_picker.tsx | 49 +++ .../shared/eql_code_editor/completer.ts | 195 +++++++++++ .../shared/eql_code_editor/constants.ts | 15 + .../eql_code_editor/eql_highlight_rules.ts | 145 +++++++++ .../shared/eql_code_editor/eql_mode.ts | 24 ++ .../shared/eql_code_editor/index.tsx | 54 +++ .../lazily_loaded_code_editor.tsx | 39 +++ .../shared/eql_code_editor/theme.ts | 91 ++++++ .../shared/eql_code_editor/tokens.ts | 25 ++ .../shared/eql_code_editor/types.ts | 33 ++ .../links/discover_links/discover_link.tsx | 4 +- .../public/components/shared/search_bar.tsx | 38 +-- .../context/apm_plugin/apm_plugin_context.tsx | 4 + .../apm/public/hooks/use_apm_route_path.ts | 14 + .../apm/public/hooks/use_static_data_view.ts | 16 + .../use_trace_explorer_enabled_setting.ts | 15 + x-pack/plugins/apm/public/plugin.ts | 3 + .../create_es_client/call_async_with_debug.ts | 2 +- .../cancel_es_request_on_abort.ts | 4 +- .../create_apm_event_client/index.ts | 54 ++- .../unpack_processor_events.ts | 16 +- .../create_internal_es_client/index.ts | 6 +- .../data_view/create_static_data_view.ts | 6 +- .../transform_service_map_responses.ts | 9 +- .../traces/get_trace_samples_by_query.ts | 168 ++++++++++ .../plugins/apm/server/routes/traces/route.ts | 51 ++- x-pack/plugins/apm/server/tutorial/index.ts | 4 +- x-pack/plugins/observability/common/index.ts | 1 + .../observability/common/ui_settings_keys.ts | 1 + .../common/utils/get_inspect_response.ts | 2 +- .../observability/server/ui_settings.ts | 16 + .../tests/data_view/static.spec.ts | 6 +- .../tests/traces/find_traces.spec.ts | 307 ++++++++++++++++++ 67 files changed, 2476 insertions(+), 404 deletions(-) rename x-pack/plugins/apm/common/{index_pattern_constants.ts => data_view_constants.ts} (67%) create mode 100644 x-pack/plugins/apm/common/trace_explorer.ts delete mode 100644 x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx create mode 100644 x-pack/plugins/apm/public/components/app/top_traces_overview/index.tsx rename x-pack/plugins/apm/public/components/app/{trace_overview => top_traces_overview}/trace_list.tsx (92%) create mode 100644 x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx create mode 100644 x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/date_picker/apm_date_picker.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/completer.ts create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/constants.ts create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_highlight_rules.ts create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_mode.ts create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/lazily_loaded_code_editor.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/theme.ts create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/tokens.ts create mode 100644 x-pack/plugins/apm/public/components/shared/eql_code_editor/types.ts create mode 100644 x-pack/plugins/apm/public/hooks/use_apm_route_path.ts create mode 100644 x-pack/plugins/apm/public/hooks/use_static_data_view.ts create mode 100644 x-pack/plugins/apm/public/hooks/use_trace_explorer_enabled_setting.ts create mode 100644 x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts create mode 100644 x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts diff --git a/src/plugins/es_ui_shared/public/components/code_editor/code_editor.tsx b/src/plugins/es_ui_shared/public/components/code_editor/code_editor.tsx index 5f172d010b836..d5ed974d0e40b 100644 --- a/src/plugins/es_ui_shared/public/components/code_editor/code_editor.tsx +++ b/src/plugins/es_ui_shared/public/components/code_editor/code_editor.tsx @@ -58,6 +58,8 @@ export interface EuiCodeEditorProps extends SupportedAriaAttributes, Omit void; } export interface EuiCodeEditorState { @@ -98,6 +100,7 @@ class EuiCodeEditor extends Component { setOrRemoveAttribute(textbox, 'aria-labelledby', this.props['aria-labelledby']); setOrRemoveAttribute(textbox, 'aria-describedby', this.props['aria-describedby']); } + this.props.onAceEditorRef?.(aceEditor); }; onEscToExit = () => { diff --git a/x-pack/plugins/apm/common/index_pattern_constants.ts b/x-pack/plugins/apm/common/data_view_constants.ts similarity index 67% rename from x-pack/plugins/apm/common/index_pattern_constants.ts rename to x-pack/plugins/apm/common/data_view_constants.ts index 4b67bba1fef91..b448918f8facf 100644 --- a/x-pack/plugins/apm/common/index_pattern_constants.ts +++ b/x-pack/plugins/apm/common/data_view_constants.ts @@ -5,4 +5,5 @@ * 2.0. */ -export const APM_STATIC_INDEX_PATTERN_ID = 'apm_static_index_pattern_id'; +// value of const needs to be backwards compatible +export const APM_STATIC_DATA_VIEW_ID = 'apm_static_index_pattern_id'; diff --git a/x-pack/plugins/apm/common/trace_explorer.ts b/x-pack/plugins/apm/common/trace_explorer.ts new file mode 100644 index 0000000000000..9ce3bc8df0bd6 --- /dev/null +++ b/x-pack/plugins/apm/common/trace_explorer.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface TraceSearchQuery { + query: string; + type: TraceSearchType; +} + +export enum TraceSearchType { + kql = 'kql', + eql = 'eql', +} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts index c131cb2dd36d7..caec7a23115ff 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts @@ -89,7 +89,7 @@ describe('Error details', () => { describe('when clicking on View x occurences in discover', () => { it('should redirects the user to discover', () => { cy.visit(errorDetailsPageHref); - cy.contains('View 1 occurrence in Discover.').click(); + cy.contains('View 1 occurrence in Discover').click(); cy.url().should('include', 'app/discover'); }); }); diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 9bb1c52b52d7c..f354556c97b5b 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -17,7 +17,8 @@ "observability", "ruleRegistry", "triggersActionsUi", - "unifiedSearch" + "unifiedSearch", + "dataViews" ], "optionalPlugins": [ "actions", @@ -33,12 +34,16 @@ ], "server": true, "ui": true, - "configPath": ["xpack", "apm"], + "configPath": [ + "xpack", + "apm" + ], "requiredBundles": [ "fleet", "kibanaReact", "kibanaUtils", "ml", - "observability" + "observability", + "esUiShared" ] } diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 19e16237c1272..b471655c6b7d5 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -52,6 +52,8 @@ export const renderApp = ({ inspector: pluginsStart.inspector, observability: pluginsStart.observability, observabilityRuleTypeRegistry, + dataViews: pluginsStart.dataViews, + unifiedSearch: pluginsStart.unifiedSearch, }; // render APM feedback link in global help menu diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 5f300b45de80a..0000000000000 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,78 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DetailView should render Discover button 1`] = ` - - View 10 occurrences in Discover. - -`; - -exports[`DetailView should render TabContent 1`] = ` - -`; - -exports[`DetailView should render tabs 1`] = ` - - - Exception stack trace - - - Metadata - - -`; diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.test.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.test.tsx index a6743f5b7d768..ac38a84bf47d7 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.test.tsx @@ -5,10 +5,33 @@ * 2.0. */ -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react'; import React from 'react'; import { mockMoment } from '../../../../utils/test_helpers'; import { DetailView } from '.'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { createMemoryHistory } from 'history'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; + +const history = createMemoryHistory({ + initialEntries: [ + '/services/opbeans-java/errors/0000?rangeFrom=now-15m&rangeTo=now', + ], +}); + +function MockContext({ children }: { children: React.ReactElement }) { + return ( + + + {children} + + + ); +} + +function renderWithMockContext(element: React.ReactElement) { + return render(element, { wrapper: MockContext }); +} describe('DetailView', () => { beforeEach(() => { @@ -17,10 +40,10 @@ describe('DetailView', () => { }); it('should render empty state', () => { - const wrapper = shallow( + const wrapper = renderWithMockContext( ); - expect(wrapper.isEmptyRender()).toBe(true); + expect(wrapper.baseElement.innerHTML).toBe('
'); }); it('should render Discover button', () => { @@ -35,23 +58,25 @@ describe('DetailView', () => { url: { full: 'myUrl' }, service: { name: 'myService' }, user: { id: 'myUserId' }, - error: { exception: { handled: true } }, + error: { exception: [{ handled: true }] }, transaction: { id: 'myTransactionId', sampled: true }, } as any, }; - const wrapper = shallow( + const discoverLink = renderWithMockContext( - ).find('DiscoverErrorLink'); + ).getByText(`View 10 occurrences in Discover`); - expect(wrapper.exists()).toBe(true); - expect(wrapper).toMatchSnapshot(); + expect(discoverLink).toBeInTheDocument(); }); it('should render a Summary', () => { const errorGroup = { occurrencesCount: 10, error: { + service: { + name: 'opbeans-python', + }, error: {}, timestamp: { us: 0, @@ -59,11 +84,14 @@ describe('DetailView', () => { } as any, transaction: undefined, }; - const wrapper = shallow( + + const rendered = renderWithMockContext( - ).find('Summary'); + ); - expect(wrapper.exists()).toBe(true); + expect( + rendered.getByText('1337 minutes ago (mocking 0)') + ).toBeInTheDocument(); }); it('should render tabs', () => { @@ -79,12 +107,14 @@ describe('DetailView', () => { user: {}, } as any, }; - const wrapper = shallow( + + const rendered = renderWithMockContext( - ).find('EuiTabs'); + ); + + expect(rendered.getByText('Exception stack trace')).toBeInTheDocument(); - expect(wrapper.exists()).toBe(true); - expect(wrapper).toMatchSnapshot(); + expect(rendered.getByText('Metadata')).toBeInTheDocument(); }); it('should render TabContent', () => { @@ -92,19 +122,23 @@ describe('DetailView', () => { occurrencesCount: 10, transaction: undefined, error: { + service: { + name: 'opbeans-python', + }, timestamp: { us: 0, }, - error: {}, + error: { + exception: [{ handled: true }], + }, context: {}, } as any, }; - const wrapper = shallow( + const rendered = renderWithMockContext( - ).find('TabContent'); + ); - expect(wrapper.exists()).toBe(true); - expect(wrapper).toMatchSnapshot(); + expect(rendered.getByText('No stack trace available.')).toBeInTheDocument(); }); it('should render without http request info', () => { @@ -115,16 +149,20 @@ describe('DetailView', () => { timestamp: { us: 0, }, + error: { + exception: [{ handled: true }], + }, http: { response: { status_code: 404 } }, url: { full: 'myUrl' }, service: { name: 'myService' }, user: { id: 'myUserId' }, - error: { exception: { handled: true } }, transaction: { id: 'myTransactionId', sampled: true }, } as any, }; expect(() => - shallow() + renderWithMockContext( + + ) ).not.toThrowError(); }); }); diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx index 03c866dcc7f06..220f276f62152 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx @@ -6,7 +6,10 @@ */ import { + EuiFlexGroup, + EuiFlexItem, EuiIcon, + EuiLink, EuiPanel, EuiSpacer, EuiTab, @@ -38,13 +41,12 @@ import { logStacktraceTab, } from './error_tabs'; import { ExceptionStacktrace } from './exception_stacktrace'; - -const HeaderContainer = euiStyled.div` - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: ${({ theme }) => theme.eui.euiSize}; -`; +import { useApmRouter } from '../../../../hooks/use_apm_router'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { ERROR_GROUP_ID } from '../../../../../common/elasticsearch_fieldnames'; +import { TraceSearchType } from '../../../../../common/trace_explorer'; +import { TransactionTab } from '../../transaction_details/waterfall_with_summary/transaction_tabs'; +import { useTraceExplorerEnabledSetting } from '../../../../hooks/use_trace_explorer_enabled_setting'; const TransactionLinkName = euiStyled.div` margin-left: ${({ theme }) => theme.eui.euiSizeS}; @@ -73,6 +75,15 @@ export function DetailView({ errorGroup, urlParams, kuery }: Props) { const { detailTab, offset, comparisonEnabled } = urlParams; + const router = useApmRouter(); + + const isTraceExplorerEnabled = useTraceExplorerEnabledSetting(); + + const { + path: { groupId }, + query, + } = useApmParams('/services/{serviceName}/errors/{groupId}'); + if (!error) { return null; } @@ -85,30 +96,72 @@ export function DetailView({ errorGroup, urlParams, kuery }: Props) { const method = error.http?.request?.method; const status = error.http?.response?.status_code; + const traceExplorerLink = router.link('/traces/explorer', { + query: { + ...query, + query: `${ERROR_GROUP_ID}:${groupId}`, + type: TraceSearchType.kql, + traceId: '', + transactionId: '', + waterfallItemId: '', + detailTab: TransactionTab.timeline, + }, + }); + return ( - - -

- {i18n.translate( - 'xpack.apm.errorGroupDetails.errorOccurrenceTitle', - { - defaultMessage: 'Error occurrence', - } - )} -

-
- - {i18n.translate( - 'xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel', - { - defaultMessage: - 'View {occurrencesCount} {occurrencesCount, plural, one {occurrence} other {occurrences}} in Discover.', - values: { occurrencesCount }, - } - )} - -
+ + + +

+ {i18n.translate( + 'xpack.apm.errorGroupDetails.errorOccurrenceTitle', + { + defaultMessage: 'Error occurrence', + } + )} +

+
+
+ {isTraceExplorerEnabled && ( + + + + + + + + {i18n.translate( + 'xpack.apm.errorGroupDetails.viewOccurrencesInTraceExplorer', + { + defaultMessage: 'Explore traces with this error', + } + )} + + + + + )} + + + + + + + + {i18n.translate( + 'xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel', + { + defaultMessage: + 'View {occurrencesCount} {occurrencesCount, plural, one {occurrence} other {occurrences}} in Discover', + values: { occurrencesCount }, + } + )} + + + + +
{ +const getStyle = ( + theme: EuiTheme, + isTraceExplorerEnabled: boolean +): cytoscape.Stylesheet[] => { const lineColor = theme.eui.euiColorMediumShade; return [ { @@ -211,6 +214,20 @@ const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => { 'target-arrow-color': theme.eui.euiColorDarkShade, }, }, + ...(isTraceExplorerEnabled + ? [ + { + selector: 'edge.hover', + style: { + width: 4, + 'z-index': zIndexEdgeHover, + 'line-color': theme.eui.euiColorDarkShade, + 'source-arrow-color': theme.eui.euiColorDarkShade, + 'target-arrow-color': theme.eui.euiColorDarkShade, + }, + }, + ] + : []), { selector: 'node.hover', style: { @@ -256,10 +273,11 @@ ${theme.eui.euiColorLightShade}`, }); export const getCytoscapeOptions = ( - theme: EuiTheme + theme: EuiTheme, + isTraceExplorerEnabled: boolean ): cytoscape.CytoscapeOptions => ({ boxSelectionEnabled: false, maxZoom: 3, minZoom: 0.2, - style: getStyle(theme), + style: getStyle(theme, isTraceExplorerEnabled), }); diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/backend_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/backend_contents.tsx index ffff0fa9d56d0..3c6b0f5a2d6f7 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/backend_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/backend_contents.tsx @@ -11,6 +11,7 @@ import { TypeOf } from '@kbn/typed-react-router-config'; import { METRIC_TYPE } from '@kbn/analytics'; import React from 'react'; import { useUiTracker } from '@kbn/observability-plugin/public'; +import { NodeDataDefinition } from 'cytoscape'; import { ContentsProps } from '.'; import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; import { useApmRouter } from '../../../../hooks/use_apm_router'; @@ -27,11 +28,13 @@ const INITIAL_STATE: Partial = { }; export function BackendContents({ - nodeData, + elementData, environment, start, end, }: ContentsProps) { + const nodeData = elementData as NodeDataDefinition; + const { query } = useAnyOfApmParams( '/service-map', '/services/{serviceName}/service-map' diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx new file mode 100644 index 0000000000000..4bcc8bb8ca53b --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { METRIC_TYPE } from '@kbn/analytics'; +import React from 'react'; +import { useUiTracker } from '@kbn/observability-plugin/public'; +import { EdgeDataDefinition } from 'cytoscape'; +import { ContentsProps } from '.'; +import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../../hooks/use_apm_router'; +import { TraceSearchType } from '../../../../../common/trace_explorer'; +import { TransactionTab } from '../../transaction_details/waterfall_with_summary/transaction_tabs'; +import { + SERVICE_NAME, + SPAN_DESTINATION_SERVICE_RESOURCE, +} from '../../../../../common/elasticsearch_fieldnames'; + +export function EdgeContents({ elementData }: ContentsProps) { + const edgeData = elementData as EdgeDataDefinition; + + const { query } = useAnyOfApmParams( + '/service-map', + '/services/{serviceName}/service-map' + ); + + const apmRouter = useApmRouter(); + + const sourceService = edgeData.sourceData['service.name']; + + const trackEvent = useUiTracker(); + + let traceQuery: string; + + if (SERVICE_NAME in edgeData.targetData) { + traceQuery = + `sequence by trace.id\n` + + ` [ span where service.name == "${sourceService}" and span.destination.service.resource != null ] by span.id\n` + + ` [ transaction where service.name == "${edgeData.targetData[SERVICE_NAME]}" ] by parent.id`; + } else { + traceQuery = + `sequence by trace.id\n` + + ` [ transaction where service.name == "${sourceService}" ]\n` + + ` [ span where service.name == "${sourceService}" and span.destination.service.resource == "${edgeData.targetData[SPAN_DESTINATION_SERVICE_RESOURCE]}" ]`; + } + + const url = apmRouter.link('/traces/explorer', { + query: { + ...query, + type: TraceSearchType.eql, + query: traceQuery, + waterfallItemId: '', + traceId: '', + transactionId: '', + detailTab: TransactionTab.timeline, + }, + }); + + return ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click*/} + { + trackEvent({ + app: 'apm', + metricType: METRIC_TYPE.CLICK, + metric: 'service_map_to_trace_explorer', + }); + }} + > + {i18n.translate('xpack.apm.serviceMap.viewInTraceExplorer', { + defaultMessage: 'Explore traces', + })} + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/externals_list_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/externals_list_contents.tsx index a564e0419046f..19a914a7a3c5a 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/externals_list_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/externals_list_contents.tsx @@ -13,6 +13,7 @@ import { } from '@elastic/eui'; import React, { Fragment } from 'react'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { NodeDataDefinition } from 'cytoscape'; import { ContentsProps } from '.'; import { SPAN_DESTINATION_SERVICE_RESOURCE, @@ -26,7 +27,8 @@ const ExternalResourcesList = euiStyled.section` overflow: auto; `; -export function ExternalsListContents({ nodeData }: ContentsProps) { +export function ExternalsListContents({ elementData }: ContentsProps) { + const nodeData = elementData as NodeDataDefinition; return ( diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/index.tsx index 937ad89293a7c..78543fa18cb7b 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/index.tsx @@ -28,32 +28,47 @@ import { } from '../../../../../common/elasticsearch_fieldnames'; import { Environment } from '../../../../../common/environment_rt'; import { useTheme } from '../../../../hooks/use_theme'; +import { useTraceExplorerEnabledSetting } from '../../../../hooks/use_trace_explorer_enabled_setting'; import { CytoscapeContext } from '../cytoscape'; import { getAnimationOptions, popoverWidth } from '../cytoscape_options'; import { BackendContents } from './backend_contents'; +import { EdgeContents } from './edge_contents'; import { ExternalsListContents } from './externals_list_contents'; import { ResourceContents } from './resource_contents'; import { ServiceContents } from './service_contents'; -function getContentsComponent(selectedNodeData: cytoscape.NodeDataDefinition) { +function getContentsComponent( + selectedElementData: + | cytoscape.NodeDataDefinition + | cytoscape.EdgeDataDefinition, + isTraceExplorerEnabled: boolean +) { if ( - selectedNodeData.groupedConnections && - Array.isArray(selectedNodeData.groupedConnections) + selectedElementData.groupedConnections && + Array.isArray(selectedElementData.groupedConnections) ) { return ExternalsListContents; } - if (selectedNodeData[SERVICE_NAME]) { + if (selectedElementData[SERVICE_NAME]) { return ServiceContents; } - if (selectedNodeData[SPAN_TYPE] === 'resource') { + if (selectedElementData[SPAN_TYPE] === 'resource') { return ResourceContents; } + if ( + isTraceExplorerEnabled && + selectedElementData.source && + selectedElementData.target + ) { + return EdgeContents; + } + return BackendContents; } export interface ContentsProps { - nodeData: cytoscape.NodeDataDefinition; + elementData: cytoscape.NodeDataDefinition | cytoscape.ElementDataDefinition; environment: Environment; kuery: string; start: string; @@ -78,19 +93,26 @@ export function Popover({ }: PopoverProps) { const theme = useTheme(); const cy = useContext(CytoscapeContext); - const [selectedNode, setSelectedNode] = useState< - cytoscape.NodeSingular | undefined + const [selectedElement, setSelectedElement] = useState< + cytoscape.NodeSingular | cytoscape.EdgeSingular | undefined >(undefined); const deselect = useCallback(() => { if (cy) { cy.elements().unselect(); } - setSelectedNode(undefined); - }, [cy, setSelectedNode]); - const renderedHeight = selectedNode?.renderedHeight() ?? 0; - const renderedWidth = selectedNode?.renderedWidth() ?? 0; - const { x, y } = selectedNode?.renderedPosition() ?? { x: -10000, y: -10000 }; - const isOpen = !!selectedNode; + setSelectedElement(undefined); + }, [cy, setSelectedElement]); + + const isTraceExplorerEnabled = useTraceExplorerEnabledSetting(); + + const renderedHeight = selectedElement?.renderedHeight() ?? 0; + const renderedWidth = selectedElement?.renderedWidth() ?? 0; + const box = selectedElement?.renderedBoundingBox({}); + + const x = box ? box.x1 + box.w / 2 : -10000; + const y = box ? box.y1 + box.h / 2 : -10000; + + const isOpen = !!selectedElement; const triggerStyle: CSSProperties = { background: 'transparent', height: renderedHeight, @@ -99,20 +121,20 @@ export function Popover({ }; const trigger =
; const zoom = cy?.zoom() ?? 1; - const height = selectedNode?.height() ?? 0; + const height = selectedElement?.height() ?? 0; const translateY = y - ((zoom + 1) * height) / 4; const popoverStyle: CSSProperties = { position: 'absolute', transform: `translate(${x}px, ${translateY}px)`, }; - const selectedNodeData = selectedNode?.data() ?? {}; + const selectedElementData = selectedElement?.data() ?? {}; const popoverRef = useRef(null); - const selectedNodeId = selectedNodeData.id; + const selectedElementId = selectedElementData.id; // Set up Cytoscape event handlers useEffect(() => { const selectHandler: cytoscape.EventHandler = (event) => { - setSelectedNode(event.target); + setSelectedElement(event.target); }; if (cy) { @@ -120,6 +142,10 @@ export function Popover({ cy.on('unselect', 'node', deselect); cy.on('viewport', deselect); cy.on('drag', 'node', deselect); + if (isTraceExplorerEnabled) { + cy.on('select', 'edge', selectHandler); + cy.on('unselect', 'edge', deselect); + } } return () => { @@ -128,9 +154,11 @@ export function Popover({ cy.removeListener('unselect', 'node', deselect); cy.removeListener('viewport', undefined, deselect); cy.removeListener('drag', 'node', deselect); + cy.removeListener('select', 'edge', selectHandler); + cy.removeListener('unselect', 'edge', deselect); } }; - }, [cy, deselect]); + }, [cy, deselect, isTraceExplorerEnabled]); // Handle positioning of popover. This makes it so the popover positions // itself correctly and the arrows are always pointing to where they should. @@ -146,20 +174,23 @@ export function Popover({ if (cy) { cy.animate({ ...getAnimationOptions(theme), - center: { eles: cy.getElementById(selectedNodeId) }, + center: { eles: cy.getElementById(selectedElementId) }, }); } }, - [cy, selectedNodeId, theme] + [cy, selectedElementId, theme] ); - const isAlreadyFocused = focusedServiceName === selectedNodeId; + const isAlreadyFocused = focusedServiceName === selectedElementId; const onFocusClick = isAlreadyFocused ? centerSelectedNode : (_event: MouseEvent) => deselect(); - const ContentsComponent = getContentsComponent(selectedNodeData); + const ContentsComponent = getContentsComponent( + selectedElementData, + isTraceExplorerEnabled + ); return (

- {selectedNodeData.label ?? selectedNodeId} + {selectedElementData.label ?? selectedElementId}

{ + if (start && end) { + return callApmApi('GET /internal/apm/traces', { + params: { + query: { + environment, + kuery, + start, + end, + }, + }, + }); + } + }, + [environment, kuery, start, end] + ); + + return ( + <> + + + {fallbackToTransactions && ( + + + + + + )} + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx b/x-pack/plugins/apm/public/components/app/top_traces_overview/trace_list.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx rename to x-pack/plugins/apm/public/components/app/top_traces_overview/trace_list.tsx index 5fbaae7411ff7..0d1914d7de455 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx +++ b/x-pack/plugins/apm/public/components/app/top_traces_overview/trace_list.tsx @@ -16,6 +16,7 @@ import { asTransactionRate, } from '../../../../common/utils/formatters'; import { useApmParams } from '../../../hooks/use_apm_params'; +import { FetcherResult, FETCH_STATUS } from '../../../hooks/use_fetcher'; import { APIReturnType } from '../../../services/rest/create_call_apm_api'; import { truncate } from '../../../utils/style'; import { EmptyMessage } from '../../shared/empty_message'; @@ -26,19 +27,17 @@ import { ServiceLink } from '../../shared/service_link'; import { TruncateWithTooltip } from '../../shared/truncate_with_tooltip'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; -type TraceGroup = APIReturnType<'GET /internal/apm/traces'>['items'][0]; - const StyledTransactionLink = euiStyled(TransactionDetailLink)` font-size: ${({ theme }) => theme.eui.euiFontSizeS}; ${truncate('100%')}; `; interface Props { - items: TraceGroup[]; - isLoading: boolean; - isFailure: boolean; + response: FetcherResult>; } +type TraceGroup = Required['data']['items'][number]; + export function getTraceListColumns({ query, }: { @@ -153,7 +152,9 @@ const noItemsMessage = ( /> ); -export function TraceList({ items = [], isLoading, isFailure }: Props) { +export function TraceList({ response }: Props) { + const { data: { items } = { items: [] }, status } = response; + const { query } = useApmParams('/traces'); const traceListColumns = useMemo( @@ -162,8 +163,8 @@ export function TraceList({ items = [], isLoading, isFailure }: Props) { ); return ( ({ + query: '', + type: TraceSearchType.kql, + }); + + const { + query: { + rangeFrom, + rangeTo, + environment, + query: queryFromUrlParams, + type: typeFromUrlParams, + traceId, + transactionId, + waterfallItemId, + detailTab, + }, + } = useApmParams('/traces/explorer'); + + const history = useHistory(); + + useEffect(() => { + setQuery({ + query: queryFromUrlParams, + type: typeFromUrlParams, + }); + }, [queryFromUrlParams, typeFromUrlParams]); + + const { start, end } = useTimeRange({ + rangeFrom, + rangeTo, + }); + + const { data: traceSamplesData, status: traceSamplesStatus } = useFetcher( + (callApmApi) => { + return callApmApi('GET /internal/apm/traces/find', { + params: { + query: { + start, + end, + environment, + query: queryFromUrlParams, + type: typeFromUrlParams, + }, + }, + }); + }, + [start, end, environment, queryFromUrlParams, typeFromUrlParams] + ); + + useEffect(() => { + const nextSample = traceSamplesData?.samples[0]; + const nextWaterfallItemId = ''; + history.replace({ + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + traceId: nextSample?.traceId ?? '', + transactionId: nextSample?.transactionId, + waterfallItemId: nextWaterfallItemId, + }), + }); + }, [traceSamplesData, history]); + + const { waterfall, status: waterfallStatus } = useWaterfallFetcher({ + traceId, + transactionId, + start, + end, + }); + + const isLoading = + traceSamplesStatus === FETCH_STATUS.LOADING || + waterfallStatus === FETCH_STATUS.LOADING; + + return ( + + + + + { + history.push({ + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + query: query.query, + type: query.type, + }), + }); + }} + onQueryChange={(nextQuery) => { + setQuery(nextQuery); + }} + /> + + + + + + + + { + push(history, { + query: { + traceId: sample.traceId, + transactionId: sample.transactionId, + waterfallItemId: '', + }, + }); + }} + onTabClick={(nextDetailTab) => { + push(history, { + query: { + detailTab: nextDetailTab, + }, + }); + }} + traceSamples={traceSamplesData?.samples ?? []} + waterfall={waterfall} + detailTab={detailTab} + waterfallItemId={waterfallItemId} + serviceName={waterfall.entryWaterfallTransaction?.doc.service.name} + /> + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx new file mode 100644 index 0000000000000..4951a378c03c3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSelect, + EuiSelectOption, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import React from 'react'; +import { + TraceSearchQuery, + TraceSearchType, +} from '../../../../../common/trace_explorer'; +import { useStaticDataView } from '../../../../hooks/use_static_data_view'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { EQLCodeEditorSuggestionType } from '../../../shared/eql_code_editor/constants'; +import { LazilyLoadedEQLCodeEditor } from '../../../shared/eql_code_editor/lazily_loaded_code_editor'; + +interface Props { + query: TraceSearchQuery; + message?: string; + error: boolean; + onQueryChange: (query: TraceSearchQuery) => void; + onQueryCommit: () => void; + loading: boolean; +} + +const options: EuiSelectOption[] = [ + { + value: TraceSearchType.kql, + text: i18n.translate('xpack.apm.traceSearchBox.traceSearchTypeKql', { + defaultMessage: 'KQL', + }), + }, + { + value: TraceSearchType.eql, + text: i18n.translate('xpack.apm.traceSearchBox.traceSearchTypeEql', { + defaultMessage: 'EQL', + }), + }, +]; + +export function TraceSearchBox({ + query, + onQueryChange, + onQueryCommit, + message, + error, + loading, +}: Props) { + const { unifiedSearch } = useApmPluginContext(); + const { value: dataView } = useStaticDataView(); + + return ( + + + + + + + {query.type === TraceSearchType.eql ? ( + { + onQueryChange({ + ...query, + query: value, + }); + }} + onBlur={() => { + onQueryCommit(); + }} + getSuggestions={async (request) => { + switch (request.type) { + case EQLCodeEditorSuggestionType.EventType: + return ['transaction', 'span', 'error']; + + case EQLCodeEditorSuggestionType.Field: + return ( + dataView?.fields.map((field) => field.name) ?? [] + ); + + case EQLCodeEditorSuggestionType.Value: + const field = dataView?.getFieldByName(request.field); + + if (!dataView || !field) { + return []; + } + + const suggestions: string[] = + await unifiedSearch.autocomplete.getValueSuggestions( + { + field, + indexPattern: dataView, + query: request.value, + useTimeRange: true, + method: 'terms_agg', + } + ); + + return suggestions.slice(0, 15); + } + }} + width="100%" + height="100px" + /> + ) : ( +
+ { + onQueryCommit(); + }} + disableAutoFocus + submitOnBlur + isClearable + onChange={(e) => { + onQueryChange({ + ...query, + query: String(e.query ?? ''), + }); + }} + /> + + )} +
+ + { + onQueryChange({ + query: '', + type: e.target.value as TraceSearchType, + }); + }} + options={options} + /> + +
+
+ + + + + {message} + + + + { + onQueryCommit(); + }} + iconType="search" + > + {i18n.translate('xpack.apm.traceSearchBox.refreshButton', { + defaultMessage: 'Search', + })} + + + + +
+
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index 21ae0f9820890..c586b782fc6e2 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -4,69 +4,81 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTab, EuiTabs } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useApmRouter } from '../../../hooks/use_apm_router'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import { APIReturnType } from '../../../services/rest/create_call_apm_api'; -import { SearchBar } from '../../shared/search_bar'; -import { TraceList } from './trace_list'; -import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher'; -import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; -import { useTimeRange } from '../../../hooks/use_time_range'; -import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher'; +import { useApmRoutePath } from '../../../hooks/use_apm_route_path'; +import { TraceSearchType } from '../../../../common/trace_explorer'; +import { TransactionTab } from '../transaction_details/waterfall_with_summary/transaction_tabs'; +import { useTraceExplorerEnabledSetting } from '../../../hooks/use_trace_explorer_enabled_setting'; -type TracesAPIResponse = APIReturnType<'GET /internal/apm/traces'>; -const DEFAULT_RESPONSE: TracesAPIResponse = { - items: [], -}; +export function TraceOverview({ children }: { children: React.ReactElement }) { + const isTraceExplorerEnabled = useTraceExplorerEnabledSetting(); -export function TraceOverview() { - const { - query: { environment, kuery, rangeFrom, rangeTo }, - } = useApmParams('/traces'); - const { fallbackToTransactions } = useFallbackToTransactionsFetcher({ - kuery, - }); + const router = useApmRouter(); - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { query } = useApmParams('/traces'); - const { status, data = DEFAULT_RESPONSE } = useProgressiveFetcher( - (callApmApi) => { - if (start && end) { - return callApmApi('GET /internal/apm/traces', { - params: { - query: { - environment, - kuery, - start, - end, - }, - }, - }); - } - }, - [environment, kuery, start, end] - ); + const routePath = useApmRoutePath(); - return ( - <> - + if (!isTraceExplorerEnabled) { + return children; + } - {fallbackToTransactions && ( - - - - - - )} + const explorerLink = router.link('/traces/explorer', { + query: { + comparisonEnabled: query.comparisonEnabled, + environment: query.environment, + kuery: query.kuery, + rangeFrom: query.rangeFrom, + rangeTo: query.rangeTo, + offset: query.offset, + refreshInterval: query.refreshInterval, + refreshPaused: query.refreshPaused, + query: '', + type: TraceSearchType.kql, + waterfallItemId: '', + traceId: '', + transactionId: '', + detailTab: TransactionTab.timeline, + }, + }); - - + const topTracesLink = router.link('/traces', { + query: { + comparisonEnabled: query.comparisonEnabled, + environment: query.environment, + kuery: query.kuery, + rangeFrom: query.rangeFrom, + rangeTo: query.rangeTo, + offset: query.offset, + refreshInterval: query.refreshInterval, + refreshPaused: query.refreshPaused, + }, + }); + + return ( + + + + + {i18n.translate('xpack.apm.traceOverview.topTracesTab', { + defaultMessage: 'Top traces', + })} + + + {i18n.translate('xpack.apm.traceOverview.traceExplorerTab', { + defaultMessage: 'Explorer', + })} + + + + {children} + ); } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index fe551cc0e96b8..35d25e9ab406d 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import React from 'react'; import { BrushEndListener, XYBrushEvent } from '@elastic/charts'; import { EuiBadge, @@ -16,6 +15,8 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import React from 'react'; +import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; @@ -32,9 +33,14 @@ import type { TabContentProps } from '../types'; import { useWaterfallFetcher } from '../use_waterfall_fetcher'; import { WaterfallWithSummary } from '../waterfall_with_summary'; -import { useTransactionDistributionChartData } from './use_transaction_distribution_chart_data'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useTimeRange } from '../../../../hooks/use_time_range'; import { HeightRetainer } from '../../../shared/height_retainer'; +import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; import { ChartTitleToolTip } from '../../correlations/chart_title_tool_tip'; +import { useTransactionDistributionChartData } from './use_transaction_distribution_chart_data'; +import { TransactionTab } from '../waterfall_with_summary/transaction_tabs'; // Enforce min height so it's consistent across all tabs on the same level // to prevent "flickering" behavior @@ -70,8 +76,28 @@ export function TransactionDistribution({ traceSamplesStatus, }: TransactionDistributionProps) { const { urlParams } = useLegacyUrlParams(); - const { waterfall, status: waterfallStatus } = useWaterfallFetcher(); + const { traceId, transactionId } = urlParams; + + const { + query: { rangeFrom, rangeTo }, + } = useApmParams('/services/{serviceName}/transactions/view'); + + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const history = useHistory(); + const { waterfall, status: waterfallStatus } = useWaterfallFetcher({ + traceId, + transactionId, + start, + end, + }); + const { waterfallItemId, detailTab } = urlParams; + + const { + query: { environment }, + } = useApmParams('/services/{serviceName}/transactions/view'); + const { serviceName } = useApmServiceContext(); const isLoading = waterfallStatus === FETCH_STATUS.LOADING || traceSamplesStatus === FETCH_STATUS.LOADING; @@ -193,7 +219,29 @@ export function TransactionDistribution({ { + history.push({ + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + transactionId: sample.transactionId, + traceId: sample.traceId, + }), + }); + }} + onTabClick={(tab) => { + history.replace({ + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + detailTab: tab, + }), + }); + }} + serviceName={serviceName} + waterfallItemId={waterfallItemId} + detailTab={detailTab as TransactionTab | undefined} waterfall={waterfall} isLoading={isLoading} traceSamples={traceSamples} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts b/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts index 72d52ae09c9bd..793524c7e17f1 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts @@ -6,10 +6,7 @@ */ import { useMemo } from 'react'; -import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params'; -import { useApmParams } from '../../../hooks/use_apm_params'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { useTimeRange } from '../../../hooks/use_time_range'; import { APIReturnType } from '../../../services/rest/create_call_apm_api'; import { getWaterfall } from './waterfall_with_summary/waterfall_container/waterfall/waterfall_helpers/waterfall_helpers'; @@ -20,16 +17,17 @@ const INITIAL_DATA: APIReturnType<'GET /internal/apm/traces/{traceId}'> = { linkedChildrenOfSpanCountBySpanId: {}, }; -export function useWaterfallFetcher() { - const { urlParams } = useLegacyUrlParams(); - const { traceId, transactionId } = urlParams; - - const { - query: { rangeFrom, rangeTo }, - } = useApmParams('/services/{serviceName}/transactions/view'); - - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - +export function useWaterfallFetcher({ + traceId, + transactionId, + start, + end, +}: { + traceId?: string; + transactionId?: string; + start: string; + end: string; +}) { const { data = INITIAL_DATA, status, diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx index 93913aff6cb6b..f9642630766bd 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx @@ -15,38 +15,40 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; -import { useHistory } from 'react-router-dom'; -import type { ApmUrlParams } from '../../../../context/url_params_context/types'; -import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; import { LoadingStatePrompt } from '../../../shared/loading_state_prompt'; import { TransactionSummary } from '../../../shared/summary/transaction_summary'; import { TransactionActionMenu } from '../../../shared/transaction_action_menu/transaction_action_menu'; import type { TraceSample } from '../../../../hooks/use_transaction_trace_samples_fetcher'; import { MaybeViewTraceLink } from './maybe_view_trace_link'; -import { TransactionTabs } from './transaction_tabs'; +import { TransactionTab, TransactionTabs } from './transaction_tabs'; import { IWaterfall } from './waterfall_container/waterfall/waterfall_helpers/waterfall_helpers'; -import { useApmParams } from '../../../../hooks/use_apm_params'; +import { Environment } from '../../../../../common/environment_rt'; interface Props { - urlParams: ApmUrlParams; waterfall: IWaterfall; isLoading: boolean; traceSamples: TraceSample[]; + environment: Environment; + onSampleClick: (sample: { transactionId: string; traceId: string }) => void; + onTabClick: (tab: string) => void; + serviceName?: string; + waterfallItemId?: string; + detailTab?: TransactionTab; } export function WaterfallWithSummary({ - urlParams, waterfall, isLoading, traceSamples, + environment, + onSampleClick, + onTabClick, + serviceName, + waterfallItemId, + detailTab, }: Props) { - const history = useHistory(); const [sampleActivePage, setSampleActivePage] = useState(0); - const { - query: { environment }, - } = useApmParams('/services/{serviceName}/transactions/view'); - useEffect(() => { setSampleActivePage(0); }, [traceSamples]); @@ -54,14 +56,7 @@ export function WaterfallWithSummary({ const goToSample = (index: number) => { setSampleActivePage(index); const sample = traceSamples[index]; - history.push({ - ...history.location, - search: fromQuery({ - ...toQuery(history.location.search), - transactionId: sample.transactionId, - traceId: sample.traceId, - }), - }); + onSampleClick(sample); }; const { entryWaterfallTransaction } = waterfall; @@ -137,7 +132,10 @@ export function WaterfallWithSummary({ diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx index ecb9fb9a3bc39..1621ea72b39a1 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx @@ -13,7 +13,8 @@ import { Transaction as ITransaction } from '../../../../../typings/es_schemas/u import { TransactionDetailLink } from '../../../shared/links/apm/transaction_detail_link'; import { IWaterfall } from './waterfall_container/waterfall/waterfall_helpers/waterfall_helpers'; import { Environment } from '../../../../../common/environment_rt'; -import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; +import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; function FullTraceButton({ isLoading, @@ -48,8 +49,16 @@ export function MaybeViewTraceLink({ environment: Environment; }) { const { - query: { latencyAggregationType, comparisonEnabled, offset }, - } = useApmParams('/services/{serviceName}/transactions/view'); + query, + query: { comparisonEnabled, offset }, + } = useAnyOfApmParams( + '/services/{serviceName}/transactions/view', + '/traces/explorer' + ); + + const latencyAggregationType = + ('latencyAggregationType' in query && query.latencyAggregationType) || + LatencyAggregationType.avg; if (isLoading || !transaction) { return ; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx index aeaea322fff96..655b1c28c3dac 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx @@ -7,27 +7,32 @@ import { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { useHistory } from 'react-router-dom'; import { LogStream } from '@kbn/infra-plugin/public'; +import React from 'react'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import type { ApmUrlParams } from '../../../../context/url_params_context/types'; -import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; import { TransactionMetadata } from '../../../shared/metadata_table/transaction_metadata'; import { WaterfallContainer } from './waterfall_container'; import { IWaterfall } from './waterfall_container/waterfall/waterfall_helpers/waterfall_helpers'; interface Props { transaction: Transaction; - urlParams: ApmUrlParams; waterfall: IWaterfall; + detailTab?: TransactionTab; + serviceName?: string; + waterfallItemId?: string; + onTabClick: (tab: TransactionTab) => void; } -export function TransactionTabs({ transaction, urlParams, waterfall }: Props) { - const history = useHistory(); +export function TransactionTabs({ + transaction, + waterfall, + detailTab, + waterfallItemId, + serviceName, + onTabClick, +}: Props) { const tabs = [timelineTab, metadataTab, logsTab]; - const currentTab = - tabs.find(({ key }) => key === urlParams.detailTab) ?? timelineTab; + const currentTab = tabs.find(({ key }) => key === detailTab) ?? timelineTab; const TabContent = currentTab.component; return ( @@ -37,13 +42,7 @@ export function TransactionTabs({ transaction, urlParams, waterfall }: Props) { return ( { - history.replace({ - ...history.location, - search: fromQuery({ - ...toQuery(history.location.search), - detailTab: key, - }), - }); + onTabClick(key); }} isSelected={currentTab.key === key} key={key} @@ -57,7 +56,8 @@ export function TransactionTabs({ transaction, urlParams, waterfall }: Props) { @@ -65,8 +65,14 @@ export function TransactionTabs({ transaction, urlParams, waterfall }: Props) { ); } +export enum TransactionTab { + timeline = 'timeline', + metadata = 'metadata', + logs = 'logs', +} + const timelineTab = { - key: 'timeline', + key: TransactionTab.timeline, label: i18n.translate('xpack.apm.propertiesTable.tabs.timelineLabel', { defaultMessage: 'Timeline', }), @@ -74,7 +80,7 @@ const timelineTab = { }; const metadataTab = { - key: 'metadata', + key: TransactionTab.metadata, label: i18n.translate('xpack.apm.propertiesTable.tabs.metadataLabel', { defaultMessage: 'Metadata', }), @@ -82,7 +88,7 @@ const metadataTab = { }; const logsTab = { - key: 'logs', + key: TransactionTab.logs, label: i18n.translate('xpack.apm.propertiesTable.tabs.logsLabel', { defaultMessage: 'Logs', }), @@ -90,13 +96,21 @@ const logsTab = { }; function TimelineTabContent({ - urlParams, waterfall, + waterfallItemId, + serviceName, }: { - urlParams: ApmUrlParams; + waterfallItemId?: string; + serviceName?: string; waterfall: IWaterfall; }) { - return ; + return ( + + ); } function MetadataTabContent({ transaction }: { transaction: Transaction }) { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx index 71e4b6a6f1aad..2dd74aeae3eef 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx @@ -7,23 +7,24 @@ import React from 'react'; import { keyBy } from 'lodash'; -import type { ApmUrlParams } from '../../../../../context/url_params_context/types'; import { IWaterfall, WaterfallLegendType, } from './waterfall/waterfall_helpers/waterfall_helpers'; import { Waterfall } from './waterfall'; import { WaterfallLegends } from './waterfall_legends'; -import { useApmServiceContext } from '../../../../../context/apm_service/use_apm_service_context'; interface Props { - urlParams: ApmUrlParams; + waterfallItemId?: string; + serviceName?: string; waterfall: IWaterfall; } -export function WaterfallContainer({ urlParams, waterfall }: Props) { - const { serviceName } = useApmServiceContext(); - +export function WaterfallContainer({ + serviceName, + waterfallItemId, + waterfall, +}: Props) { if (!waterfall) { return null; } @@ -75,10 +76,7 @@ export function WaterfallContainer({ urlParams, waterfall }: Props) { return (
- +
); } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/flyout_top_level_properties.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/flyout_top_level_properties.tsx index 2a168904d4a2f..ce44f136f3555 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/flyout_top_level_properties.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/flyout_top_level_properties.tsx @@ -12,8 +12,9 @@ import { TRANSACTION_NAME, } from '../../../../../../../common/elasticsearch_fieldnames'; import { getNextEnvironmentUrlParam } from '../../../../../../../common/environment_filter_values'; +import { LatencyAggregationType } from '../../../../../../../common/latency_aggregation_types'; import { Transaction } from '../../../../../../../typings/es_schemas/ui/transaction'; -import { useApmParams } from '../../../../../../hooks/use_apm_params'; +import { useAnyOfApmParams } from '../../../../../../hooks/use_apm_params'; import { TransactionDetailLink } from '../../../../../shared/links/apm/transaction_detail_link'; import { ServiceLink } from '../../../../../shared/service_link'; import { StickyProperties } from '../../../../../shared/sticky_properties'; @@ -23,9 +24,17 @@ interface Props { } export function FlyoutTopLevelProperties({ transaction }: Props) { - const { query } = useApmParams('/services/{serviceName}/transactions/view'); + const { query } = useAnyOfApmParams( + '/services/{serviceName}/transactions/view', + '/traces/explorer' + ); - const { latencyAggregationType, comparisonEnabled, offset } = query; + const latencyAggregationType = + ('latencyAggregationType' in query && query.latencyAggregationType) || + LatencyAggregationType.avg; + const serviceGroup = ('serviceGroup' in query && query.serviceGroup) || ''; + + const { comparisonEnabled, offset } = query; if (!transaction) { return null; @@ -45,7 +54,7 @@ export function FlyoutTopLevelProperties({ transaction }: Props) { val: ( ), diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx index 19d487ce35fb1..ecf121b777ff5 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx @@ -18,11 +18,12 @@ import { getNextEnvironmentUrlParam } from '../../../../../../../../common/envir import { NOT_AVAILABLE_LABEL } from '../../../../../../../../common/i18n'; import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/transaction'; -import { useApmParams } from '../../../../../../../hooks/use_apm_params'; +import { useAnyOfApmParams } from '../../../../../../../hooks/use_apm_params'; import { BackendLink } from '../../../../../../shared/backend_link'; import { TransactionDetailLink } from '../../../../../../shared/links/apm/transaction_detail_link'; import { ServiceLink } from '../../../../../../shared/service_link'; import { StickyProperties } from '../../../../../../shared/sticky_properties'; +import { LatencyAggregationType } from '../../../../../../../../common/latency_aggregation_types'; interface Props { span: Span; @@ -30,9 +31,17 @@ interface Props { } export function StickySpanProperties({ span, transaction }: Props) { - const { query } = useApmParams('/services/{serviceName}/transactions/view'); - const { environment, latencyAggregationType, comparisonEnabled, offset } = - query; + const { query } = useAnyOfApmParams( + '/services/{serviceName}/transactions/view', + '/traces/explorer' + ); + const { environment, comparisonEnabled, offset } = query; + + const latencyAggregationType = + ('latencyAggregationType' in query && query.latencyAggregationType) || + LatencyAggregationType.avg; + + const serviceGroup = ('serviceGroup' in query && query.serviceGroup) || ''; const trackEvent = useUiTracker(); @@ -56,6 +65,7 @@ export function StickySpanProperties({ span, transaction }: Props) { agentName={transaction.agent.name} query={{ ...query, + serviceGroup, environment: nextEnvironment, }} serviceName={transaction.service.name} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx index d372cec9ce16d..1b16840e566cd 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx @@ -24,7 +24,7 @@ import { ColdStartBadge } from './badge/cold_start_badge'; import { IWaterfallSpanOrTransaction } from './waterfall_helpers/waterfall_helpers'; import { FailureBadge } from './failure_badge'; import { useApmRouter } from '../../../../../../hooks/use_apm_router'; -import { useApmParams } from '../../../../../../hooks/use_apm_params'; +import { useAnyOfApmParams } from '../../../../../../hooks/use_apm_params'; type ItemType = 'transaction' | 'span' | 'error'; @@ -258,12 +258,16 @@ function RelatedErrors({ }) { const apmRouter = useApmRouter(); const theme = useTheme(); - const { query } = useApmParams('/services/{serviceName}/transactions/view'); + const { query } = useAnyOfApmParams( + '/services/{serviceName}/transactions/view', + '/traces/explorer' + ); const href = apmRouter.link(`/services/{serviceName}/errors`, { path: { serviceName: item.doc.service.name }, query: { ...query, + serviceGroup: '', kuery: `${TRACE_ID} : "${item.doc.trace.id}" and ${TRANSACTION_ID} : "${item.doc.transaction?.id}"`, }, }); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx index 82d85891e36da..a10518ab58e4c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx @@ -17,7 +17,6 @@ import { simpleTrace, traceChildStartBeforeParent, traceWithErrors, - urlParams as testUrlParams, } from './waterfall_container.stories.data'; import type { ApmPluginContextValue } from '../../../../../context/apm_plugin/apm_plugin_context'; @@ -50,48 +49,87 @@ const stories: Meta = { }; export default stories; -export const Example: Story = ({ urlParams, waterfall }) => { - return ; +export const Example: Story = ({ + serviceName, + waterfallItemId, + waterfall, +}) => { + return ( + + ); }; Example.args = { - urlParams: testUrlParams, waterfall: getWaterfall(simpleTrace, '975c8d5bfd1dd20b'), }; -export const WithErrors: Story = ({ urlParams, waterfall }) => { - return ; +export const WithErrors: Story = ({ + serviceName, + waterfallItemId, + waterfall, +}) => { + return ( + + ); }; WithErrors.args = { - urlParams: testUrlParams, waterfall: getWaterfall(traceWithErrors, '975c8d5bfd1dd20b'), }; export const ChildStartsBeforeParent: Story = ({ - urlParams, + serviceName, + waterfallItemId, waterfall, }) => { - return ; + return ( + + ); }; ChildStartsBeforeParent.args = { - urlParams: testUrlParams, waterfall: getWaterfall(traceChildStartBeforeParent, '975c8d5bfd1dd20b'), }; -export const InferredSpans: Story = ({ urlParams, waterfall }) => { - return ; +export const InferredSpans: Story = ({ + serviceName, + waterfallItemId, + waterfall, +}) => { + return ( + + ); }; InferredSpans.args = { - urlParams: testUrlParams, waterfall: getWaterfall(inferredSpans, 'f2387d37260d00bd'), }; export const ManyChildrenWithSameLength: Story = ({ - urlParams, + serviceName, + waterfallItemId, waterfall, }) => { - return ; + return ( + + ); }; ManyChildrenWithSameLength.args = { - urlParams: testUrlParams, waterfall: getWaterfall(manyChildrenWithSameLength, '9a7f717439921d39'), }; diff --git a/x-pack/plugins/apm/public/components/routing/app_root.tsx b/x-pack/plugins/apm/public/components/routing/app_root.tsx index b82a44a598249..fe2491851b7ae 100644 --- a/x-pack/plugins/apm/public/components/routing/app_root.tsx +++ b/x-pack/plugins/apm/public/components/routing/app_root.tsx @@ -20,6 +20,7 @@ import { HeaderMenuPortal, InspectorContextProvider, } from '@kbn/observability-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; import { ScrollToTopOnPathChange } from '../app/main/scroll_to_top_on_path_change'; import { AnomalyDetectionJobsContextProvider } from '../../context/anomaly_detection_jobs/anomaly_detection_jobs_context'; import { @@ -39,6 +40,8 @@ import { TrackPageview } from './track_pageview'; import { RedirectWithDefaultEnvironment } from '../shared/redirect_with_default_environment'; import { RedirectWithOffset } from '../shared/redirect_with_offset'; +const storage = new Storage(localStorage); + export function ApmAppRoot({ apmPluginContextValue, pluginsStart, @@ -58,7 +61,7 @@ export function ApmAppRoot({ role="main" > - + diff --git a/x-pack/plugins/apm/public/components/routing/home/index.tsx b/x-pack/plugins/apm/public/components/routing/home/index.tsx index 5955b5bb5d909..a28f467c760fb 100644 --- a/x-pack/plugins/apm/public/components/routing/home/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/index.tsx @@ -5,40 +5,49 @@ * 2.0. */ import { i18n } from '@kbn/i18n'; -import { Outlet } from '@kbn/typed-react-router-config'; +import { Outlet, Route } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React, { ComponentProps } from 'react'; import { toBooleanRt } from '@kbn/io-ts-utils'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { environmentRt } from '../../../../common/environment_rt'; +import { TraceSearchType } from '../../../../common/trace_explorer'; import { BackendDetailOverview } from '../../app/backend_detail_overview'; import { BackendInventory } from '../../app/backend_inventory'; import { Breadcrumb } from '../../app/breadcrumb'; import { ServiceInventory } from '../../app/service_inventory'; import { ServiceMapHome } from '../../app/service_map'; import { TraceOverview } from '../../app/trace_overview'; +import { TraceExplorer } from '../../app/trace_explorer'; +import { TopTracesOverview } from '../../app/top_traces_overview'; import { ApmMainTemplate } from '../templates/apm_main_template'; import { RedirectToBackendOverviewRouteView } from './redirect_to_backend_overview_route_view'; import { ServiceGroupTemplate } from '../templates/service_group_template'; import { ServiceGroupsRedirect } from '../service_groups_redirect'; import { RedirectTo } from '../redirect_to'; import { offsetRt } from '../../../../common/offset_rt'; +import { TransactionTab } from '../../app/transaction_details/waterfall_with_summary/transaction_tabs'; -function page({ +function page< + TPath extends string, + TChildren extends Record | undefined = undefined +>({ path, element, + children, title, showServiceGroupSaveButton = false, }: { path: TPath; element: React.ReactElement; + children?: TChildren; title: string; showServiceGroupSaveButton?: boolean; }): Record< TPath, { element: React.ReactElement; - } + } & (TChildren extends Record ? { children: TChildren } : {}) > { return { [path]: { @@ -52,8 +61,9 @@ function page({ ), + children, }, - } as Record }>; + } as any; } function serviceGroupPage({ @@ -155,19 +165,58 @@ export const home = { element: , serviceGroupContextTab: 'service-inventory', }), - ...page({ - path: '/traces', - title: i18n.translate('xpack.apm.views.traceOverview.title', { - defaultMessage: 'Traces', - }), - element: , - }), ...serviceGroupPage({ path: '/service-map', title: ServiceMapTitle, element: , serviceGroupContextTab: 'service-map', }), + ...page({ + path: '/traces', + title: i18n.translate('xpack.apm.views.traceOverview.title', { + defaultMessage: 'Traces', + }), + element: ( + + + + ), + children: { + '/traces/explorer': { + element: , + params: t.type({ + query: t.type({ + query: t.string, + type: t.union([ + t.literal(TraceSearchType.kql), + t.literal(TraceSearchType.eql), + ]), + waterfallItemId: t.string, + traceId: t.string, + transactionId: t.string, + detailTab: t.union([ + t.literal(TransactionTab.timeline), + t.literal(TransactionTab.metadata), + t.literal(TransactionTab.logs), + ]), + }), + }), + defaults: { + query: { + query: '', + type: TraceSearchType.kql, + waterfallItemId: '', + traceId: '', + transactionId: '', + detailTab: TransactionTab.timeline, + }, + }, + }, + '/traces': { + element: , + }, + }, + }), '/backends': { element: , params: t.partial({ diff --git a/x-pack/plugins/apm/public/components/shared/date_picker/apm_date_picker.tsx b/x-pack/plugins/apm/public/components/shared/date_picker/apm_date_picker.tsx new file mode 100644 index 0000000000000..96d9ae768f352 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/date_picker/apm_date_picker.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useApmParams } from '../../../hooks/use_apm_params'; + +import { DatePicker } from '.'; +import { useTimeRangeId } from '../../../context/time_range_id/use_time_range_id'; +import { + toBoolean, + toNumber, +} from '../../../context/url_params_context/helpers'; + +export function ApmDatePicker() { + const { query } = useApmParams('/*'); + + if (!('rangeFrom' in query)) { + throw new Error('range not available in route parameters'); + } + + const { + rangeFrom, + rangeTo, + refreshPaused: refreshPausedFromUrl = 'true', + refreshInterval: refreshIntervalFromUrl = '0', + } = query; + + const refreshPaused = toBoolean(refreshPausedFromUrl); + + const refreshInterval = toNumber(refreshIntervalFromUrl); + + const { incrementTimeRangeId } = useTimeRangeId(); + + return ( + { + incrementTimeRangeId(); + }} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/completer.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/completer.ts new file mode 100644 index 0000000000000..5128c288fefc2 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/completer.ts @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Editor, IEditSession, TokenInfo as AceTokenInfo } from 'brace'; +import { Maybe } from '../../../../typings/common'; +import { EQLCodeEditorSuggestionType } from './constants'; +import { EQLToken } from './tokens'; +import { + EQLCodeEditorSuggestion, + EQLCodeEditorSuggestionCallback, + EQLCodeEditorSuggestionRequest, +} from './types'; + +type TokenInfo = AceTokenInfo & { + type: string; + index: number; +}; + +export class EQLCodeEditorCompleter { + callback?: EQLCodeEditorSuggestionCallback; + + private async getCompletionsAsync( + session: IEditSession, + position: { row: number; column: number }, + prefix: string | undefined + ): Promise { + const token = session.getTokenAt( + position.row, + position.column + ) as Maybe; + const tokensInLine = session.getTokens(position.row) as TokenInfo[]; + + function withWhitespace( + vals: EQLCodeEditorSuggestion[], + options: { + before?: string; + after?: string; + } = {} + ) { + const { after = ' ' } = options; + let { before = ' ' } = options; + + if ( + before && + (token?.value.match(/^\s+$/) || (token && token.type !== 'text')) + ) { + before = before.trimLeft(); + } + + return vals.map((val) => { + const suggestion = typeof val === 'string' ? { value: val } : val; + const valueAsString = suggestion.value; + + return { + ...suggestion, + caption: valueAsString, + value: [before, valueAsString, after].join(''), + }; + }); + } + + if ( + position.row === 0 && + (!token || token.index === 0) && + 'sequence by'.includes(prefix || '') + ) { + return withWhitespace(['sequence by'], { + before: '', + after: ' ', + }); + } + + const previousTokens = tokensInLine + .slice(0, token ? tokensInLine.indexOf(token) : tokensInLine.length) + .reverse(); + + const completedEqlToken = previousTokens.find((t) => + t.type.startsWith('eql.') + ); + + switch (completedEqlToken?.type) { + case undefined: + return [ + ...withWhitespace(['['], { before: '', after: ' ' }), + ...(position.row > 2 + ? withWhitespace(['until'], { before: '', after: ' [ ' }) + : []), + ]; + + case EQLToken.Sequence: + return withWhitespace( + await this.getExternalSuggestions({ + type: EQLCodeEditorSuggestionType.Field, + }), + { + after: '\n\t[ ', + } + ); + + case EQLToken.SequenceItemStart: + return withWhitespace( + [ + ...(await this.getExternalSuggestions({ + type: EQLCodeEditorSuggestionType.EventType, + })), + 'any', + ], + { after: ' where ' } + ); + + case EQLToken.EventType: + return withWhitespace(['where']); + + case EQLToken.Where: + case EQLToken.LogicalOperator: + return [ + ...withWhitespace( + await this.getExternalSuggestions({ + type: EQLCodeEditorSuggestionType.Field, + }) + ), + ...withWhitespace(['true', 'false'], { after: ' ]\n\t' }), + ]; + + case EQLToken.BoolCondition: + return withWhitespace([']'], { after: '\n\t' }); + + case EQLToken.Operator: + case EQLToken.InOperator: + const field = + previousTokens?.find((t) => t.type === EQLToken.Field)?.value ?? ''; + + const hasStartedValueLiteral = + !!prefix?.trim() || token?.value.trim() === '"'; + + return withWhitespace( + await this.getExternalSuggestions({ + type: EQLCodeEditorSuggestionType.Value, + field, + value: prefix ?? '', + }), + { before: hasStartedValueLiteral ? '' : ' "', after: '" ' } + ); + + case EQLToken.Value: + return [ + ...withWhitespace([']'], { after: '\n\t' }), + ...withWhitespace(['and', 'or']), + ]; + } + + return []; + } + + private async getExternalSuggestions( + request: EQLCodeEditorSuggestionRequest + ): Promise { + if (this.callback) { + return this.callback(request); + } + return []; + } + + getCompletions( + _: Editor, + session: IEditSession, + position: { row: number; column: number }, + prefix: string | undefined, + cb: (err: Error | null, suggestions?: EQLCodeEditorSuggestion[]) => void + ) { + this.getCompletionsAsync(session, position, prefix) + .then((suggestions) => { + cb( + null, + suggestions.map((sugg) => { + const suggestion = + typeof sugg === 'string' + ? { value: sugg, score: 1000 } + : { score: 1000, ...sugg }; + + return suggestion; + }) + ); + }) + .catch(cb); + } + + setSuggestionCb(cb?: EQLCodeEditorSuggestionCallback) { + this.callback = cb; + } +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/constants.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/constants.ts new file mode 100644 index 0000000000000..5fe28cb602c49 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const EQL_MODE_NAME = 'ace/mode/eql'; +export const EQL_THEME_NAME = 'ace/theme/eql'; + +export enum EQLCodeEditorSuggestionType { + EventType = 'eventType', + Field = 'field', + Value = 'value', +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_highlight_rules.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_highlight_rules.ts new file mode 100644 index 0000000000000..8d04036451bb0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_highlight_rules.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import 'brace/ext/language_tools'; +import { acequire } from 'brace'; +import { EQLToken } from './tokens'; + +const TextHighlightRules = acequire( + 'ace/mode/text_highlight_rules' +).TextHighlightRules; + +export class EQLHighlightRules extends TextHighlightRules { + constructor() { + super(); + + const fieldNameOrValueRegex = /((?:[^\s]+)|(?:".*?"))/; + const operatorRegex = /(:|==|>|>=|<|<=|!=)/; + + const sequenceItemEnd = { + token: EQLToken.SequenceItemEnd, + regex: /(\])/, + next: 'start', + }; + + this.$rules = { + start: [ + { + token: EQLToken.Sequence, + regex: /(sequence by)/, + next: 'field', + }, + { + token: EQLToken.SequenceItemStart, + regex: /(\[)/, + next: 'sequence_item', + }, + { + token: EQLToken.Until, + regex: /(until)/, + next: 'start', + }, + ], + field: [ + { + token: EQLToken.Field, + regex: fieldNameOrValueRegex, + next: 'start', + }, + ], + sequence_item: [ + { + token: EQLToken.EventType, + regex: fieldNameOrValueRegex, + next: 'where', + }, + ], + sequence_item_end: [sequenceItemEnd], + where: [ + { + token: EQLToken.Where, + regex: /(where)/, + next: 'condition', + }, + ], + condition: [ + { + token: EQLToken.BoolCondition, + regex: /(true|false)/, + next: 'sequence_item_end', + }, + { + token: EQLToken.Field, + regex: fieldNameOrValueRegex, + next: 'comparison_operator', + }, + ], + comparison_operator: [ + { + token: EQLToken.Operator, + regex: operatorRegex, + next: 'value_or_value_list', + }, + ], + value_or_value_list: [ + { + token: EQLToken.Value, + regex: /("([^"]+)")|([\d+\.]+)|(true|false|null)/, + next: 'logical_operator_or_sequence_item_end', + }, + { + token: EQLToken.InOperator, + regex: /(in)/, + next: 'value_list', + }, + ], + logical_operator_or_sequence_item_end: [ + { + token: EQLToken.LogicalOperator, + regex: /(and|or|not)/, + next: 'condition', + }, + sequenceItemEnd, + ], + value_list: [ + { + token: EQLToken.ValueListStart, + regex: /(\()/, + next: 'value_list_item', + }, + ], + value_list_item: [ + { + token: EQLToken.Value, + regex: fieldNameOrValueRegex, + next: 'comma', + }, + ], + comma: [ + { + token: EQLToken.Comma, + regex: /,/, + next: 'value_list_item_or_end', + }, + ], + value_list_item_or_end: [ + { + token: EQLToken.Value, + regex: fieldNameOrValueRegex, + next: 'comma', + }, + { + token: EQLToken.ValueListEnd, + regex: /\)/, + next: 'logical_operator_or_sequence_item_end', + }, + ], + }; + + this.normalizeRules(); + } +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_mode.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_mode.ts new file mode 100644 index 0000000000000..36f923b3210c0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/eql_mode.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TextMode as TextModeInterface, acequire } from 'brace'; +import { EQL_MODE_NAME } from './constants'; +import { EQLHighlightRules } from './eql_highlight_rules'; + +type ITextMode = new () => TextModeInterface; + +const TextMode = acequire('ace/mode/text').Mode as ITextMode; + +export class EQLMode extends TextMode { + HighlightRules: typeof EQLHighlightRules; + $id: string; + constructor() { + super(); + this.$id = EQL_MODE_NAME; + this.HighlightRules = EQLHighlightRules; + } +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/index.tsx b/x-pack/plugins/apm/public/components/shared/eql_code_editor/index.tsx new file mode 100644 index 0000000000000..8a8cd1b0dff41 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/index.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import 'brace/ext/language_tools'; +import { last } from 'lodash'; +import React, { useRef } from 'react'; +import { EuiCodeEditor } from '@kbn/es-ui-shared-plugin/public'; +import { EQLCodeEditorCompleter } from './completer'; +import { EQL_THEME_NAME } from './constants'; +import { EQLMode } from './eql_mode'; +import './theme'; +import { EQLCodeEditorProps } from './types'; + +export function EQLCodeEditor(props: EQLCodeEditorProps) { + const { + showGutter = false, + setOptions, + getSuggestions, + ...restProps + } = props; + + const completer = useRef(new EQLCodeEditorCompleter()); + const eqlMode = useRef(new EQLMode()); + + completer.current.setSuggestionCb(getSuggestions); + + const options = { + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + wrap: true, + ...setOptions, + }; + + return ( +
+ { + if (editor) { + editor.editor.completers = [completer.current]; + } + }} + {...restProps} + /> +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/lazily_loaded_code_editor.tsx b/x-pack/plugins/apm/public/components/shared/eql_code_editor/lazily_loaded_code_editor.tsx new file mode 100644 index 0000000000000..3432331aaa062 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/lazily_loaded_code_editor.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { once } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { EQLCodeEditorProps } from './types'; + +const loadEqlCodeEditor = once(() => import('.').then((m) => m.EQLCodeEditor)); + +type EQLCodeEditorComponentType = Awaited>; + +export function LazilyLoadedEQLCodeEditor(props: EQLCodeEditorProps) { + const [EQLCodeEditor, setEQLCodeEditor] = useState< + EQLCodeEditorComponentType | undefined + >(); + + useEffect(() => { + loadEqlCodeEditor().then((editor) => { + setEQLCodeEditor(() => { + return editor; + }); + }); + }, []); + + return EQLCodeEditor ? ( + + ) : ( + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/theme.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/theme.ts new file mode 100644 index 0000000000000..2dfbecf63f428 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/theme.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { euiLightVars as theme } from '@kbn/ui-theme'; +import { EQL_THEME_NAME } from './constants'; + +// @ts-expect-error +ace.define( + EQL_THEME_NAME, + ['require', 'exports', 'module', 'ace/lib/dom'], + function (acequire: any, exports: any) { + exports.isDark = false; + exports.cssClass = 'ace-eql'; + exports.cssText = ` + .ace-eql .ace_scroller { + background-color: transparent; + } + .ace-eql .ace_marker-layer .ace_selection { + background: rgb(181, 213, 255); + } + .ace-eql .ace_placeholder { + color: ${theme.euiTextSubduedColor}; + padding: 0; + } + .ace-eql .ace_sequence, + .ace-eql .ace_where, + .ace-eql .ace_until { + color: ${theme.euiColorDarkShade}; + } + .ace-eql .ace_sequence_item_start, + .ace-eql .ace_sequence_item_end, + .ace-eql .ace_operator, + .ace-eql .ace_logical_operator { + color: ${theme.euiColorMediumShade}; + } + .ace-eql .ace_value, + .ace-eql .ace_bool_condition { + color: ${theme.euiColorAccent}; + } + .ace-eql .ace_event_type, + .ace-eql .ace_field { + color: ${theme.euiColorPrimaryText}; + } + // .ace-eql .ace_gutter { + // color: #333; + // } + .ace-eql .ace_print-margin { + width: 1px; + background: #e8e8e8; + } + .ace-eql .ace_fold { + background-color: #6B72E6; + } + .ace-eql .ace_cursor { + color: black; + } + .ace-eql .ace_invisible { + color: rgb(191, 191, 191); + } + .ace-eql .ace_marker-layer .ace_selection { + background: rgb(181, 213, 255); + } + .ace-eql.ace_multiselect .ace_selection.ace_start { + box-shadow: 0 0 3px 0px white; + } + .ace-eql .ace_marker-layer .ace_step { + background: rgb(252, 255, 0); + } + .ace-eql .ace_marker-layer .ace_stack { + background: rgb(164, 229, 101); + } + .ace-eql .ace_marker-layer .ace_bracket { + margin: -1px 0 0 -1px; + border: 1px solid rgb(192, 192, 192); + } + .ace-eql .ace_marker-layer .ace_selected-word { + background: rgb(250, 250, 255); + border: 1px solid rgb(200, 200, 250); + } + .ace-eql .ace_indent-guide { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y; + }`; + + const dom = acequire('../lib/dom'); + dom.importCssString(exports.cssText, exports.cssClass); + } +); diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/tokens.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/tokens.ts new file mode 100644 index 0000000000000..5525d8318afaf --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/tokens.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum EQLToken { + Sequence = 'eql.sequence', + SequenceItemStart = 'eql.sequence_item_start', + SequenceItemEnd = 'eql.sequence_item_end', + Until = 'eql.until', + Field = 'eql.field', + EventType = 'eql.event_type', + Where = 'eql.where', + BoolCondition = 'eql.bool_condition', + Operator = 'eql.operator', + Value = 'eql.value', + LogicalOperator = 'eql.logical_operator', + InOperator = 'eql.in_operator', + ValueListStart = 'eql.value_list_start', + ValueListItem = 'eql.value_list_item', + ValueListEnd = 'eql.value_list_end', + Comma = 'eql.comma', +} diff --git a/x-pack/plugins/apm/public/components/shared/eql_code_editor/types.ts b/x-pack/plugins/apm/public/components/shared/eql_code_editor/types.ts new file mode 100644 index 0000000000000..250cda155ea18 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/eql_code_editor/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCodeEditorProps } from '@kbn/es-ui-shared-plugin/public'; +import { EQLCodeEditorSuggestionType } from './constants'; + +export type EQLCodeEditorSuggestion = + | string + | { value: string; score?: number }; + +export type EQLCodeEditorSuggestionRequest = + | { + type: + | EQLCodeEditorSuggestionType.EventType + | EQLCodeEditorSuggestionType.Field; + } + | { type: EQLCodeEditorSuggestionType.Value; field: string; value: string }; + +export type EQLCodeEditorSuggestionCallback = ( + request: EQLCodeEditorSuggestionRequest +) => Promise; + +export type EQLCodeEditorProps = Omit< + EuiCodeEditorProps, + 'mode' | 'theme' | 'setOptions' +> & { + getSuggestions?: EQLCodeEditorSuggestionCallback; + setOptions?: EuiCodeEditorProps['setOptions']; +}; diff --git a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx index 4df9bc0447aa1..e052688d73474 100644 --- a/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/discover_links/discover_link.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import rison, { RisonValue } from 'rison-node'; import url from 'url'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; +import { APM_STATIC_DATA_VIEW_ID } from '../../../../../common/data_view_constants'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { getTimepickerRisonData } from '../rison_helpers'; @@ -46,7 +46,7 @@ export const getDiscoverHref = ({ _g: getTimepickerRisonData(location.search), _a: { ...query._a, - index: APM_STATIC_INDEX_PATTERN_ID, + index: APM_STATIC_DATA_VIEW_ID, }, }; diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.tsx index f1d82e2e307e1..c71e21bee2c38 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar.tsx @@ -13,11 +13,8 @@ import { EuiSpacer, } from '@elastic/eui'; import React from 'react'; -import { useTimeRangeId } from '../../context/time_range_id/use_time_range_id'; -import { toBoolean, toNumber } from '../../context/url_params_context/helpers'; -import { useApmParams } from '../../hooks/use_apm_params'; import { useBreakpoints } from '../../hooks/use_breakpoints'; -import { DatePicker } from './date_picker'; +import { ApmDatePicker } from './date_picker/apm_date_picker'; import { KueryBar } from './kuery_bar'; import { TimeComparison } from './time_comparison'; import { TransactionTypeSelect } from './transaction_type_select'; @@ -31,39 +28,6 @@ interface Props { kueryBarBoolFilter?: QueryDslQueryContainer[]; } -function ApmDatePicker() { - const { query } = useApmParams('/*'); - - if (!('rangeFrom' in query)) { - throw new Error('range not available in route parameters'); - } - - const { - rangeFrom, - rangeTo, - refreshPaused: refreshPausedFromUrl = 'true', - refreshInterval: refreshIntervalFromUrl = '0', - } = query; - - const refreshPaused = toBoolean(refreshPausedFromUrl); - - const refreshInterval = toNumber(refreshIntervalFromUrl); - - const { incrementTimeRangeId } = useTimeRangeId(); - - return ( - { - incrementTimeRangeId(); - }} - /> - ); -} - export function SearchBar({ hidden = false, showKueryBar = true, diff --git a/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx index 67209c23324ad..a8f5ab1b149fb 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx @@ -11,6 +11,8 @@ import type { ObservabilityRuleTypeRegistry } from '@kbn/observability-plugin/pu import { MapsStartApi } from '@kbn/maps-plugin/public'; import { ObservabilityPublicStart } from '@kbn/observability-plugin/public'; import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { ApmPluginSetupDeps } from '../../plugin'; import { ConfigSchema } from '../..'; @@ -22,6 +24,8 @@ export interface ApmPluginContextValue { plugins: ApmPluginSetupDeps & { maps?: MapsStartApi }; observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; observability: ObservabilityPublicStart; + dataViews: DataViewsPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } export const ApmPluginContext = createContext({} as ApmPluginContextValue); diff --git a/x-pack/plugins/apm/public/hooks/use_apm_route_path.ts b/x-pack/plugins/apm/public/hooks/use_apm_route_path.ts new file mode 100644 index 0000000000000..b659e26610490 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_apm_route_path.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useRoutePath, PathsOf } from '@kbn/typed-react-router-config'; +import { ApmRoutes } from '../components/routing/apm_route_config'; + +export function useApmRoutePath() { + const path = useRoutePath(); + + return path as PathsOf; +} diff --git a/x-pack/plugins/apm/public/hooks/use_static_data_view.ts b/x-pack/plugins/apm/public/hooks/use_static_data_view.ts new file mode 100644 index 0000000000000..4281f7cac83c8 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_static_data_view.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import useAsync from 'react-use/lib/useAsync'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; +import { APM_STATIC_DATA_VIEW_ID } from '../../common/data_view_constants'; + +export function useStaticDataView() { + const { dataViews } = useApmPluginContext(); + + return useAsync(() => dataViews.get(APM_STATIC_DATA_VIEW_ID)); +} diff --git a/x-pack/plugins/apm/public/hooks/use_trace_explorer_enabled_setting.ts b/x-pack/plugins/apm/public/hooks/use_trace_explorer_enabled_setting.ts new file mode 100644 index 0000000000000..51fd5f32875da --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_trace_explorer_enabled_setting.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { apmTraceExplorerTab } from '@kbn/observability-plugin/common'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; + +export function useTraceExplorerEnabledSetting() { + const { core } = useApmPluginContext(); + + return core.uiSettings.get(apmTraceExplorerTab, false); +} diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 2ee87571cb719..53f74b99c486e 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -47,6 +47,7 @@ import type { import type { SecurityPluginStart } from '@kbn/security-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { enableServiceGroups } from '@kbn/observability-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { registerApmAlerts } from './components/alerting/register_apm_alerts'; import { getApmEnrollmentFlyoutData, @@ -88,6 +89,8 @@ export interface ApmPluginStartDeps { fleet?: FleetStart; security?: SecurityPluginStart; spaces?: SpacesPluginStart; + dataViews: DataViewsPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } const servicesTitle = i18n.translate('xpack.apm.navigation.servicesTitle', { diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts index 36d0de8ccde96..241814e433b62 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts @@ -36,7 +36,7 @@ export async function callAsyncWithDebug({ requestParams: Record; operationName: string; isCalledWithInternalUser: boolean; // only allow inspection of queries that were retrieved with credentials of the end user -}) { +}): Promise { if (!debug) { return cb(); } diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts index 1156930018991..5075d7dfc1237 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts @@ -11,10 +11,10 @@ export function cancelEsRequestOnAbort>( promise: T, request: KibanaRequest, controller: AbortController -) { +): T { const subscription = request.events.aborted$.subscribe(() => { controller.abort(); }); - return promise.finally(() => subscription.unsubscribe()); + return promise.finally(() => subscription.unsubscribe()) as T; } diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 0fcf02f95ac0e..f8613bf8c9e6f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -6,6 +6,7 @@ */ import type { + EqlSearchRequest, TermsEnumRequest, TermsEnumResponse, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -30,7 +31,10 @@ import { getDebugTitle, } from '../call_async_with_debug'; import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort'; -import { unpackProcessorEvents } from './unpack_processor_events'; +import { + unpackProcessorEvents, + processorEventsToIndex, +} from './unpack_processor_events'; export type APMEventESSearchRequest = Omit & { apm: { @@ -46,6 +50,10 @@ export type APMEventESTermsEnumRequest = Omit & { apm: { events: ProcessorEvent[] }; }; +export type APMEventEqlSearchRequest = Omit & { + apm: { events: ProcessorEvent[] }; +}; + // These keys shoul all be `ProcessorEvent.x`, but until TypeScript 4.2 we're inlining them here. // See https://github.com/microsoft/TypeScript/issues/37888 type TypeOfProcessorEvent = { @@ -114,7 +122,7 @@ export class APMEventClient { this.esClient.search(searchParams, { signal: controller.signal, meta: true, - }), + }) as Promise, this.request, controller ); @@ -139,6 +147,48 @@ export class APMEventClient { }); } + async eqlSearch(operationName: string, params: APMEventEqlSearchRequest) { + const requestType = 'eql_search'; + const index = processorEventsToIndex(params.apm.events, this.indices); + + return callAsyncWithDebug({ + cb: () => { + const { apm, ...rest } = params; + + const eqlSearchPromise = withApmSpan(operationName, () => { + const controller = new AbortController(); + return cancelEsRequestOnAbort( + this.esClient.eql.search( + { + index, + ...rest, + }, + { signal: controller.signal, meta: true } + ), + this.request, + controller + ); + }); + + return unwrapEsResponse(eqlSearchPromise); + }, + getDebugMessage: () => ({ + body: getDebugBody({ + params, + requestType, + operationName, + }), + title: getDebugTitle(this.request), + }), + isCalledWithInternalUser: false, + debug: this.debug, + request: this.request, + requestType, + operationName, + requestParams: params, + }); + } + async termsEnum( operationName: string, params: APMEventESTermsEnumRequest diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts index 3fc6b40f9cfe7..3ef5a715e2c20 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts @@ -9,7 +9,6 @@ import { uniq, defaultsDeep, cloneDeep } from 'lodash'; import { ESSearchRequest, ESFilter } from '@kbn/core/types/elasticsearch'; import { PROCESSOR_EVENT } from '../../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../../common/processor_event'; -import { APMEventESSearchRequest, APMEventESTermsEnumRequest } from '.'; import { ApmIndicesConfig } from '../../../../routes/settings/apm_indices/get_apm_indices'; const processorEventIndexMap = { @@ -21,13 +20,24 @@ const processorEventIndexMap = { [ProcessorEvent.profile]: 'transaction', } as const; +export function processorEventsToIndex( + events: ProcessorEvent[], + indices: ApmIndicesConfig +) { + return uniq(events.map((event) => indices[processorEventIndexMap[event]])); +} + export function unpackProcessorEvents( - request: APMEventESSearchRequest | APMEventESTermsEnumRequest, + request: { + apm: { + events: ProcessorEvent[]; + }; + }, indices: ApmIndicesConfig ) { const { apm, ...params } = request; const events = uniq(apm.events); - const index = events.map((event) => indices[processorEventIndexMap[event]]); + const index = processorEventsToIndex(events, indices); const withFilterForProcessorEvent: ESSearchRequest & { body: { query: { bool: { filter: ESFilter[] } } }; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts index 522fd5c078af3..900f9afe66653 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts @@ -74,7 +74,11 @@ export async function createInternalESClient({ ): Promise> => { return callEs(operationName, { requestType: 'search', - cb: (signal) => asInternalUser.search(params, { signal, meta: true }), + cb: (signal) => + asInternalUser.search(params, { + signal, + meta: true, + }) as Promise<{ body: any }>, params, }); }, diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts index 9f22eaa6b9c30..4a0878a5dc135 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsErrorHelpers } from '@kbn/core/server'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../../common/index_pattern_constants'; +import { APM_STATIC_DATA_VIEW_ID } from '../../../common/data_view_constants'; import { hasHistoricalAgentData } from '../historical_data/has_historical_agent_data'; import { Setup } from '../../lib/helpers/setup_request'; import { APMRouteHandlerResources } from '../typings'; @@ -55,7 +55,7 @@ export async function createStaticDataView({ 'index-pattern', getApmDataViewAttributes(apmDataViewTitle), { - id: APM_STATIC_INDEX_PATTERN_ID, + id: APM_STATIC_DATA_VIEW_ID, overwrite: forceOverwrite, namespace: spaceId, } @@ -86,7 +86,7 @@ async function getForceOverwrite({ const existingDataView = await savedObjectsClient.get( 'index-pattern', - APM_STATIC_INDEX_PATTERN_ID + APM_STATIC_DATA_VIEW_ID ); // if the existing data view does not matches the new one, force an update diff --git a/x-pack/plugins/apm/server/routes/service_map/transform_service_map_responses.ts b/x-pack/plugins/apm/server/routes/service_map/transform_service_map_responses.ts index 0cdbdc26c69df..4d4522461252b 100644 --- a/x-pack/plugins/apm/server/routes/service_map/transform_service_map_responses.ts +++ b/x-pack/plugins/apm/server/routes/service_map/transform_service_map_responses.ts @@ -115,7 +115,7 @@ export function transformServiceMapResponses(response: ServiceMapResponse) { ? anomalies.serviceAnomalies.find( (item) => item.serviceName === serviceName ) - : null; + : undefined; if (matchedServiceNodes.length) { return { @@ -158,9 +158,16 @@ export function transformServiceMapResponses(response: ServiceMapResponse) { const sourceData = getConnectionNode(connection.source); const targetData = getConnectionNode(connection.destination); + const label = + sourceData[SERVICE_NAME] + + ' to ' + + (targetData[SERVICE_NAME] || + targetData[SPAN_DESTINATION_SERVICE_RESOURCE]); + return { source: sourceData.id, target: targetData.id, + label, id: getConnectionId({ source: sourceData, destination: targetData }), sourceData, targetData, diff --git a/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts b/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts new file mode 100644 index 0000000000000..48ca80780d77f --- /dev/null +++ b/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + rangeQuery, + kqlQuery, + termsQuery, +} from '@kbn/observability-plugin/server'; +import { Environment } from '../../../common/environment_rt'; +import { Setup } from '../../lib/helpers/setup_request'; +import { TraceSearchType } from '../../../common/trace_explorer'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { environmentQuery } from '../../../common/utils/environment_query'; +import { + PARENT_ID, + PROCESSOR_EVENT, + TRACE_ID, + TRANSACTION_ID, + TRANSACTION_SAMPLED, +} from '../../../common/elasticsearch_fieldnames'; +import { asMutableArray } from '../../../common/utils/as_mutable_array'; + +export async function getTraceSamplesByQuery({ + setup, + start, + end, + environment, + query, + type, +}: { + setup: Setup; + start: number; + end: number; + environment: Environment; + query: string; + type: TraceSearchType; +}) { + const size = 500; + + let traceIds: string[] = []; + + if (type === TraceSearchType.kql) { + traceIds = + ( + await setup.apmEventClient.search('get_trace_ids_by_kql_query', { + apm: { + events: [ + ProcessorEvent.transaction, + ProcessorEvent.span, + ProcessorEvent.error, + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(query), + ], + }, + }, + aggs: { + traceId: { + terms: { + field: TRACE_ID, + execution_hint: 'map', + size, + }, + }, + }, + }, + }) + ).aggregations?.traceId.buckets.map((bucket) => bucket.key as string) ?? + []; + } else if (type === TraceSearchType.eql) { + traceIds = + ( + await setup.apmEventClient.eqlSearch('get_trace_ids_by_eql_query', { + apm: { + events: [ + ProcessorEvent.transaction, + ProcessorEvent.span, + ProcessorEvent.error, + ], + }, + body: { + size: 1000, + filter: { + bool: { + filter: [ + ...rangeQuery(start, end), + ...environmentQuery(environment), + ], + }, + }, + event_category_field: PROCESSOR_EVENT, + query, + }, + filter_path: 'hits.sequences.events._source.trace.id', + }) + ).hits?.sequences?.flatMap((sequence) => + sequence.events.map( + (event) => (event._source as { trace: { id: string } }).trace.id + ) + ) ?? []; + } + + if (!traceIds.length) { + return []; + } + + const traceSamplesResponse = await setup.apmEventClient.search( + 'get_trace_samples_by_trace_ids', + { + apm: { + events: [ProcessorEvent.transaction], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { + term: { + [TRANSACTION_SAMPLED]: true, + }, + }, + ...termsQuery(TRACE_ID, ...traceIds), + ...rangeQuery(start, end), + ], + must_not: [{ exists: { field: PARENT_ID } }], + }, + }, + aggs: { + transactionId: { + terms: { + field: TRANSACTION_ID, + size, + }, + aggs: { + latest: { + top_metrics: { + metrics: asMutableArray([{ field: TRACE_ID }] as const), + size: 1, + sort: { + '@timestamp': 'desc' as const, + }, + }, + }, + }, + }, + }, + }, + } + ); + + return ( + traceSamplesResponse.aggregations?.transactionId.buckets.map((bucket) => ({ + traceId: bucket.latest.top[0].metrics['trace.id'] as string, + transactionId: bucket.key as string, + })) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/traces/route.ts b/x-pack/plugins/apm/server/routes/traces/route.ts index afca332fea0b5..f47e85778e16d 100644 --- a/x-pack/plugins/apm/server/routes/traces/route.ts +++ b/x-pack/plugins/apm/server/routes/traces/route.ts @@ -6,9 +6,9 @@ */ import * as t from 'io-ts'; +import { TraceSearchType } from '../../../common/trace_explorer'; import { setupRequest } from '../../lib/helpers/setup_request'; -import { getTraceItems } from './get_trace_items'; -import { getTopTracesPrimaryStats } from './get_top_traces_primary_stats'; +import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, @@ -16,9 +16,11 @@ import { probabilityRt, rangeRt, } from '../default_api_types'; -import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; -import { getRootTransactionByTraceId } from '../transactions/get_transaction_by_trace'; import { getTransaction } from '../transactions/get_transaction'; +import { getRootTransactionByTraceId } from '../transactions/get_transaction_by_trace'; +import { getTopTracesPrimaryStats } from './get_top_traces_primary_stats'; +import { getTraceItems } from './get_trace_items'; +import { getTraceSamplesByQuery } from './get_trace_samples_by_query'; const tracesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces', @@ -135,9 +137,50 @@ const transactionByIdRoute = createApmServerRoute({ }, }); +const findTracesRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/traces/find', + params: t.type({ + query: t.intersection([ + rangeRt, + environmentRt, + t.type({ + query: t.string, + type: t.union([ + t.literal(TraceSearchType.kql), + t.literal(TraceSearchType.eql), + ]), + }), + ]), + }), + options: { + tags: ['access:apm'], + }, + handler: async ( + resources + ): Promise<{ + samples: Array<{ traceId: string; transactionId: string }>; + }> => { + const { start, end, environment, query, type } = resources.params.query; + + const setup = await setupRequest(resources); + + return { + samples: await getTraceSamplesByQuery({ + setup, + start, + end, + environment, + query, + type, + }), + }; + }, +}); + export const traceRouteRepository = { ...tracesByIdRoute, ...tracesRoute, ...rootTransactionByTraceIdRoute, ...transactionByIdRoute, + ...findTracesRoute, }; diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index 682d1979e12c7..e211e708c8ab3 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -13,7 +13,7 @@ import { } from '@kbn/home-plugin/server'; import { CloudSetup } from '@kbn/cloud-plugin/server'; import { APMConfig } from '..'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../common/index_pattern_constants'; +import { APM_STATIC_DATA_VIEW_ID } from '../../common/data_view_constants'; import { getApmDataViewAttributes } from '../routes/data_view/get_apm_data_view_attributes'; import { getApmDataViewTitle } from '../routes/data_view/get_apm_data_view_title'; import { ApmIndicesConfig } from '../routes/settings/apm_indices/get_apm_indices'; @@ -42,7 +42,7 @@ export const tutorialProvider = const dataViewTitle = getApmDataViewTitle(apmIndices); const savedObjects = [ { - id: APM_STATIC_INDEX_PATTERN_ID, + id: APM_STATIC_DATA_VIEW_ID, attributes: getApmDataViewAttributes(dataViewTitle), type: 'index-pattern', }, diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index e01b9ba3f9922..b380fa6c80745 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -17,6 +17,7 @@ export { defaultApmServiceEnvironment, apmServiceInventoryOptimizedSorting, apmProgressiveLoading, + apmTraceExplorerTab, } from './ui_settings_keys'; export { diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index 287fe541cc7b6..c49f95053bb8a 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -15,3 +15,4 @@ export const apmProgressiveLoading = 'observability:apmProgressiveLoading'; export const enableServiceGroups = 'observability:enableServiceGroups'; export const apmServiceInventoryOptimizedSorting = 'observability:apmServiceInventoryOptimizedSorting'; +export const apmTraceExplorerTab = 'observability:apmTraceExplorerTab'; diff --git a/x-pack/plugins/observability/common/utils/get_inspect_response.ts b/x-pack/plugins/observability/common/utils/get_inspect_response.ts index eaf467cf4c95e..dbd0cd68736db 100644 --- a/x-pack/plugins/observability/common/utils/get_inspect_response.ts +++ b/x-pack/plugins/observability/common/utils/get_inspect_response.ts @@ -71,7 +71,7 @@ function getStats({ }, }; - if (esResponse?.hits) { + if (esResponse?.hits?.hits) { stats.hits = { label: i18n.translate('xpack.observability.inspector.stats.hitsLabel', { defaultMessage: 'Hits', diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 5b21b07d1cea3..67a7a6bf0bd54 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -19,6 +19,7 @@ import { enableServiceGroups, apmServiceInventoryOptimizedSorting, enableNewSyntheticsView, + apmTraceExplorerTab, } from '../common/ui_settings_keys'; const technicalPreviewLabel = i18n.translate( @@ -187,4 +188,19 @@ export const uiSettings: Record[${technicalPreviewLabel}]` }, + }), + schema: schema.boolean(), + value: false, + requiresPageReload: true, + type: 'boolean', + }, }; diff --git a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts index 0e5fb6d1c98fd..0f265343b9d01 100644 --- a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts +++ b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts @@ -7,7 +7,7 @@ import { apm, ApmSynthtraceEsClient, timerange } from '@elastic/apm-synthtrace'; import expect from '@kbn/expect'; -import { APM_STATIC_INDEX_PATTERN_ID } from '@kbn/apm-plugin/common/index_pattern_constants'; +import { APM_STATIC_DATA_VIEW_ID } from '@kbn/apm-plugin/common/data_view_constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { SupertestReturnType } from '../../common/apm_api_supertest'; @@ -26,13 +26,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { function deleteDataView() { // return supertest.delete('/api/saved_objects//').set('kbn-xsrf', 'foo').expect(200) return supertest - .delete(`/api/saved_objects/index-pattern/${APM_STATIC_INDEX_PATTERN_ID}`) + .delete(`/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}`) .set('kbn-xsrf', 'foo') .expect(200); } function getDataView() { - return supertest.get(`/api/saved_objects/index-pattern/${APM_STATIC_INDEX_PATTERN_ID}`); + return supertest.get(`/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}`); } function getDataViewSuggestions(field: string) { diff --git a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts new file mode 100644 index 0000000000000..ebbdbda5e2e1e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts @@ -0,0 +1,307 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@elastic/apm-synthtrace'; +import expect from '@kbn/expect'; +import { TraceSearchType } from '@kbn/apm-plugin/common/trace_explorer'; +import { Environment } from '@kbn/apm-plugin/common/environment_rt'; +import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; +import { sortBy } from 'lodash'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { ApmApiError } from '../../common/apm_api_supertest'; + +type Instance = ReturnType['instance']>; +type Transaction = ReturnType; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2022-01-01T00:00:00.000Z').getTime(); + const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; + + // for EQL sequences to work, events need a slight time offset, + // as ES will sort based on @timestamp. to acommodate this offset + // we also add a little bit of a buffer to the requested time range + const endWithOffset = end + 100000; + + async function fetchTraceSamples({ + query, + type, + environment, + }: { + query: string; + type: TraceSearchType; + environment: Environment; + }) { + return apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/find`, + params: { + query: { + query, + type, + start: new Date(start).toISOString(), + end: new Date(endWithOffset).toISOString(), + environment, + }, + }, + }); + } + + function fetchTraces(samples: Array<{ traceId: string; transactionId: string }>) { + if (!samples.length) { + return []; + } + + return Promise.all( + samples.map(async ({ traceId }) => { + const response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}`, + params: { + path: { traceId }, + query: { + start: new Date(start).toISOString(), + end: new Date(endWithOffset).toISOString(), + }, + }, + }); + return response.body.traceDocs; + }) + ); + } + + registry.when( + 'Find traces when traces do not exist', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + it('handles empty state', async () => { + const response = await fetchTraceSamples({ + query: '', + type: TraceSearchType.kql, + environment: ENVIRONMENT_ALL.value, + }); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ + samples: [], + }); + }); + } + ); + + registry.when( + 'Find traces when traces exist', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + before(() => { + const java = apm.service('java', 'production', 'java').instance('java'); + + const node = apm.service('node', 'development', 'nodejs').instance('node'); + + const python = apm.service('python', 'production', 'python').instance('python'); + + function generateTrace( + timestamp: number, + order: Instance[], + db?: 'elasticsearch' | 'redis' + ) { + return order + .concat() + .reverse() + .reduce((prev, instance, index) => { + const invertedIndex = order.length - index - 1; + + const duration = 50; + const time = timestamp + invertedIndex * 10; + + const transaction: Transaction = instance + .transaction(`GET /${instance.fields['service.name']!}/api`) + .timestamp(time) + .duration(duration); + + if (prev) { + const next = order[invertedIndex + 1].fields['service.name']!; + transaction.children( + instance + .span(`GET ${next}/api`, 'external', 'http') + .destination(next) + .duration(duration) + .timestamp(time + 1) + .children(prev) + ); + } else if (db) { + transaction.children( + instance + .span(db, 'db', db) + .destination(db) + .duration(duration) + .timestamp(time + 1) + ); + } + + return transaction; + }, undefined)!; + } + + return synthtraceEsClient.index( + timerange(start, end) + .interval('15m') + .rate(1) + .generator((timestamp) => { + return [ + generateTrace(timestamp, [java, node]), + generateTrace(timestamp, [node, java], 'redis'), + generateTrace(timestamp, [python], 'redis'), + generateTrace(timestamp, [python, node, java], 'elasticsearch'), + generateTrace(timestamp, [java, python, node]), + ]; + }) + ); + }); + + describe('when using KQL', () => { + describe('and the query is empty', () => { + it('returns all trace samples', async () => { + const { + body: { samples }, + } = await fetchTraceSamples({ + query: '', + type: TraceSearchType.kql, + environment: 'ENVIRONMENT_ALL', + }); + + expect(samples.length).to.eql(5); + }); + }); + + describe('and query is set', () => { + it('returns the relevant traces', async () => { + const { + body: { samples }, + } = await fetchTraceSamples({ + query: 'span.destination.service.resource:elasticsearch', + type: TraceSearchType.kql, + environment: 'ENVIRONMENT_ALL', + }); + + expect(samples.length).to.eql(1); + }); + }); + }); + + describe('when using EQL', () => { + describe('and the query is invalid', () => { + it.skip('returns a 400', async function () { + try { + await fetchTraceSamples({ + query: '', + type: TraceSearchType.eql, + environment: 'ENVIRONMENT_ALL', + }); + this.fail(); + } catch (error: unknown) { + const apiError = error as ApmApiError; + expect(apiError.res.status).to.eql(400); + } + }); + }); + + describe('and the query is set', () => { + it('returns the correct trace samples for transaction sequences', async () => { + const { + body: { samples }, + } = await fetchTraceSamples({ + query: `sequence by trace.id + [ transaction where service.name == "java" ] + [ transaction where service.name == "node" ]`, + type: TraceSearchType.eql, + environment: 'ENVIRONMENT_ALL', + }); + + const traces = await fetchTraces(samples); + + expect(traces.length).to.eql(2); + + const mapped = traces.map((traceDocs) => { + return sortBy(traceDocs, '@timestamp') + .filter((doc) => doc.processor.event === 'transaction') + .map((doc) => doc.service.name); + }); + + expect(mapped).to.eql([ + ['java', 'node'], + ['java', 'python', 'node'], + ]); + }); + }); + + it('returns the correct trace samples for join sequences', async () => { + const { + body: { samples }, + } = await fetchTraceSamples({ + query: `sequence by trace.id + [ span where service.name == "java" ] by span.id + [ transaction where service.name == "python" ] by parent.id`, + type: TraceSearchType.eql, + environment: 'ENVIRONMENT_ALL', + }); + + const traces = await fetchTraces(samples); + + expect(traces.length).to.eql(1); + + const mapped = traces.map((traceDocs) => { + return sortBy(traceDocs, '@timestamp') + .filter((doc) => doc.processor.event === 'transaction') + .map((doc) => doc.service.name); + }); + + expect(mapped).to.eql([['java', 'python', 'node']]); + }); + + it('returns the correct trace samples for exit spans', async () => { + const { + body: { samples }, + } = await fetchTraceSamples({ + query: `sequence by trace.id + [ transaction where service.name == "python" ] + [ span where span.destination.service.resource == "redis" ]`, + type: TraceSearchType.eql, + environment: 'ENVIRONMENT_ALL', + }); + + const traces = await fetchTraces(samples); + + expect(traces.length).to.eql(1); + + const mapped = traces.map((traceDocs) => { + return sortBy(traceDocs, '@timestamp') + .filter( + (doc) => doc.processor.event === 'transaction' || doc.processor.event === 'span' + ) + .map((doc) => { + if (doc.span && 'destination' in doc.span) { + return doc.span.destination!.service.resource; + } + return doc.service.name; + }); + }); + + expect(mapped).to.eql([['python', 'redis']]); + }); + }); + + after(() => synthtraceEsClient.clean()); + } + ); +} From 52e51f6d1c356be0d2f93865ebad8f6d3458465f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Mon, 23 May 2022 15:27:54 +0200 Subject: [PATCH 05/71] [Stack Monitoring] Convert enterprise search routes to TypeScript (#132649) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../http_api/enterprise_search/index.ts} | 2 +- .../post_enterprise_search_overview.ts | 30 +++++++++++++++ .../server/lib/details/get_metrics.ts | 11 +++--- .../get_enterprise_search_for_clusters.ts | 15 ++++---- .../server/lib/enterprise_search/get_stats.ts | 12 ++++-- .../routes/api/v1/enterprise_search/index.ts | 13 +++++++ ...set_overview.js => metric_set_overview.ts} | 4 +- .../{overview.js => overview.ts} | 38 +++++++++---------- .../monitoring/server/routes/api/v1/index.ts | 1 + .../monitoring/server/routes/api/v1/ui.ts | 2 - .../plugins/monitoring/server/routes/index.ts | 2 + 11 files changed, 90 insertions(+), 40 deletions(-) rename x-pack/plugins/monitoring/{server/routes/api/v1/enterprise_search/index.js => common/http_api/enterprise_search/index.ts} (82%) create mode 100644 x-pack/plugins/monitoring/common/http_api/enterprise_search/post_enterprise_search_overview.ts create mode 100644 x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/index.ts rename x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/{metric_set_overview.js => metric_set_overview.ts} (93%) rename x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/{overview.js => overview.ts} (55%) diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/index.js b/x-pack/plugins/monitoring/common/http_api/enterprise_search/index.ts similarity index 82% rename from x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/index.js rename to x-pack/plugins/monitoring/common/http_api/enterprise_search/index.ts index 4eb6af5bb116d..372cadb4b9a8f 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/index.js +++ b/x-pack/plugins/monitoring/common/http_api/enterprise_search/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { entSearchOverviewRoute } from './overview'; +export * from './post_enterprise_search_overview'; diff --git a/x-pack/plugins/monitoring/common/http_api/enterprise_search/post_enterprise_search_overview.ts b/x-pack/plugins/monitoring/common/http_api/enterprise_search/post_enterprise_search_overview.ts new file mode 100644 index 0000000000000..9f264bc7fbb27 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/enterprise_search/post_enterprise_search_overview.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, timeRangeRT } from '../shared'; + +export const postEnterpriseSearchOverviewRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, +}); + +export const postEnterpriseSearchOverviewRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + timeRange: timeRangeRT, + }), +]); + +export type PostEnterpriseSearchOverviewRequestPayload = rt.TypeOf< + typeof postEnterpriseSearchOverviewRequestPayloadRT +>; + +export const postEnterpriseSearchOverviewResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts b/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts index f7e65efa74737..57d0928a7b9f4 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts +++ b/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts @@ -6,12 +6,13 @@ */ import moment from 'moment'; -import { checkParam } from '../error_missing_required'; -import { getSeries } from './get_series'; +import { INDEX_PATTERN_TYPES } from '../../../common/constants'; +import { TimeRange } from '../../../common/http_api/shared'; +import { LegacyRequest } from '../../types'; import { calculateTimeseriesInterval } from '../calculate_timeseries_interval'; +import { checkParam } from '../error_missing_required'; import { getTimezone } from '../get_timezone'; -import { LegacyRequest } from '../../types'; -import { INDEX_PATTERN_TYPES } from '../../../common/constants'; +import { getSeries } from './get_series'; export interface NamedMetricDescriptor { keys: string | string[]; @@ -30,7 +31,7 @@ export function isNamedMetricDescriptor( // TODO: Switch to an options object argument here export async function getMetrics( - req: LegacyRequest, + req: LegacyRequest, moduleType: INDEX_PATTERN_TYPES, metricSet: MetricDescriptor[] = [], filters: Array> = [], diff --git a/x-pack/plugins/monitoring/server/lib/enterprise_search/get_enterprise_search_for_clusters.ts b/x-pack/plugins/monitoring/server/lib/enterprise_search/get_enterprise_search_for_clusters.ts index f530912562976..d83b8e73c703e 100644 --- a/x-pack/plugins/monitoring/server/lib/enterprise_search/get_enterprise_search_for_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/enterprise_search/get_enterprise_search_for_clusters.ts @@ -5,17 +5,18 @@ * 2.0. */ +import { TimeRange } from '../../../common/http_api/shared'; import { ElasticsearchResponse } from '../../../common/types/es'; -import { LegacyRequest, Cluster } from '../../types'; -import { createEnterpriseSearchQuery } from './create_enterprise_search_query'; +import { Globals } from '../../static_globals'; +import { Cluster, LegacyRequest } from '../../types'; +import { getLegacyIndexPattern } from '../cluster/get_index_patterns'; import { EnterpriseSearchMetric } from '../metrics'; +import { createEnterpriseSearchQuery } from './create_enterprise_search_query'; import { entSearchAggFilterPath, entSearchAggResponseHandler, entSearchUuidsAgg, } from './_enterprise_search_stats'; -import { getLegacyIndexPattern } from '../cluster/get_index_patterns'; -import { Globals } from '../../static_globals'; function handleResponse(clusterUuid: string, response: ElasticsearchResponse) { const stats = entSearchAggResponseHandler(response); @@ -27,12 +28,12 @@ function handleResponse(clusterUuid: string, response: ElasticsearchResponse) { } export function getEnterpriseSearchForClusters( - req: LegacyRequest, + req: LegacyRequest, clusters: Cluster[], ccs: string ) { - const start = req.payload.timeRange.min; - const end = req.payload.timeRange.max; + const start = req.payload.timeRange?.min; + const end = req.payload.timeRange?.max; const config = req.server.config; const maxBucketSize = config.ui.max_bucket_size; diff --git a/x-pack/plugins/monitoring/server/lib/enterprise_search/get_stats.ts b/x-pack/plugins/monitoring/server/lib/enterprise_search/get_stats.ts index afe315f6a3ba7..725f5b953a2ef 100644 --- a/x-pack/plugins/monitoring/server/lib/enterprise_search/get_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/enterprise_search/get_stats.ts @@ -6,18 +6,22 @@ */ import moment from 'moment'; +import { TimeRange } from '../../../common/http_api/shared'; import { ElasticsearchResponse } from '../../../common/types/es'; +import { Globals } from '../../static_globals'; import { LegacyRequest } from '../../types'; +import { getLegacyIndexPattern } from '../cluster/get_index_patterns'; import { createEnterpriseSearchQuery } from './create_enterprise_search_query'; import { entSearchAggFilterPath, - entSearchUuidsAgg, entSearchAggResponseHandler, + entSearchUuidsAgg, } from './_enterprise_search_stats'; -import { getLegacyIndexPattern } from '../cluster/get_index_patterns'; -import { Globals } from '../../static_globals'; -export async function getStats(req: LegacyRequest, clusterUuid: string) { +export async function getStats( + req: LegacyRequest, + clusterUuid: string +) { const config = req.server.config; const start = moment.utc(req.payload.timeRange.min).valueOf(); const end = moment.utc(req.payload.timeRange.max).valueOf(); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/index.ts b/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/index.ts new file mode 100644 index 0000000000000..440fe1404af56 --- /dev/null +++ b/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MonitoringCore } from '../../../../types'; +import { entSearchOverviewRoute } from './overview'; + +export function registerV1EnterpriseSearchRoutes(server: MonitoringCore) { + entSearchOverviewRoute(server); +} diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/metric_set_overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/metric_set_overview.ts similarity index 93% rename from x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/metric_set_overview.js rename to x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/metric_set_overview.ts index 4bec4bc3948c5..8d23a3981a320 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/metric_set_overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/metric_set_overview.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const metricSet = [ +import { MetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSet: MetricDescriptor[] = [ // Low level usage metrics { name: 'enterprise_search_heap', diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/overview.ts similarity index 55% rename from x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/overview.js rename to x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/overview.ts index e3e269d8b3148..6581b52655cee 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/enterprise_search/overview.ts @@ -5,31 +5,29 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; +import { + postEnterpriseSearchOverviewRequestParamsRT, + postEnterpriseSearchOverviewRequestPayloadRT, + postEnterpriseSearchOverviewResponsePayloadRT, +} from '../../../../../common/http_api/enterprise_search'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { getMetrics } from '../../../../lib/details/get_metrics'; -import { metricSet } from './metric_set_overview'; -import { handleError } from '../../../../lib/errors'; import { getStats } from '../../../../lib/enterprise_search'; +import { handleError } from '../../../../lib/errors'; +import { MonitoringCore } from '../../../../types'; +import { metricSet } from './metric_set_overview'; + +export function entSearchOverviewRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postEnterpriseSearchOverviewRequestParamsRT); + const validateBody = createValidationFunction(postEnterpriseSearchOverviewRequestPayloadRT); -export function entSearchOverviewRoute(server) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/enterprise_search', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, - async handler(req) { const clusterUuid = req.params.clusterUuid; try { @@ -40,7 +38,7 @@ export function entSearchOverviewRoute(server) { }), ]); - return { stats, metrics }; + return postEnterpriseSearchOverviewResponsePayloadRT.encode({ stats, metrics }); } catch (err) { return handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/index.ts b/x-pack/plugins/monitoring/server/routes/api/v1/index.ts index e0f5e55c6c128..b59e98006816e 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/index.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/index.ts @@ -12,5 +12,6 @@ export { registerV1CheckAccessRoutes } from './check_access'; export { registerV1ClusterRoutes } from './cluster'; export { registerV1ElasticsearchRoutes } from './elasticsearch'; export { registerV1ElasticsearchSettingsRoutes } from './elasticsearch_settings'; +export { registerV1EnterpriseSearchRoutes } from './enterprise_search'; export { registerV1LogstashRoutes } from './logstash'; export { registerV1SetupRoutes } from './setup'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/ui.ts b/x-pack/plugins/monitoring/server/routes/api/v1/ui.ts index 7aaa6591e868e..eb76cc12d78a4 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/ui.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/ui.ts @@ -10,5 +10,3 @@ // @ts-expect-error export { kibanaInstanceRoute, kibanaInstancesRoute, kibanaOverviewRoute } from './kibana'; -// @ts-expect-error -export { entSearchOverviewRoute } from './enterprise_search'; diff --git a/x-pack/plugins/monitoring/server/routes/index.ts b/x-pack/plugins/monitoring/server/routes/index.ts index f38612d5a42da..c16b806bfb93a 100644 --- a/x-pack/plugins/monitoring/server/routes/index.ts +++ b/x-pack/plugins/monitoring/server/routes/index.ts @@ -17,6 +17,7 @@ import { registerV1ClusterRoutes, registerV1ElasticsearchRoutes, registerV1ElasticsearchSettingsRoutes, + registerV1EnterpriseSearchRoutes, registerV1LogstashRoutes, registerV1SetupRoutes, } from './api/v1'; @@ -45,6 +46,7 @@ export function requireUIRoutes( registerV1ClusterRoutes(server); registerV1ElasticsearchRoutes(server); registerV1ElasticsearchSettingsRoutes(server, npRoute); + registerV1EnterpriseSearchRoutes(server); registerV1LogstashRoutes(server); registerV1SetupRoutes(server); } From c23412b1b3022f5507dc25266da4655a21b041e7 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Mon, 23 May 2022 09:33:52 -0400 Subject: [PATCH 06/71] [Security Solution] Default Event Filters advanced policy fields and migration (#132418) * [Security Solution] Default Event Filters advanced policy fields and migration * change field name * update option names * fix typo * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * pr comments Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../fleet/server/saved_objects/index.ts | 3 +- .../migrations/security_solution/index.ts | 1 + .../security_solution/to_v8_3_0.test.ts | 155 ++++++++++++++++++ .../migrations/security_solution/to_v8_3_0.ts | 42 +++++ .../saved_objects/migrations/to_v8_3_0.ts | 17 ++ .../policy/models/advanced_policy_schema.ts | 30 ++++ 6 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.test.ts create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.ts diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index edcf2ed751f3e..009cdef6aa771 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -38,7 +38,7 @@ import { migratePackagePolicyToV7150 } from './migrations/to_v7_15_0'; import { migrateInstallationToV7160, migratePackagePolicyToV7160 } from './migrations/to_v7_16_0'; import { migrateInstallationToV800, migrateOutputToV800 } from './migrations/to_v8_0_0'; import { migratePackagePolicyToV820 } from './migrations/to_v8_2_0'; -import { migrateInstallationToV830 } from './migrations/to_v8_3_0'; +import { migrateInstallationToV830, migratePackagePolicyToV830 } from './migrations/to_v8_3_0'; /* * Saved object types and mappings @@ -210,6 +210,7 @@ const getSavedObjectTypes = ( '7.15.0': migratePackagePolicyToV7150, '7.16.0': migratePackagePolicyToV7160, '8.2.0': migratePackagePolicyToV820, + '8.3.0': migratePackagePolicyToV830, }, }, [PACKAGES_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts index 16c87ab3a69ee..02456661674a0 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/index.ts @@ -12,3 +12,4 @@ export { migrateEndpointPackagePolicyToV7140 } from './to_v7_14_0'; export { migratePackagePolicyToV7150 } from './to_v7_15_0'; export { migratePackagePolicyToV7160 } from './to_v7_16_0'; export { migratePackagePolicyToV820 } from './to_v8_2_0'; +export { migratePackagePolicyToV830 } from './to_v8_3_0'; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.test.ts new file mode 100644 index 0000000000000..d5eb8c5a86547 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.test.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; + +import type { PackagePolicy } from '../../../../common'; + +import { migratePackagePolicyToV830 as migration } from './to_v8_3_0'; + +describe('8.3.0 Endpoint Package Policy migration', () => { + const policyDoc = ({ windowsAdvanced = {}, macAdvanced = {}, linuxAdvanced = {} }) => { + return { + id: 'mock-saved-object-id', + attributes: { + name: 'Some Policy Name', + package: { + name: 'endpoint', + title: '', + version: '', + }, + id: 'endpoint', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + policy: { + value: { + windows: { + ...windowsAdvanced, + }, + mac: { + ...macAdvanced, + }, + linux: { + ...linuxAdvanced, + }, + }, + }, + }, + }, + ], + }, + type: ' nested', + }; + }; + + it('adds advanced event filters defaulted to false', () => { + const initialDoc = policyDoc({}); + + const migratedDoc = policyDoc({ + windowsAdvanced: { advanced: { event_filters: { default: false } } }, + macAdvanced: { advanced: { event_filters: { default: false } } }, + linuxAdvanced: { advanced: { event_filters: { default: false } } }, + }); + + expect(migration(initialDoc, {} as SavedObjectMigrationContext)).toEqual(migratedDoc); + }); + + it('adds advanced event filters defaulted to false and preserves existing advanced fields', () => { + const initialDoc = policyDoc({ + windowsAdvanced: { advanced: { existingAdvanced: true } }, + macAdvanced: { advanced: { existingAdvanced: true } }, + linuxAdvanced: { advanced: { existingAdvanced: true } }, + }); + + const migratedDoc = policyDoc({ + windowsAdvanced: { advanced: { event_filters: { default: false }, existingAdvanced: true } }, + macAdvanced: { advanced: { event_filters: { default: false }, existingAdvanced: true } }, + linuxAdvanced: { advanced: { event_filters: { default: false }, existingAdvanced: true } }, + }); + + expect(migration(initialDoc, {} as SavedObjectMigrationContext)).toEqual(migratedDoc); + }); + + it('does not modify non-endpoint package policies', () => { + const doc: SavedObjectUnsanitizedDoc = { + id: 'mock-saved-object-id', + attributes: { + name: 'Some Policy Name', + package: { + name: 'notEndpoint', + title: '', + version: '', + }, + id: 'notEndpoint', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'notEndpoint', + enabled: true, + streams: [], + config: {}, + }, + ], + }, + type: ' nested', + }; + + expect( + migration(doc, {} as SavedObjectMigrationContext) as SavedObjectUnsanitizedDoc + ).toEqual({ + attributes: { + name: 'Some Policy Name', + package: { + name: 'notEndpoint', + title: '', + version: '', + }, + id: 'notEndpoint', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'notEndpoint', + enabled: true, + streams: [], + config: {}, + }, + ], + }, + type: ' nested', + id: 'mock-saved-object-id', + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.ts new file mode 100644 index 0000000000000..ade6ec9ab1565 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v8_3_0.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; +import { cloneDeep } from 'lodash'; + +import type { PackagePolicy } from '../../../../common'; + +export const migratePackagePolicyToV830: SavedObjectMigrationFn = ( + packagePolicyDoc +) => { + if (packagePolicyDoc.attributes.package?.name !== 'endpoint') { + return packagePolicyDoc; + } + + const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = + cloneDeep(packagePolicyDoc); + + const input = updatedPackagePolicyDoc.attributes.inputs[0]; + + if (input && input.config) { + const policy = input.config.policy.value; + + const migratedPolicy = { event_filters: { default: false } }; + + policy.windows.advanced = policy.windows.advanced + ? { ...policy.windows.advanced, ...migratedPolicy } + : { ...migratedPolicy }; + policy.mac.advanced = policy.mac.advanced + ? { ...policy.mac.advanced, ...migratedPolicy } + : { ...migratedPolicy }; + policy.linux.advanced = policy.linux.advanced + ? { ...policy.linux.advanced, ...migratedPolicy } + : { ...migratedPolicy }; + } + + return updatedPackagePolicyDoc; +}; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_3_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_3_0.ts index 843427f3cf862..b8fbeb2b0ab3a 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_3_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_3_0.ts @@ -7,8 +7,11 @@ import type { SavedObjectMigrationFn } from '@kbn/core/server'; +import type { PackagePolicy } from '../../../common'; import type { Installation } from '../../../common'; +import { migratePackagePolicyToV830 as SecSolMigratePackagePolicyToV830 } from './security_solution'; + export const migrateInstallationToV830: SavedObjectMigrationFn = ( installationDoc, migrationContext @@ -17,3 +20,17 @@ export const migrateInstallationToV830: SavedObjectMigrationFn = ( + packagePolicyDoc, + migrationContext +) => { + let updatedPackagePolicyDoc = packagePolicyDoc; + + // Endpoint specific migrations + if (packagePolicyDoc.attributes.package?.name === 'endpoint') { + updatedPackagePolicyDoc = SecSolMigratePackagePolicyToV830(packagePolicyDoc, migrationContext); + } + + return updatedPackagePolicyDoc; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index 7f86540e36426..df13b8ba43f06 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -884,4 +884,34 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'linux.advanced.event_filter.default', + first_supported_version: '8.3', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.event_filter.default', + { + defaultMessage: 'Download default event filter rules from Elastic. Default: true', + } + ), + }, + { + key: 'mac.advanced.event_filter.default', + first_supported_version: '8.3', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.event_filter.default', + { + defaultMessage: 'Download default event filter rules from Elastic. Default: true', + } + ), + }, + { + key: 'windows.advanced.event_filter.default', + first_supported_version: '8.3', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.event_filter.default', + { + defaultMessage: 'Download default event filter rules from Elastic. Default: true', + } + ), + }, ]; From 0beb268616003a07286c97b40b916bb198c405ff Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 23 May 2022 15:34:45 +0200 Subject: [PATCH 07/71] [data.query] implement `PersistableState` interface for `QueryState` (#132623) --- src/plugins/data/common/index.ts | 1 + .../query/filters/persistable_state.test.ts | 40 ++++++++ .../common/query/filters/persistable_state.ts | 65 ++++++++++++ src/plugins/data/common/query/index.ts | 1 + .../common/query/persistable_state.test.ts | 45 +++++++-- .../data/common/query/persistable_state.ts | 98 +++++++++++-------- src/plugins/data/common/query/query_state.ts | 26 +++++ .../search_source/search_source_service.ts | 2 +- src/plugins/data/public/index.ts | 1 + .../query/filter_manager/filter_manager.ts | 19 ++-- src/plugins/data/public/query/mocks.ts | 17 ++++ .../data/public/query/query_service.test.ts | 8 ++ .../data/public/query/query_service.ts | 86 +++++++++++++--- src/plugins/data/public/query/query_state.ts | 13 +-- .../create_query_state_observable.ts | 4 +- .../data/public/query/state_sync/index.ts | 1 + .../data/server/query/query_service.ts | 30 ++++-- .../server/query/route_handler_context.ts | 2 +- .../server/saved_objects/migrations/query.ts | 2 +- 19 files changed, 359 insertions(+), 102 deletions(-) create mode 100644 src/plugins/data/common/query/filters/persistable_state.test.ts create mode 100644 src/plugins/data/common/query/filters/persistable_state.ts create mode 100644 src/plugins/data/common/query/query_state.ts diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index 8944aa7db2843..aa4da04d2e198 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -74,6 +74,7 @@ export { isQuery, isTimeRange, } from './query'; +export type { QueryState } from './query'; export * from './search'; export type { RefreshInterval, diff --git a/src/plugins/data/common/query/filters/persistable_state.test.ts b/src/plugins/data/common/query/filters/persistable_state.test.ts new file mode 100644 index 0000000000000..9ce3e2536a70a --- /dev/null +++ b/src/plugins/data/common/query/filters/persistable_state.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { extract, inject } from './persistable_state'; +import { Filter } from '@kbn/es-query'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; + +describe('filter manager persistable state tests', () => { + const filters: Filter[] = [ + { meta: { alias: 'test', disabled: false, negate: false, index: 'test' } }, + ]; + describe('reference injection', () => { + test('correctly inserts reference to filter', () => { + const updatedFilters = inject(filters, [ + { type: DATA_VIEW_SAVED_OBJECT_TYPE, name: 'test', id: '123' }, + ]); + expect(updatedFilters[0]).toHaveProperty('meta.index', '123'); + }); + + test('drops index setting if reference is missing', () => { + const updatedFilters = inject(filters, [ + { type: DATA_VIEW_SAVED_OBJECT_TYPE, name: 'test123', id: '123' }, + ]); + expect(updatedFilters[0]).toHaveProperty('meta.index', undefined); + }); + }); + + describe('reference extraction', () => { + test('correctly extracts references', () => { + const { state, references } = extract(filters); + expect(state[0]).toHaveProperty('meta.index'); + expect(references[0]).toHaveProperty('id', 'test'); + }); + }); +}); diff --git a/src/plugins/data/common/query/filters/persistable_state.ts b/src/plugins/data/common/query/filters/persistable_state.ts new file mode 100644 index 0000000000000..a309573fb9df2 --- /dev/null +++ b/src/plugins/data/common/query/filters/persistable_state.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; +import { Filter } from '@kbn/es-query'; +import { SavedObjectReference } from '@kbn/core/types'; +import { MigrateFunctionsObject, VersionedState } from '@kbn/kibana-utils-plugin/common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; + +export const extract = (filters: Filter[]) => { + const references: SavedObjectReference[] = []; + const updatedFilters = filters.map((filter) => { + if (filter.meta?.index) { + const id = uuid(); + references.push({ + type: DATA_VIEW_SAVED_OBJECT_TYPE, + name: id, + id: filter.meta.index, + }); + + return { + ...filter, + meta: { + ...filter.meta, + index: id, + }, + }; + } + return filter; + }); + return { state: updatedFilters, references }; +}; + +export const inject = (filters: Filter[], references: SavedObjectReference[]) => { + return filters.map((filter) => { + if (!filter.meta.index) { + return filter; + } + const reference = references.find((ref) => ref.name === filter.meta.index); + return { + ...filter, + meta: { + ...filter.meta, + index: reference && reference.id, + }, + }; + }); +}; + +export const telemetry = (filters: Filter[], collector: unknown) => { + return {}; +}; + +export const migrateToLatest = (filters: VersionedState) => { + return filters.state; +}; + +export const getAllMigrations = (): MigrateFunctionsObject => { + return {}; +}; diff --git a/src/plugins/data/common/query/index.ts b/src/plugins/data/common/query/index.ts index 35b1617dfa7d6..2d6e64841f6ea 100644 --- a/src/plugins/data/common/query/index.ts +++ b/src/plugins/data/common/query/index.ts @@ -9,3 +9,4 @@ export * from './timefilter'; export * from './types'; export * from './is_query'; +export * from './query_state'; diff --git a/src/plugins/data/common/query/persistable_state.test.ts b/src/plugins/data/common/query/persistable_state.test.ts index c69f7dee15e37..2fcfce910ebb8 100644 --- a/src/plugins/data/common/query/persistable_state.test.ts +++ b/src/plugins/data/common/query/persistable_state.test.ts @@ -6,35 +6,60 @@ * Side Public License, v 1. */ -import { extract, inject } from './persistable_state'; +import { extract, inject, getAllMigrations } from './persistable_state'; import { Filter } from '@kbn/es-query'; -import { DATA_VIEW_SAVED_OBJECT_TYPE } from '..'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; +import { QueryState } from './query_state'; -describe('filter manager persistable state tests', () => { +describe('query service persistable state tests', () => { const filters: Filter[] = [ { meta: { alias: 'test', disabled: false, negate: false, index: 'test' } }, ]; + const query = { language: 'kql', query: 'query' }; + const time = { from: new Date().toISOString(), to: new Date().toISOString() }; + const refreshInterval = { pause: false, value: 10 }; + + const queryState: QueryState = { + filters, + query, + time, + refreshInterval, + }; + describe('reference injection', () => { test('correctly inserts reference to filter', () => { - const updatedFilters = inject(filters, [ + const updatedQueryState = inject(queryState, [ { type: DATA_VIEW_SAVED_OBJECT_TYPE, name: 'test', id: '123' }, ]); - expect(updatedFilters[0]).toHaveProperty('meta.index', '123'); + expect(updatedQueryState.filters[0]).toHaveProperty('meta.index', '123'); + expect(updatedQueryState.query).toEqual(queryState.query); + expect(updatedQueryState.time).toEqual(queryState.time); + expect(updatedQueryState.refreshInterval).toEqual(queryState.refreshInterval); }); - test('drops index setting if reference is missing', () => { - const updatedFilters = inject(filters, [ + test('drops index setting from filter if reference is missing', () => { + const updatedQueryState = inject(queryState, [ { type: DATA_VIEW_SAVED_OBJECT_TYPE, name: 'test123', id: '123' }, ]); - expect(updatedFilters[0]).toHaveProperty('meta.index', undefined); + expect(updatedQueryState.filters[0]).toHaveProperty('meta.index', undefined); }); }); describe('reference extraction', () => { test('correctly extracts references', () => { - const { state, references } = extract(filters); - expect(state[0]).toHaveProperty('meta.index'); + const { state, references } = extract(queryState); + expect(state.filters[0]).toHaveProperty('meta.index'); expect(references[0]).toHaveProperty('id', 'test'); + + expect(state.query).toEqual(queryState.query); + expect(state.time).toEqual(queryState.time); + expect(state.refreshInterval).toEqual(queryState.refreshInterval); + }); + }); + + describe('migrations', () => { + test('getAllMigrations', () => { + expect(getAllMigrations()).toEqual({}); }); }); }); diff --git a/src/plugins/data/common/query/persistable_state.ts b/src/plugins/data/common/query/persistable_state.ts index 4860d892ab5d7..cf74bb0a2cb67 100644 --- a/src/plugins/data/common/query/persistable_state.ts +++ b/src/plugins/data/common/query/persistable_state.ts @@ -6,60 +6,72 @@ * Side Public License, v 1. */ -import uuid from 'uuid'; -import { Filter } from '@kbn/es-query'; import { SavedObjectReference } from '@kbn/core/types'; -import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; -import { DATA_VIEW_SAVED_OBJECT_TYPE } from '..'; +import { mapValues } from 'lodash'; +import { + mergeMigrationFunctionMaps, + MigrateFunctionsObject, + VersionedState, +} from '@kbn/kibana-utils-plugin/common'; +import type { QueryState } from './query_state'; +import * as filtersPersistableState from './filters/persistable_state'; -export const extract = (filters: Filter[]) => { +export const extract = (queryState: QueryState) => { const references: SavedObjectReference[] = []; - const updatedFilters = filters.map((filter) => { - if (filter.meta?.index) { - const id = uuid(); - references.push({ - type: DATA_VIEW_SAVED_OBJECT_TYPE, - name: id, - id: filter.meta.index, - }); - return { - ...filter, - meta: { - ...filter.meta, - index: id, - }, - }; - } - return filter; - }); - return { state: updatedFilters, references }; + const { state: updatedFilters, references: referencesFromFilters } = + filtersPersistableState.extract(queryState.filters ?? []); + references.push(...referencesFromFilters); + + return { + state: { + ...queryState, + filters: updatedFilters, + }, + references, + }; }; -export const inject = (filters: Filter[], references: SavedObjectReference[]) => { - return filters.map((filter) => { - if (!filter.meta.index) { - return filter; - } - const reference = references.find((ref) => ref.name === filter.meta.index); - return { - ...filter, - meta: { - ...filter.meta, - index: reference && reference.id, - }, - }; - }); +export const inject = (queryState: QueryState, references: SavedObjectReference[]) => { + const updatedFilters = filtersPersistableState.inject(queryState.filters ?? [], references); + + return { + ...queryState, + filters: updatedFilters, + }; }; -export const telemetry = (filters: Filter[], collector: unknown) => { - return {}; +export const telemetry = (queryState: QueryState, collector: unknown) => { + const filtersTelemetry = filtersPersistableState.telemetry(queryState.filters ?? [], collector); + return { + ...filtersTelemetry, + }; }; -export const migrateToLatest = (filters: Filter[], version: string) => { - return filters; +export const migrateToLatest = ({ state, version }: VersionedState) => { + const migratedFilters = filtersPersistableState.migrateToLatest({ + state: state.filters ?? [], + version, + }); + + return { + ...state, + filters: migratedFilters, + }; }; export const getAllMigrations = (): MigrateFunctionsObject => { - return {}; + const queryMigrations: MigrateFunctionsObject = {}; + + const filterMigrations: MigrateFunctionsObject = mapValues( + filtersPersistableState.getAllMigrations(), + (migrate) => { + return (state: QueryState) => ({ + ...state, + filters: migrate(state.filters ?? []), + }); + } + ); + + return mergeMigrationFunctionMaps(queryMigrations, filterMigrations); }; diff --git a/src/plugins/data/common/query/query_state.ts b/src/plugins/data/common/query/query_state.ts new file mode 100644 index 0000000000000..fbc5626f9a28c --- /dev/null +++ b/src/plugins/data/common/query/query_state.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Filter } from '@kbn/es-query'; +import type { TimeRange, RefreshInterval } from './timefilter/types'; +import type { Query } from './types'; + +/** + * All query state service state + * + * @remark + * `type` instead of `interface` to make it compatible with PersistableState utils + * + */ +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type QueryState = { + time?: TimeRange; + refreshInterval?: RefreshInterval; + filters?: Filter[]; + query?: Query; +}; diff --git a/src/plugins/data/common/search/search_source/search_source_service.ts b/src/plugins/data/common/search/search_source/search_source_service.ts index c03053c839f4d..e87f589044f9a 100644 --- a/src/plugins/data/common/search/search_source/search_source_service.ts +++ b/src/plugins/data/common/search/search_source/search_source_service.ts @@ -20,7 +20,7 @@ import { SerializedSearchSourceFields, } from '.'; import { IndexPatternsContract } from '../..'; -import { getAllMigrations as filtersGetAllMigrations } from '../../query/persistable_state'; +import { getAllMigrations as filtersGetAllMigrations } from '../../query/filters/persistable_state'; const getAllMigrations = (): MigrateFunctionsObject => { const searchSourceMigrations = {}; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 0f50384893b18..c91e21d8683c3 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -269,6 +269,7 @@ export type { NowProviderInternalContract } from './now_provider'; export type { QueryState, + QueryState$, SavedQuery, SavedQueryService, SavedQueryTimeFilter, diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts index c874a15b5efc6..cf9ad750d2809 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts @@ -11,24 +11,25 @@ import { Subject } from 'rxjs'; import { IUiSettingsClient } from '@kbn/core/public'; -import { isFilterPinned, onlyDisabledFiltersChanged, Filter } from '@kbn/es-query'; -import { PersistableStateService } from '@kbn/kibana-utils-plugin/common/persistable_state'; -import { sortFilters } from './lib/sort_filters'; -import { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; - import { - FilterStateStore, + isFilterPinned, + onlyDisabledFiltersChanged, + Filter, uniqFilters, compareFilters, COMPARE_ALL_OPTIONS, - UI_SETTINGS, -} from '../../../common'; +} from '@kbn/es-query'; +import { PersistableStateService } from '@kbn/kibana-utils-plugin/common/persistable_state'; +import { sortFilters } from './lib/sort_filters'; +import { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; + +import { FilterStateStore, UI_SETTINGS } from '../../../common'; import { getAllMigrations, inject, extract, telemetry, -} from '../../../common/query/persistable_state'; +} from '../../../common/query/filters/persistable_state'; interface PartitionedFilters { globalFilters: Filter[]; diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts index 296a61afef2fd..14de815c0d793 100644 --- a/src/plugins/data/public/query/mocks.ts +++ b/src/plugins/data/public/query/mocks.ts @@ -22,6 +22,12 @@ const createSetupContractMock = () => { queryString: queryStringManagerMock.createSetupContract(), state$: new Observable(), getState: jest.fn(), + + inject: jest.fn(), + extract: jest.fn(), + telemetry: jest.fn(), + migrateToLatest: jest.fn(), + getAllMigrations: jest.fn(), }; return setupContract; @@ -37,6 +43,11 @@ const createStartContractMock = () => { getState: jest.fn(), timefilter: timefilterServiceMock.createStartContract(), getEsQuery: jest.fn(), + inject: jest.fn(), + extract: jest.fn(), + telemetry: jest.fn(), + migrateToLatest: jest.fn(), + getAllMigrations: jest.fn(), }; return startContract; @@ -47,6 +58,12 @@ const createMock = () => { setup: jest.fn(), start: jest.fn(), stop: jest.fn(), + + inject: jest.fn(), + extract: jest.fn(), + telemetry: jest.fn(), + migrateToLatest: jest.fn(), + getAllMigrations: jest.fn(), }; mocked.setup.mockReturnValue(createSetupContractMock()); diff --git a/src/plugins/data/public/query/query_service.test.ts b/src/plugins/data/public/query/query_service.test.ts index 5eb6815c3ba20..6ddde2d18f74f 100644 --- a/src/plugins/data/public/query/query_service.test.ts +++ b/src/plugins/data/public/query/query_service.test.ts @@ -59,6 +59,14 @@ describe('query_service', () => { queryStringManager = queryServiceStart.queryString; }); + test('implements PersistableState interface', () => { + expect(queryServiceStart).toHaveProperty('inject'); + expect(queryServiceStart).toHaveProperty('extract'); + expect(queryServiceStart).toHaveProperty('telemetry'); + expect(queryServiceStart).toHaveProperty('migrateToLatest'); + expect(queryServiceStart).toHaveProperty('getAllMigrations'); + }); + test('state is initialized with state from query service', () => { const state = queryServiceStart.getState(); diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index 8b309c9821d3e..5eb24846c3578 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -8,26 +8,32 @@ import { share } from 'rxjs/operators'; import { HttpStart, IUiSettingsClient } from '@kbn/core/public'; +import { PersistableStateService, VersionedState } from '@kbn/kibana-utils-plugin/common'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { buildEsQuery } from '@kbn/es-query'; import { FilterManager } from './filter_manager'; import { createAddToQueryLog } from './lib'; -import { TimefilterService } from './timefilter'; import type { TimefilterSetup } from './timefilter'; +import { TimefilterService } from './timefilter'; import { createSavedQueryService } from './saved_query/saved_query_service'; -import { createQueryStateObservable } from './state_sync/create_query_state_observable'; -import { getQueryState } from './query_state'; +import { + createQueryStateObservable, + QueryState$, +} from './state_sync/create_query_state_observable'; +import { getQueryState, QueryState } from './query_state'; import type { QueryStringContract } from './query_string'; import { QueryStringManager } from './query_string'; import { getEsQueryConfig, TimeRange } from '../../common'; import { getUiSettings } from '../services'; import { NowProviderInternalContract } from '../now_provider'; import { IndexPattern } from '..'; - -/** - * Query Service - * @internal - */ +import { + extract, + getAllMigrations, + inject, + migrateToLatest, + telemetry, +} from '../../common/query/persistable_state'; interface QueryServiceSetupDependencies { storage: IStorageWrapper; @@ -41,14 +47,41 @@ interface QueryServiceStartDependencies { http: HttpStart; } -export class QueryService { +export interface QuerySetup extends PersistableStateService { + filterManager: FilterManager; + timefilter: TimefilterSetup; + queryString: QueryStringContract; + state$: QueryState$; + getState(): QueryState; +} + +export interface QueryStart extends PersistableStateService { + filterManager: FilterManager; + timefilter: TimefilterSetup; + queryString: QueryStringContract; + state$: QueryState$; + getState(): QueryState; + + // TODO: type explicitly + addToQueryLog: ReturnType; + // TODO: type explicitly + savedQueries: ReturnType; + // TODO: type explicitly + getEsQuery(indexPattern: IndexPattern, timeRange?: TimeRange): ReturnType; +} + +/** + * Query Service + * @internal + */ +export class QueryService implements PersistableStateService { filterManager!: FilterManager; timefilter!: TimefilterSetup; queryStringManager!: QueryStringContract; - state$!: ReturnType; + state$!: QueryState$; - public setup({ storage, uiSettings, nowProvider }: QueryServiceSetupDependencies) { + public setup({ storage, uiSettings, nowProvider }: QueryServiceSetupDependencies): QuerySetup { this.filterManager = new FilterManager(uiSettings); const timefilterService = new TimefilterService(nowProvider); @@ -71,10 +104,11 @@ export class QueryService { queryString: this.queryStringManager, state$: this.state$, getState: () => this.getQueryState(), + ...this.getPersistableStateMethods(), }; } - public start({ storage, uiSettings, http }: QueryServiceStartDependencies) { + public start({ storage, uiSettings, http }: QueryServiceStartDependencies): QueryStart { return { addToQueryLog: createAddToQueryLog({ storage, @@ -96,6 +130,7 @@ export class QueryService { getEsQueryConfig(getUiSettings()) ); }, + ...this.getPersistableStateMethods(), }; } @@ -110,8 +145,27 @@ export class QueryService { filterManager: this.filterManager, }); } -} -/** @public */ -export type QuerySetup = ReturnType; -export type QueryStart = ReturnType; + public extract = extract; + + public inject = inject; + + public telemetry = telemetry; + + public getAllMigrations = getAllMigrations; + + public migrateToLatest = (versionedState: VersionedState) => { + // Argument of type 'VersionedState' is not assignable to parameter of type 'VersionedState'. + return migrateToLatest(versionedState as VersionedState); + }; + + private getPersistableStateMethods(): PersistableStateService { + return { + extract: this.extract.bind(this), + inject: this.inject.bind(this), + telemetry: this.telemetry.bind(this), + migrateToLatest: this.migrateToLatest.bind(this), + getAllMigrations: this.getAllMigrations.bind(this), + }; + } +} diff --git a/src/plugins/data/public/query/query_state.ts b/src/plugins/data/public/query/query_state.ts index 77242c981bda2..e07bc631ca1e8 100644 --- a/src/plugins/data/public/query/query_state.ts +++ b/src/plugins/data/public/query/query_state.ts @@ -6,21 +6,12 @@ * Side Public License, v 1. */ -import type { Filter } from '@kbn/es-query'; +import type { QueryState } from '../../common'; import type { TimefilterSetup } from './timefilter'; import type { FilterManager } from './filter_manager'; import type { QueryStringContract } from './query_string'; -import type { RefreshInterval, TimeRange, Query } from '../../common'; -/** - * All query state service state - */ -export interface QueryState { - time?: TimeRange; - refreshInterval?: RefreshInterval; - filters?: Filter[]; - query?: Query; -} +export type { QueryState }; export function getQueryState({ timefilter: { timefilter }, diff --git a/src/plugins/data/public/query/state_sync/create_query_state_observable.ts b/src/plugins/data/public/query/state_sync/create_query_state_observable.ts index 39e7802753ee2..8520423915dba 100644 --- a/src/plugins/data/public/query/state_sync/create_query_state_observable.ts +++ b/src/plugins/data/public/query/state_sync/create_query_state_observable.ts @@ -16,6 +16,8 @@ import { getQueryState, QueryState } from '../query_state'; import { QueryStateChange } from './types'; import type { QueryStringContract } from '../query_string'; +export type QueryState$ = Observable<{ changes: QueryStateChange; state: QueryState }>; + export function createQueryStateObservable({ timefilter, filterManager, @@ -24,7 +26,7 @@ export function createQueryStateObservable({ timefilter: TimefilterSetup; filterManager: FilterManager; queryString: QueryStringContract; -}): Observable<{ changes: QueryStateChange; state: QueryState }> { +}): QueryState$ { const state = createStateContainer( getQueryState({ timefilter, filterManager, queryString }) ); diff --git a/src/plugins/data/public/query/state_sync/index.ts b/src/plugins/data/public/query/state_sync/index.ts index ffeda864f5172..4137184c90a69 100644 --- a/src/plugins/data/public/query/state_sync/index.ts +++ b/src/plugins/data/public/query/state_sync/index.ts @@ -9,3 +9,4 @@ export { connectToQueryState } from './connect_to_query_state'; export { syncQueryStateWithUrl, syncGlobalQueryStateWithUrl } from './sync_state_with_url'; export type { QueryStateChange, GlobalQueryStateFromUrl } from './types'; +export type { QueryState$ } from './create_query_state_observable'; diff --git a/src/plugins/data/server/query/query_service.ts b/src/plugins/data/server/query/query_service.ts index ea7c05905efee..81e511ea75e64 100644 --- a/src/plugins/data/server/query/query_service.ts +++ b/src/plugins/data/server/query/query_service.ts @@ -7,16 +7,27 @@ */ import { CoreSetup, Plugin } from '@kbn/core/server'; +import type { Filter } from '@kbn/es-query'; +import { PersistableStateService } from '@kbn/kibana-utils-plugin/common'; import { querySavedObjectType } from '../saved_objects'; -import { extract, getAllMigrations, inject, telemetry } from '../../common/query/persistable_state'; +import * as queryPersistableState from '../../common/query/persistable_state'; +import * as filtersPersistableState from '../../common/query/filters/persistable_state'; import { registerSavedQueryRoutes } from './routes'; import { registerSavedQueryRouteHandlerContext, SavedQueryRouteHandlerContext, } from './route_handler_context'; +import { QueryState } from '../../common'; +export interface QuerySetup extends PersistableStateService { + filterManager: PersistableStateService; +} + +/** + * @internal + */ export class QueryService implements Plugin { - public setup(core: CoreSetup) { + public setup(core: CoreSetup): QuerySetup { core.savedObjects.registerType(querySavedObjectType); core.http.registerRouteHandlerContext( 'savedQuery', @@ -25,17 +36,18 @@ export class QueryService implements Plugin { registerSavedQueryRoutes(core); return { + extract: queryPersistableState.extract, + inject: queryPersistableState.inject, + telemetry: queryPersistableState.telemetry, + getAllMigrations: queryPersistableState.getAllMigrations, filterManager: { - extract, - inject, - telemetry, - getAllMigrations, + extract: filtersPersistableState.extract, + inject: filtersPersistableState.inject, + telemetry: filtersPersistableState.telemetry, + getAllMigrations: filtersPersistableState.getAllMigrations, }, }; } public start() {} } - -/** @public */ -export type QuerySetup = ReturnType; diff --git a/src/plugins/data/server/query/route_handler_context.ts b/src/plugins/data/server/query/route_handler_context.ts index 0af658d7692c5..a79063014abc0 100644 --- a/src/plugins/data/server/query/route_handler_context.ts +++ b/src/plugins/data/server/query/route_handler_context.ts @@ -9,7 +9,7 @@ import { CustomRequestHandlerContext, RequestHandlerContext, SavedObject } from '@kbn/core/server'; import { isFilters } from '@kbn/es-query'; import { isQuery, SavedQueryAttributes } from '../../common'; -import { extract, inject } from '../../common/query/persistable_state'; +import { extract, inject } from '../../common/query/filters/persistable_state'; function injectReferences({ id, diff --git a/src/plugins/data/server/saved_objects/migrations/query.ts b/src/plugins/data/server/saved_objects/migrations/query.ts index b297276f9a22a..dadba8e4c94a5 100644 --- a/src/plugins/data/server/saved_objects/migrations/query.ts +++ b/src/plugins/data/server/saved_objects/migrations/query.ts @@ -10,7 +10,7 @@ import { mapValues } from 'lodash'; import { SavedObject } from '@kbn/core/server'; import { mergeMigrationFunctionMaps } from '@kbn/kibana-utils-plugin/common'; import { SavedQueryAttributes } from '../../../common'; -import { extract, getAllMigrations } from '../../../common/query/persistable_state'; +import { extract, getAllMigrations } from '../../../common/query/filters/persistable_state'; const extractFilterReferences = (doc: SavedObject) => { const { state: filters, references } = extract(doc.attributes.filters ?? []); From d415fbac53982005312a2982085551f889bd8839 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 23 May 2022 14:43:02 +0100 Subject: [PATCH 08/71] skip flaky suite (#132628) --- .../functional/apps/home/feature_controls/home_security.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/home/feature_controls/home_security.ts b/x-pack/test/functional/apps/home/feature_controls/home_security.ts index 96a3ccbe8ea5d..bfa3df5642975 100644 --- a/x-pack/test/functional/apps/home/feature_controls/home_security.ts +++ b/x-pack/test/functional/apps/home/feature_controls/home_security.ts @@ -35,7 +35,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); }); - describe('global all privileges', () => { + // https://github.com/elastic/kibana/issues/132628 + describe.skip('global all privileges', () => { before(async () => { await security.role.create('global_all_role', { elasticsearch: {}, From 906569f6795162692d532e6a24f70a5e91b7f68c Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Mon, 23 May 2022 10:03:56 -0400 Subject: [PATCH 09/71] Update list of filterable tags when a new agent is enrolled (#132638) --- .../sections/agents/agent_list_page/index.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index bbea3284f72b8..9bb2c8107e6e1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -244,13 +244,15 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { inactive: agentsRequest.data.totalInactive, }); - // Only set tags on the first request - we don't want the list of tags to update based - // on the returned set of agents from the API - if (allTags === undefined) { - const newAllTags = Array.from( - new Set(agentsRequest.data.items.flatMap((agent) => agent.tags ?? [])) - ); + const newAllTags = Array.from( + new Set(agentsRequest.data.items.flatMap((agent) => agent.tags ?? [])) + ); + // We only want to update the list of available tags if we've either received + // more tags than we currently have from the API (e.g. new agents have been enrolled) + // or we haven't set our list of tags yet. TODO: Would it be possible to remove a tag + // from the filterable list if an agent is unenrolled and no agents remain with that tag? + if (!allTags || newAllTags.length > allTags.length) { setAllTags(newAllTags); } From 96c988abcebce335ce1af7c245ea8620921b5642 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Mon, 23 May 2022 16:12:09 +0200 Subject: [PATCH 10/71] [Discover][Alerting] Add test button to rule flyout (#132540) --- .../expression/es_query_expression.tsx | 112 +++++------------- .../search_source_expression.test.tsx | 68 ++++++++--- .../search_source_expression_form.tsx | 19 ++- .../es_query/expression/test_query_row.tsx | 68 +++++++++++ .../expression/use_test_query.test.ts | 37 ++++++ .../es_query/expression/use_test_query.ts | 57 +++++++++ 6 files changed, 264 insertions(+), 97 deletions(-) create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.tsx create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.test.ts create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.ts diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx index afb45f90c6e52..92096ba4541c4 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx @@ -16,16 +16,13 @@ import 'brace/theme/github'; import { EuiFlexGroup, EuiFlexItem, - EuiButtonEmpty, EuiSpacer, EuiFormRow, - EuiText, EuiTitle, EuiLink, EuiIconTip, } from '@elastic/eui'; import { DocLinksStart, HttpSetup } from '@kbn/core/public'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { XJson, EuiCodeEditor } from '@kbn/es-ui-shared-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -42,10 +39,8 @@ import { buildSortedEventsQuery } from '../../../../common/build_sorted_events_q import { EsQueryAlertParams, SearchType } from '../types'; import { IndexSelectPopover } from '../../components/index_select_popover'; import { DEFAULT_VALUES } from '../constants'; - -function totalHitsToNumber(total: estypes.SearchHitsMetadata['total']): number { - return typeof total === 'number' ? total : total?.value ?? 0; -} +import { TestQueryRow } from './test_query_row'; +import { totalHitsToNumber } from './use_test_query'; const { useXJsonMode } = XJson; const xJsonMode = new XJsonMode(); @@ -109,8 +104,6 @@ export const EsQueryExpression = ({ }> >([]); const { convertToJson, setXJson, xJson } = useXJsonMode(DEFAULT_VALUES.QUERY); - const [testQueryResult, setTestQueryResult] = useState(null); - const [testQueryError, setTestQueryError] = useState(null); const setDefaultExpressionValues = async () => { setRuleProperty('params', currentAlertParams); @@ -133,55 +126,39 @@ export const EsQueryExpression = ({ } }; - const hasValidationErrors = () => { + const hasValidationErrors = useCallback(() => { const { errors: validationErrors } = validateExpression(currentAlertParams); return Object.keys(validationErrors).some( (key) => validationErrors[key] && validationErrors[key].length ); - }; + }, [currentAlertParams]); - const onTestQuery = async () => { - if (!hasValidationErrors()) { - setTestQueryError(null); - setTestQueryResult(null); - try { - const window = `${timeWindowSize}${timeWindowUnit}`; - const timeWindow = parseDuration(window); - const parsedQuery = JSON.parse(esQuery); - const now = Date.now(); - const { rawResponse } = await firstValueFrom( - data.search.search({ - params: buildSortedEventsQuery({ - index, - from: new Date(now - timeWindow).toISOString(), - to: new Date(now).toISOString(), - filter: parsedQuery.query, - size: 0, - searchAfterSortId: undefined, - timeField: timeField ? timeField : '', - track_total_hits: true, - }), - }) - ); - - const hits = rawResponse.hits; - setTestQueryResult( - i18n.translate('xpack.stackAlerts.esQuery.ui.numQueryMatchesText', { - defaultMessage: 'Query matched {count} documents in the last {window}.', - values: { count: totalHitsToNumber(hits.total), window }, - }) - ); - } catch (err) { - const message = err?.body?.attributes?.error?.root_cause[0]?.reason || err?.body?.message; - setTestQueryError( - i18n.translate('xpack.stackAlerts.esQuery.ui.queryError', { - defaultMessage: 'Error testing query: {message}', - values: { message: message ? `${err.message}: ${message}` : err.message }, - }) - ); - } + const onTestQuery = useCallback(async () => { + const window = `${timeWindowSize}${timeWindowUnit}`; + if (hasValidationErrors()) { + return { nrOfDocs: 0, timeWindow: window }; } - }; + const timeWindow = parseDuration(window); + const parsedQuery = JSON.parse(esQuery); + const now = Date.now(); + const { rawResponse } = await firstValueFrom( + data.search.search({ + params: buildSortedEventsQuery({ + index, + from: new Date(now - timeWindow).toISOString(), + to: new Date(now).toISOString(), + filter: parsedQuery.query, + size: 0, + searchAfterSortId: undefined, + timeField: timeField ? timeField : '', + track_total_hits: true, + }), + }) + ); + + const hits = rawResponse.hits; + return { nrOfDocs: totalHitsToNumber(hits.total), timeWindow: window }; + }, [data.search, esQuery, index, timeField, timeWindowSize, timeWindowUnit, hasValidationErrors]); return ( @@ -281,36 +258,7 @@ export const EsQueryExpression = ({ }} /> - - - - - - {testQueryResult && ( - - -

{testQueryResult}

-
-
- )} - {testQueryError && ( - - -

{testQueryError}

-
-
- )} + diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx index d12833a3f258f..091fd606e1bf0 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx @@ -14,12 +14,19 @@ import { EsQueryAlertParams, SearchType } from '../types'; import { SearchSourceExpression } from './search_source_expression'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { act } from 'react-dom/test-utils'; +import { of } from 'rxjs'; +import { IKibanaSearchResponse, ISearchSource } from '@kbn/data-plugin/common'; +import { IUiSettingsClient } from '@kbn/core/public'; +import { findTestSubject } from '@elastic/eui/lib/test'; import { EuiLoadingSpinner } from '@elastic/eui'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; const dataViewPluginMock = dataViewPluginMocks.createStartContract(); const chartsStartMock = chartPluginMock.createStartContract(); const unifiedSearchMock = unifiedSearchPluginMock.createStartContract(); +export const uiSettingsMock = { + get: jest.fn(), +} as unknown as IUiSettingsClient; const defaultSearchSourceExpressionParams: EsQueryAlertParams = { size: 100, @@ -52,7 +59,23 @@ const searchSourceMock = { } return ''; }, -}; + setField: jest.fn(), + createCopy: jest.fn(() => { + return searchSourceMock; + }), + setParent: jest.fn(() => { + return searchSourceMock; + }), + fetch$: jest.fn(() => { + return of({ + rawResponse: { + hits: { + total: 1234, + }, + }, + }); + }), +} as unknown as ISearchSource; const savedQueryMock = { id: 'test-id', @@ -67,10 +90,6 @@ const savedQueryMock = { }, }; -jest.mock('./search_source_expression_form', () => ({ - SearchSourceExpressionForm: () =>
search source expression form mock
, -})); - const dataMock = dataPluginMock.createStartContract(); (dataMock.search.searchSource.create as jest.Mock).mockImplementation(() => Promise.resolve(searchSourceMock) @@ -79,6 +98,9 @@ const dataMock = dataPluginMock.createStartContract(); (dataMock.query.savedQueries.getSavedQuery as jest.Mock).mockImplementation(() => Promise.resolve(savedQueryMock) ); +dataMock.query.savedQueries.findSavedQueries = jest.fn(() => + Promise.resolve({ total: 0, queries: [] }) +); const setup = (alertParams: EsQueryAlertParams) => { const errors = { @@ -88,8 +110,8 @@ const setup = (alertParams: EsQueryAlertParams) => { searchConfiguration: [], }; - const wrapper = mountWithIntl( - + return mountWithIntl( + ) => { /> ); - - return wrapper; }; describe('SearchSourceAlertTypeExpression', () => { test('should render correctly', async () => { - let wrapper = setup(defaultSearchSourceExpressionParams).children(); + let wrapper = setup(defaultSearchSourceExpressionParams); expect(wrapper.find(EuiLoadingSpinner).exists()).toBeTruthy(); - expect(wrapper.text().includes('Cant find searchSource')).toBeFalsy(); await act(async () => { await nextTick(); }); wrapper = await wrapper.update(); + expect(findTestSubject(wrapper, 'thresholdExpression')).toBeTruthy(); + }); + test('should show success message if Test Query is successful', async () => { + let wrapper = setup(defaultSearchSourceExpressionParams); + await act(async () => { + await nextTick(); + }); + wrapper = await wrapper.update(); + await act(async () => { + findTestSubject(wrapper, 'testQuery').simulate('click'); + wrapper.update(); + }); + wrapper = await wrapper.update(); - expect(wrapper.text().includes('Cant find searchSource')).toBeFalsy(); - expect(wrapper.text().includes('search source expression form mock')).toBeTruthy(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="testQuerySuccess"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="testQueryError"]').exists()).toBeFalsy(); + expect(wrapper.find('EuiText[data-test-subj="testQuerySuccess"]').text()).toEqual( + `Query matched 1234 documents in the last 15s.` + ); }); test('should render error prompt', async () => { (dataMock.search.searchSource.create as jest.Mock).mockImplementationOnce(() => Promise.reject(new Error('Cant find searchSource')) ); - let wrapper = setup(defaultSearchSourceExpressionParams).children(); + let wrapper = setup(defaultSearchSourceExpressionParams); expect(wrapper.find(EuiLoadingSpinner).exists()).toBeTruthy(); expect(wrapper.text().includes('Cant find searchSource')).toBeFalsy(); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx index afd6a156187ee..3c4353188af81 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -7,10 +7,12 @@ import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import deepEqual from 'fast-deep-equal'; +import { firstValueFrom } from 'rxjs'; +import { Filter } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Filter, DataView, Query, ISearchSource } from '@kbn/data-plugin/common'; +import { DataView, Query, ISearchSource, getTime } from '@kbn/data-plugin/common'; import { ForLastExpression, IErrorObject, @@ -24,6 +26,8 @@ import { EsQueryAlertParams, SearchType } from '../types'; import { DEFAULT_VALUES } from '../constants'; import { DataViewSelectPopover } from '../../components/data_view_select_popover'; import { useTriggersAndActionsUiDeps } from '../util'; +import { totalHitsToNumber } from './use_test_query'; +import { TestQueryRow } from './test_query_row'; interface LocalState { index: DataView; @@ -161,6 +165,17 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp (updatedValue: number) => dispatch({ type: 'size', payload: updatedValue }), [] ); + const onTestFetch = useCallback(async () => { + const timeWindow = `${timeWindowSize}${timeWindowUnit}`; + const testSearchSource = searchSource.createCopy(); + const timeFilter = getTime(searchSource.getField('index')!, { + from: `now-${timeWindow}`, + to: 'now', + }); + testSearchSource.setField('filter', timeFilter); + const { rawResponse } = await firstValueFrom(testSearchSource.fetch$()); + return { nrOfDocs: totalHitsToNumber(rawResponse.hits.total), timeWindow }; + }, [searchSource, timeWindowSize, timeWindowUnit]); return ( @@ -264,6 +279,8 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp onChangeSelectedValue={onChangeSizeValue} /> + + ); }; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.tsx new file mode 100644 index 0000000000000..6d18f2f0537ce --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiButtonEmpty, EuiFormRow, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useTestQuery } from './use_test_query'; + +export function TestQueryRow({ + fetch, + hasValidationErrors, +}: { + fetch: () => Promise<{ nrOfDocs: number; timeWindow: string }>; + hasValidationErrors: boolean; +}) { + const { onTestQuery, testQueryResult, testQueryError, testQueryLoading } = useTestQuery(fetch); + + return ( + <> + + + + + + {testQueryLoading && ( + + +

+ +

+
+
+ )} + {testQueryResult && ( + + +

{testQueryResult}

+
+
+ )} + {testQueryError && ( + + +

{testQueryError}

+
+
+ )} + + ); +} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.test.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.test.ts new file mode 100644 index 0000000000000..e8df349e320db --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { act } from 'react-test-renderer'; +import { useTestQuery } from './use_test_query'; + +describe('useTestQuery', () => { + test('returning a valid result', async () => { + const { result } = renderHook(useTestQuery, { + initialProps: () => Promise.resolve({ nrOfDocs: 1, timeWindow: '1s' }), + }); + await act(async () => { + await result.current.onTestQuery(); + }); + expect(result.current.testQueryLoading).toBe(false); + expect(result.current.testQueryError).toBe(null); + expect(result.current.testQueryResult).toContain('1s'); + expect(result.current.testQueryResult).toContain('1 document'); + }); + test('returning an error', async () => { + const errorMsg = 'How dare you writing such a query'; + const { result } = renderHook(useTestQuery, { + initialProps: () => Promise.reject({ message: errorMsg }), + }); + await act(async () => { + await result.current.onTestQuery(); + }); + expect(result.current.testQueryLoading).toBe(false); + expect(result.current.testQueryError).toContain(errorMsg); + expect(result.current.testQueryResult).toBe(null); + }); +}); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.ts new file mode 100644 index 0000000000000..b80500471638e --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +/** + * Hook used to test the data fetching execution by returning a number of found documents + * Or in error in case it's failing + */ +export function useTestQuery(fetch: () => Promise<{ nrOfDocs: number; timeWindow: string }>) { + const [testQueryResult, setTestQueryResult] = useState(null); + const [testQueryError, setTestQueryError] = useState(null); + const [testQueryLoading, setTestQueryLoading] = useState(false); + + const onTestQuery = useCallback(async () => { + setTestQueryLoading(true); + setTestQueryError(null); + setTestQueryResult(null); + try { + const { nrOfDocs, timeWindow } = await fetch(); + + setTestQueryResult( + i18n.translate('xpack.stackAlerts.esQuery.ui.numQueryMatchesText', { + defaultMessage: 'Query matched {count} documents in the last {window}.', + values: { count: nrOfDocs, window: timeWindow }, + }) + ); + } catch (err) { + const message = err?.body?.attributes?.error?.root_cause[0]?.reason || err?.body?.message; + setTestQueryError( + i18n.translate('xpack.stackAlerts.esQuery.ui.queryError', { + defaultMessage: 'Error testing query: {message}', + values: { message: message ? `${err.message}: ${message}` : err.message }, + }) + ); + } finally { + setTestQueryLoading(false); + } + }, [fetch]); + + return { + onTestQuery, + testQueryResult, + testQueryError, + testQueryLoading, + }; +} + +export function totalHitsToNumber(total: estypes.SearchHitsMetadata['total']): number { + return typeof total === 'number' ? total : total?.value ?? 0; +} From 9754e0b0e2f540cd929c7ac20fefb596cfdc27b2 Mon Sep 17 00:00:00 2001 From: "Lucas F. da Costa" Date: Mon, 23 May 2022 15:16:52 +0100 Subject: [PATCH 11/71] [Uptime] add flag to rerun monitors when they're edited (#132639) --- .../routes/monitor_cruds/edit_monitor.ts | 21 +-- .../synthetics_service/service_api_client.ts | 10 +- .../synthetics_service.test.ts | 145 ++++++++++++------ .../synthetics_service/synthetics_service.ts | 3 +- 4 files changed, 116 insertions(+), 63 deletions(-) diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts index 669577e8d60ee..8bd29c460b8cf 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -92,16 +92,19 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ monitor.type === 'browser' ? { ...formattedMonitor, urls: '' } : formattedMonitor ); - const errors = await syntheticsService.pushConfigs([ - { - ...editedMonitor, - id: updatedMonitor.id, - fields: { - config_id: updatedMonitor.id, + const errors = await syntheticsService.pushConfigs( + [ + { + ...editedMonitor, + id: updatedMonitor.id, + fields: { + config_id: updatedMonitor.id, + }, + fields_under_root: true, }, - fields_under_root: true, - }, - ]); + ], + true + ); sendTelemetryEvents( logger, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts index 41ae8a5a55090..a688b23baef9d 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts @@ -24,6 +24,7 @@ export interface ServiceData { api_key: string; }; runOnce?: boolean; + isEdit?: boolean; } export class ServiceAPIClient { @@ -118,7 +119,7 @@ export class ServiceAPIClient { async callAPI( method: 'POST' | 'PUT' | 'DELETE', - { monitors: allMonitors, output, runOnce }: ServiceData + { monitors: allMonitors, output, runOnce, isEdit }: ServiceData ) { if (this.username === TEST_SERVICE_USERNAME) { // we don't want to call service while local integration tests are running @@ -134,7 +135,12 @@ export class ServiceAPIClient { return axios({ method, url: url + (runOnce ? '/run' : '/monitors'), - data: { monitors: monitorsStreams, output, stack_version: this.kibanaVersion }, + data: { + monitors: monitorsStreams, + output, + stack_version: this.kibanaVersion, + is_edit: isEdit, + }, headers: process.env.NODE_ENV !== 'production' && this.authorization ? { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts index 952e18ce9c884..6654fdbf2a132 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -12,6 +12,7 @@ import { SyntheticsService, SyntheticsConfig } from './synthetics_service'; import { loggerMock } from '@kbn/core/server/logging/logger.mock'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import axios, { AxiosResponse } from 'axios'; +import times from 'lodash/times'; describe('SyntheticsService', () => { const mockEsClient = { @@ -27,6 +28,60 @@ describe('SyntheticsService', () => { const logger = loggerMock.create(); + const getMockedService = (locationsNum: number = 1) => { + serverMock.config = { service: { devUrl: 'http://localhost' } }; + const service = new SyntheticsService(logger, serverMock, { + username: 'dev', + password: '12345', + }); + + const locations = times(locationsNum).map((n) => { + return { + id: `loc-${n}`, + label: `Location ${n}`, + url: `example.com/${n}`, + geo: { + lat: 0, + lon: 0, + }, + isServiceManaged: true, + }; + }); + + service.apiClient.locations = locations; + + jest.spyOn(service, 'getApiKey').mockResolvedValue({ name: 'example', id: 'i', apiKey: 'k' }); + jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' }); + + return { service, locations }; + }; + + const getFakePayload = (locations: SyntheticsConfig['locations']) => { + return { + type: 'http', + enabled: true, + schedule: { + number: '3', + unit: 'm', + }, + name: 'my mon', + locations, + urls: 'http://google.com', + max_redirects: '0', + password: '', + proxy_url: '', + id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d', + fields: { config_id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d' }, + fields_under_root: true, + }; + }; + + beforeEach(() => { + (axios as jest.MockedFunction).mockReset(); + }); + + afterEach(() => jest.restoreAllMocks()); + it('inits properly', async () => { const service = new SyntheticsService(logger, serverMock, {}); service.init(); @@ -72,58 +127,10 @@ describe('SyntheticsService', () => { }); describe('addConfig', () => { - afterEach(() => jest.restoreAllMocks()); - it('saves configs only to the selected locations', async () => { - serverMock.config = { service: { devUrl: 'http://localhost' } }; - const service = new SyntheticsService(logger, serverMock, { - username: 'dev', - password: '12345', - }); - - service.apiClient.locations = [ - { - id: 'selected', - label: 'Selected Location', - url: 'example.com/1', - geo: { - lat: 0, - lon: 0, - }, - isServiceManaged: true, - }, - { - id: 'not selected', - label: 'Not Selected Location', - url: 'example.com/2', - geo: { - lat: 0, - lon: 0, - }, - isServiceManaged: true, - }, - ]; + const { service, locations } = getMockedService(3); - jest.spyOn(service, 'getApiKey').mockResolvedValue({ name: 'example', id: 'i', apiKey: 'k' }); - jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' }); - - const payload = { - type: 'http', - enabled: true, - schedule: { - number: '3', - unit: 'm', - }, - name: 'my mon', - locations: [{ id: 'selected', isServiceManaged: true }], - urls: 'http://google.com', - max_redirects: '0', - password: '', - proxy_url: '', - id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d', - fields: { config_id: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d' }, - fields_under_root: true, - }; + const payload = getFakePayload([locations[0]]); (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); @@ -132,7 +139,43 @@ describe('SyntheticsService', () => { expect(axios).toHaveBeenCalledTimes(1); expect(axios).toHaveBeenCalledWith( expect.objectContaining({ - url: 'example.com/1/monitors', + url: locations[0].url + '/monitors', + }) + ); + }); + }); + + describe('pushConfigs', () => { + it('does not include the isEdit flag on normal push requests', async () => { + const { service, locations } = getMockedService(); + + (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); + + const payload = getFakePayload([locations[0]]); + + await service.pushConfigs([payload] as SyntheticsConfig[]); + + expect(axios).toHaveBeenCalledTimes(1); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ is_edit: false }), + }) + ); + }); + + it('includes the isEdit flag on edit requests', async () => { + const { service, locations } = getMockedService(); + + (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); + + const payload = getFakePayload([locations[0]]); + + await service.pushConfigs([payload] as SyntheticsConfig[], true); + + expect(axios).toHaveBeenCalledTimes(1); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ is_edit: true }), }) ); }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index b1af1717e1a1c..35861786a38dc 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -251,7 +251,7 @@ export class SyntheticsService { } } - async pushConfigs(configs?: SyntheticsConfig[]) { + async pushConfigs(configs?: SyntheticsConfig[], isEdit?: boolean) { const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs())); if (monitors.length === 0) { this.logger.debug('No monitor found which can be pushed to service.'); @@ -267,6 +267,7 @@ export class SyntheticsService { const data = { monitors, output: await this.getOutput(this.apiKey), + isEdit: !!isEdit, }; this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`); From 09039ac3364bf3c939070a03117bf4e2b7b7407f Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Mon, 23 May 2022 16:20:57 +0200 Subject: [PATCH 12/71] [Osquery] Change discover to open in a new tab (#131894) --- .../cypress/integration/all/discover.spec.ts | 39 +++++++++++++++ .../cypress/integration/all/packs.spec.ts | 50 ++++++++++++------- .../packs/pack_queries_status_table.tsx | 7 ++- .../scripts/roles_users/soc_manager/role.json | 2 +- 4 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/osquery/cypress/integration/all/discover.spec.ts diff --git a/x-pack/plugins/osquery/cypress/integration/all/discover.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/discover.spec.ts new file mode 100644 index 0000000000000..3e47b983dcda4 --- /dev/null +++ b/x-pack/plugins/osquery/cypress/integration/all/discover.spec.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { login } from '../../tasks/login'; +import { navigateTo } from '../../tasks/navigation'; +import { checkResults, inputQuery, selectAllAgents, submitQuery } from '../../tasks/live_query'; +import { ROLES } from '../../test'; + +// TODO: So far just one test, but this is a good place to start. Move tests from pack view into here. +describe('ALL - Discover', () => { + beforeEach(() => { + login(ROLES.soc_manager); + navigateTo('/app/osquery'); + }); + + it('should be opened in new tab in results table', () => { + cy.contains('New live query').click(); + selectAllAgents(); + inputQuery('select * from uptime; '); + submitQuery(); + checkResults(); + cy.contains('View in Lens').should('exist'); + cy.contains('View in Discover') + .should('exist') + .should('have.attr', 'href') + .then(($href) => { + // @ts-expect-error-next-line href string - check types + cy.visit($href); + cy.getBySel('breadcrumbs').contains('Discover').should('exist'); + cy.getBySel('discoverDocTable', { timeout: 60000 }).contains( + 'action_data.queryselect * from uptime' + ); + }); + }); +}); diff --git a/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts index b765a9d16ef7e..4a8842d21c9b1 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts @@ -59,7 +59,7 @@ describe('ALL - Packs', () => { cy.react('EuiFormRow', { props: { label: 'Interval (s)' } }) .click() .clear() - .type('500'); + .type('10'); cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); cy.react('EuiTableRow').contains(SAVED_QUERY_ID); findAndClickButton('Save pack'); @@ -96,21 +96,7 @@ describe('ALL - Packs', () => { cy.contains('ID must be unique').should('exist'); cy.react('EuiFlyoutFooter').react('EuiButtonEmpty').contains('Cancel').click(); }); - // THIS TESTS TAKES TOO LONG FOR NOW - LET ME THINK IT THROUGH - it.skip('to click the icon and visit discover', () => { - preparePack(PACK_NAME); - cy.react('CustomItemAction', { - props: { index: 0, item: { id: SAVED_QUERY_ID } }, - }).click(); - cy.getBySel('superDatePickerToggleQuickMenuButton').click(); - cy.getBySel('superDatePickerToggleRefreshButton').click(); - cy.getBySel('superDatePickerRefreshIntervalInput').clear().type('10'); - cy.get('button').contains('Apply').click(); - cy.getBySel('discoverDocTable', { timeout: 60000 }).contains( - `pack_${PACK_NAME}_${SAVED_QUERY_ID}` - ); - }); - it.skip('by clicking in Lens button', () => { + it('should open lens in new tab', () => { let lensUrl = ''; cy.window().then((win) => { cy.stub(win, 'open') @@ -122,17 +108,43 @@ describe('ALL - Packs', () => { preparePack(PACK_NAME); cy.react('CustomItemAction', { props: { index: 1, item: { id: SAVED_QUERY_ID } }, - }).click(); + }) + .should('exist') + .click(); cy.window() .its('open') .then(() => { cy.visit(lensUrl); }); - cy.getBySel('lnsWorkspace'); + cy.getBySel('lnsWorkspace').should('exist'); cy.getBySel('breadcrumbs').contains(`Action pack_${PACK_NAME}_${SAVED_QUERY_ID} results`); }); - // strange behaviour with modal + // TODO extremely strange behaviour with Cypress not finding Discover's page elements + // it('should open discover in new tab', () => { + // preparePack(PACK_NAME); + // cy.wait(1000); + // cy.react('CustomItemAction', { + // props: { index: 0, item: { id: SAVED_QUERY_ID } }, + // }) + // .should('exist') + // .within(() => { + // cy.get('a') + // .should('have.attr', 'href') + // .then(($href) => { + // // @ts-expect-error-next-line href string - check types + // cy.visit($href); + // cy.getBySel('breadcrumbs').contains('Discover').should('exist'); + // cy.contains(`action_id: pack_${PACK_NAME}_${SAVED_QUERY_ID}`); + // cy.getBySel('superDatePickerToggleQuickMenuButton').click(); + // cy.getBySel('superDatePickerCommonlyUsed_Today').click(); + // cy.getBySel('discoverDocTable', { timeout: 60000 }).contains( + // `pack_${PACK_NAME}_${SAVED_QUERY_ID}` + // ); + // }); + // }); + // }); + it('activate and deactive pack', () => { cy.contains('Packs').click(); cy.react('ActiveStateSwitchComponent', { diff --git a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx index 3aa345f986493..178bbe3536834 100644 --- a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx @@ -355,7 +355,12 @@ const ViewResultsInDiscoverActionComponent: React.FC - + ); }; diff --git a/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json b/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json index e3c97c0fe6560..e427fece0ea01 100644 --- a/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json +++ b/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json @@ -3,7 +3,7 @@ "cluster": ["manage"], "indices": [ { - "names": [".items-*", ".lists-*", ".alerts-security.alerts-*", ".siem-signals-*"], + "names": [".items-*", ".lists-*", ".alerts-security.alerts-*", ".siem-signals-*", "logs-*"], "privileges": ["manage", "read", "write", "view_index_metadata", "maintenance"] }, { From 9a8993268d9531a7036140e63c741b0d5c5cebdd Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 23 May 2022 16:37:52 +0200 Subject: [PATCH 13/71] Use kibana feature privileges with api key (#130918) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Portner --- packages/kbn-utility-types/src/index.ts | 12 + .../api_keys/api_keys.test.mock.ts | 20 ++ .../authentication/api_keys/api_keys.test.ts | 206 ++++++++++- .../authentication/api_keys/api_keys.ts | 110 +++++- .../server/authentication/api_keys/index.ts | 2 +- .../authentication_service.test.ts | 4 + .../authentication/authentication_service.ts | 7 + x-pack/plugins/security/server/lib/index.ts | 13 + .../security/server/lib/role_schema.ts | 212 +++++++++++ .../security/server/lib/role_utils.test.ts | 26 ++ .../plugins/security/server/lib/role_utils.ts | 109 ++++++ x-pack/plugins/security/server/plugin.ts | 8 +- .../server/routes/api_keys/create.test.ts | 1 + .../security/server/routes/api_keys/create.ts | 51 ++- .../server/routes/authorization/roles/get.ts | 2 +- .../routes/authorization/roles/get_all.ts | 2 +- .../routes/authorization/roles/model/index.ts | 9 +- .../roles/model/put_payload.test.ts | 3 +- .../authorization/roles/model/put_payload.ts | 339 ++---------------- .../server/routes/authorization/roles/put.ts | 14 +- .../routes/monitor_cruds/add_monitor.ts | 20 +- .../server/synthetics_service/get_api_key.ts | 5 +- .../api_integration/apis/security/api_keys.ts | 35 ++ .../apis/uptime/rest/add_monitor.ts | 101 +++++- .../apis/uptime/rest/synthetics_enablement.ts | 8 +- 25 files changed, 939 insertions(+), 380 deletions(-) create mode 100644 x-pack/plugins/security/server/authentication/api_keys/api_keys.test.mock.ts create mode 100644 x-pack/plugins/security/server/lib/index.ts create mode 100644 x-pack/plugins/security/server/lib/role_schema.ts create mode 100644 x-pack/plugins/security/server/lib/role_utils.test.ts create mode 100644 x-pack/plugins/security/server/lib/role_utils.ts diff --git a/packages/kbn-utility-types/src/index.ts b/packages/kbn-utility-types/src/index.ts index 50741bbd96954..6d040d7aac8f7 100644 --- a/packages/kbn-utility-types/src/index.ts +++ b/packages/kbn-utility-types/src/index.ts @@ -110,3 +110,15 @@ export type PublicMethodsOf = Pick>; export type Writable = { -readonly [K in keyof T]: T[K]; }; + +/** + * XOR for some properties applied to a type + * (XOR is one of these but not both or neither) + * + * Usage: OneOf + * + * To require aria-label or aria-labelledby but not both + * Example: OneOf + */ +export type OneOf = Omit & + { [k in K]: Pick, k> & { [k1 in Exclude]?: never } }[K]; diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.mock.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.mock.ts new file mode 100644 index 0000000000000..47cd7688fb468 --- /dev/null +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.mock.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as Lib from '../../lib'; + +export const mockValidateKibanaPrivileges = jest.fn() as jest.MockedFunction< + typeof Lib['validateKibanaPrivileges'] +>; + +jest.mock('../../lib', () => { + const actual = jest.requireActual('../../lib'); + return { + ...actual, + validateKibanaPrivileges: mockValidateKibanaPrivileges, + }; +}); diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts index b5332a7296062..2aa318acff59d 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts @@ -5,12 +5,16 @@ * 2.0. */ +// eslint-disable-next-line import/order +import { mockValidateKibanaPrivileges } from './api_keys.test.mock'; + import { elasticsearchServiceMock, httpServerMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import { ALL_SPACES_ID } from '../../../common/constants'; import type { SecurityLicense } from '../../../common/licensing'; import { licenseMock } from '../../../common/licensing/index.mock'; import { APIKeys } from './api_keys'; @@ -26,6 +30,8 @@ describe('API Keys', () => { let mockLicense: jest.Mocked; beforeEach(() => { + mockValidateKibanaPrivileges.mockReset().mockReturnValue({ validationErrors: [] }); + mockClusterClient = elasticsearchServiceMock.createClusterClient(); mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockClusterClient.asScoped.mockReturnValue(mockScopedClusterClient); @@ -37,6 +43,8 @@ describe('API Keys', () => { clusterClient: mockClusterClient, logger: loggingSystemMock.create().get('api-keys'), license: mockLicense, + applicationName: 'kibana-.kibana', + kibanaFeatures: [], }); }); @@ -135,6 +143,32 @@ describe('API Keys', () => { role_descriptors: {}, }); expect(result).toBeNull(); + expect(mockValidateKibanaPrivileges).not.toHaveBeenCalled(); + expect(mockScopedClusterClient.asCurrentUser.security.createApiKey).not.toHaveBeenCalled(); + }); + + it('throws an error when kibana privilege validation fails', async () => { + mockLicense.isEnabled.mockReturnValue(true); + mockValidateKibanaPrivileges + .mockReturnValueOnce({ validationErrors: ['error1'] }) // for descriptor1 + .mockReturnValueOnce({ validationErrors: [] }) // for descriptor2 + .mockReturnValueOnce({ validationErrors: ['error2'] }); // for descriptor3 + + await expect( + apiKeys.create(httpServerMock.createKibanaRequest(), { + name: 'key-name', + kibana_role_descriptors: { + descriptor1: { elasticsearch: {}, kibana: [] }, + descriptor2: { elasticsearch: {}, kibana: [] }, + descriptor3: { elasticsearch: {}, kibana: [] }, + }, + expiration: '1d', + }) + ).rejects.toEqual( + // The validation errors from descriptor1 and descriptor3 are concatenated into the final error message + new Error('API key cannot be created due to validation errors: ["error1","error2"]') + ); + expect(mockValidateKibanaPrivileges).toHaveBeenCalledTimes(3); expect(mockScopedClusterClient.asCurrentUser.security.createApiKey).not.toHaveBeenCalled(); }); @@ -159,6 +193,7 @@ describe('API Keys', () => { id: '123', name: 'key-name', }); + expect(mockValidateKibanaPrivileges).not.toHaveBeenCalled(); // this is only called if kibana_role_descriptors is defined expect(mockScopedClusterClient.asCurrentUser.security.createApiKey).toHaveBeenCalledWith({ body: { name: 'key-name', @@ -177,7 +212,37 @@ describe('API Keys', () => { role_descriptors: {}, }); expect(result).toBeNull(); + expect(mockValidateKibanaPrivileges).not.toHaveBeenCalled(); + expect(mockClusterClient.asInternalUser.security.grantApiKey).not.toHaveBeenCalled(); + }); + + it('throws an error when kibana privilege validation fails', async () => { + mockLicense.isEnabled.mockReturnValue(true); + mockValidateKibanaPrivileges + .mockReturnValueOnce({ validationErrors: ['error1'] }) // for descriptor1 + .mockReturnValueOnce({ validationErrors: [] }) // for descriptor2 + .mockReturnValueOnce({ validationErrors: ['error2'] }); // for descriptor3 + await expect( + apiKeys.grantAsInternalUser( + httpServerMock.createKibanaRequest({ + headers: { authorization: `Basic ${encodeToBase64('foo:bar')}` }, + }), + { + name: 'key-name', + kibana_role_descriptors: { + descriptor1: { elasticsearch: {}, kibana: [] }, + descriptor2: { elasticsearch: {}, kibana: [] }, + descriptor3: { elasticsearch: {}, kibana: [] }, + }, + expiration: '1d', + } + ) + ).rejects.toEqual( + // The validation errors from descriptor1 and descriptor3 are concatenated into the final error message + new Error('API key cannot be created due to validation errors: ["error1","error2"]') + ); + expect(mockValidateKibanaPrivileges).toHaveBeenCalledTimes(3); expect(mockClusterClient.asInternalUser.security.grantApiKey).not.toHaveBeenCalled(); }); @@ -192,9 +257,7 @@ describe('API Keys', () => { }); const result = await apiKeys.grantAsInternalUser( httpServerMock.createKibanaRequest({ - headers: { - authorization: `Basic ${encodeToBase64('foo:bar')}`, - }, + headers: { authorization: `Basic ${encodeToBase64('foo:bar')}` }, }), { name: 'test_api_key', @@ -208,6 +271,7 @@ describe('API Keys', () => { name: 'key-name', expires: '1d', }); + expect(mockValidateKibanaPrivileges).not.toHaveBeenCalled(); // this is only called if kibana_role_descriptors is defined expect(mockClusterClient.asInternalUser.security.grantApiKey).toHaveBeenCalledWith({ body: { api_key: { @@ -231,9 +295,7 @@ describe('API Keys', () => { }); const result = await apiKeys.grantAsInternalUser( httpServerMock.createKibanaRequest({ - headers: { - authorization: `Bearer foo-access-token`, - }, + headers: { authorization: `Bearer foo-access-token` }, }), { name: 'test_api_key', @@ -246,6 +308,7 @@ describe('API Keys', () => { id: '123', name: 'key-name', }); + expect(mockValidateKibanaPrivileges).not.toHaveBeenCalled(); // this is only called if kibana_role_descriptors is defined expect(mockClusterClient.asInternalUser.security.grantApiKey).toHaveBeenCalledWith({ body: { api_key: { @@ -277,6 +340,7 @@ describe('API Keys', () => { ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unsupported scheme \\"Digest\\" for granting API Key"` ); + expect(mockValidateKibanaPrivileges).not.toHaveBeenCalled(); expect(mockClusterClient.asInternalUser.security.grantApiKey).not.toHaveBeenCalled(); }); }); @@ -398,4 +462,134 @@ describe('API Keys', () => { }); }); }); + + describe('with kibana privileges', () => { + it('creates api key with application privileges', async () => { + mockLicense.isEnabled.mockReturnValue(true); + + mockScopedClusterClient.asCurrentUser.security.createApiKey.mockResponseOnce({ + id: '123', + name: 'key-name', + // @ts-expect-error @elastic/elsticsearch CreateApiKeyResponse.expiration: number + expiration: '1d', + api_key: 'abc123', + }); + const result = await apiKeys.create(httpServerMock.createKibanaRequest(), { + name: 'key-name', + kibana_role_descriptors: { + synthetics_writer: { + elasticsearch: { cluster: ['manage'], indices: [], run_as: [] }, + kibana: [ + { + base: [], + spaces: [ALL_SPACES_ID], + feature: { + uptime: ['all'], + }, + }, + ], + }, + }, + expiration: '1d', + }); + expect(result).toEqual({ + api_key: 'abc123', + expiration: '1d', + id: '123', + name: 'key-name', + }); + expect(mockScopedClusterClient.asCurrentUser.security.createApiKey).toHaveBeenCalledWith({ + body: { + name: 'key-name', + role_descriptors: { + synthetics_writer: { + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_uptime.all'], + resources: ['*'], + }, + ], + cluster: ['manage'], + indices: [], + run_as: [], + }, + }, + expiration: '1d', + }, + }); + }); + + it('creates api key with application privileges as internal user', async () => { + mockLicense.isEnabled.mockReturnValue(true); + + mockClusterClient.asInternalUser.security.grantApiKey.mockResponseOnce({ + id: '123', + name: 'key-name', + api_key: 'abc123', + // @ts-expect-error invalid definition + expires: '1d', + }); + const result = await apiKeys.grantAsInternalUser( + httpServerMock.createKibanaRequest({ + headers: { + authorization: `Basic ${encodeToBase64('foo:bar')}`, + }, + }), + { + name: 'key-name', + kibana_role_descriptors: { + synthetics_writer: { + elasticsearch: { + cluster: ['manage'], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + spaces: [ALL_SPACES_ID], + feature: { + uptime: ['all'], + }, + }, + ], + }, + }, + expiration: '1d', + } + ); + expect(result).toEqual({ + api_key: 'abc123', + expires: '1d', + id: '123', + name: 'key-name', + }); + expect(mockClusterClient.asInternalUser.security.grantApiKey).toHaveBeenCalledWith({ + body: { + api_key: { + name: 'key-name', + role_descriptors: { + synthetics_writer: { + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_uptime.all'], + resources: ['*'], + }, + ], + cluster: ['manage'], + indices: [], + run_as: [], + }, + }, + expiration: '1d', + }, + grant_type: 'password', + password: 'bar', + username: 'foo', + }, + }); + }); + }); }); diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts index 82b27212182d6..0eef6fac74035 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts @@ -5,9 +5,15 @@ * 2.0. */ +/* eslint-disable max-classes-per-file */ + import type { IClusterClient, KibanaRequest, Logger } from '@kbn/core/server'; +import type { KibanaFeature } from '@kbn/features-plugin/server'; +import type { OneOf } from '@kbn/utility-types'; import type { SecurityLicense } from '../../../common/licensing'; +import type { ElasticsearchPrivilegesType, KibanaPrivilegesType } from '../../lib'; +import { transformPrivilegesToElasticsearchPrivileges, validateKibanaPrivileges } from '../../lib'; import { BasicHTTPAuthorizationHeaderCredentials, HTTPAuthorizationHeader, @@ -21,18 +27,29 @@ export interface ConstructorOptions { logger: Logger; clusterClient: IClusterClient; license: SecurityLicense; + applicationName: string; + kibanaFeatures: KibanaFeature[]; } -/** - * Represents the params for creating an API key - */ -export interface CreateAPIKeyParams { +interface BaseCreateAPIKeyParams { name: string; - role_descriptors: Record; expiration?: string; metadata?: Record; + role_descriptors: Record; + kibana_role_descriptors: Record< + string, + { elasticsearch: ElasticsearchPrivilegesType; kibana: KibanaPrivilegesType } + >; } +/** + * Represents the params for creating an API key + */ +export type CreateAPIKeyParams = OneOf< + BaseCreateAPIKeyParams, + 'role_descriptors' | 'kibana_role_descriptors' +>; + type GrantAPIKeyParams = | { api_key: CreateAPIKeyParams; @@ -129,11 +146,21 @@ export class APIKeys { private readonly logger: Logger; private readonly clusterClient: IClusterClient; private readonly license: SecurityLicense; - - constructor({ logger, clusterClient, license }: ConstructorOptions) { + private readonly applicationName: string; + private readonly kibanaFeatures: KibanaFeature[]; + + constructor({ + logger, + clusterClient, + license, + applicationName, + kibanaFeatures, + }: ConstructorOptions) { this.logger = logger; this.clusterClient = clusterClient; this.license = license; + this.applicationName = applicationName; + this.kibanaFeatures = kibanaFeatures; } /** @@ -171,30 +198,33 @@ export class APIKeys { * Returns newly created API key or `null` if API keys are disabled. * * @param request Request instance. - * @param params The params to create an API key + * @param createParams The params to create an API key */ async create( request: KibanaRequest, - params: CreateAPIKeyParams + createParams: CreateAPIKeyParams ): Promise { if (!this.license.isEnabled()) { return null; } + const { expiration, metadata, name } = createParams; + + const roleDescriptors = this.parseRoleDescriptorsWithKibanaPrivileges(createParams); + this.logger.debug('Trying to create an API key'); // User needs `manage_api_key` privilege to use this API let result: CreateAPIKeyResult; try { - result = await this.clusterClient - .asScoped(request) - .asCurrentUser.security.createApiKey({ body: params }); + result = await this.clusterClient.asScoped(request).asCurrentUser.security.createApiKey({ + body: { role_descriptors: roleDescriptors, name, metadata, expiration }, + }); this.logger.debug('API key was created successfully'); } catch (e) { this.logger.error(`Failed to create API key: ${e.message}`); throw e; } - return result; } @@ -215,7 +245,14 @@ export class APIKeys { `Unable to grant an API Key, request does not contain an authorization header` ); } - const params = this.getGrantParams(createParams, authorizationHeader); + const { expiration, metadata, name } = createParams; + + const roleDescriptors = this.parseRoleDescriptorsWithKibanaPrivileges(createParams); + + const params = this.getGrantParams( + { expiration, metadata, name, role_descriptors: roleDescriptors }, + authorizationHeader + ); // User needs `manage_api_key` or `grant_api_key` privilege to use this API let result: GrantAPIKeyResult; @@ -329,4 +366,49 @@ export class APIKeys { throw new Error(`Unsupported scheme "${authorizationHeader.scheme}" for granting API Key`); } + + private parseRoleDescriptorsWithKibanaPrivileges(createParams: CreateAPIKeyParams) { + if (createParams.role_descriptors) { + return createParams.role_descriptors; + } + + const roleDescriptors = Object.create(null); + + const { kibana_role_descriptors: kibanaRoleDescriptors } = createParams; + + const allValidationErrors: string[] = []; + if (kibanaRoleDescriptors) { + Object.entries(kibanaRoleDescriptors).forEach(([roleKey, roleDescriptor]) => { + const { validationErrors } = validateKibanaPrivileges( + this.kibanaFeatures, + roleDescriptor.kibana + ); + allValidationErrors.push(...validationErrors); + + const applications = transformPrivilegesToElasticsearchPrivileges( + this.applicationName, + roleDescriptor.kibana + ); + if (applications.length > 0 && roleDescriptors) { + roleDescriptors[roleKey] = { + ...roleDescriptor.elasticsearch, + applications, + }; + } + }); + } + if (allValidationErrors.length) { + throw new CreateApiKeyValidationError( + `API key cannot be created due to validation errors: ${JSON.stringify(allValidationErrors)}` + ); + } + + return roleDescriptors; + } +} + +export class CreateApiKeyValidationError extends Error { + constructor(message: string) { + super(message); + } } diff --git a/x-pack/plugins/security/server/authentication/api_keys/index.ts b/x-pack/plugins/security/server/authentication/api_keys/index.ts index 44fe48f04debb..c3ef6f6f139a9 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/index.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/index.ts @@ -12,4 +12,4 @@ export type { InvalidateAPIKeysParams, GrantAPIKeyResult, } from './api_keys'; -export { APIKeys } from './api_keys'; +export { APIKeys, CreateApiKeyValidationError } from './api_keys'; diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index 585dd54dd8d5a..8882797380397 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -69,6 +69,8 @@ describe('AuthenticationService', () => { clusterClient: ReturnType; featureUsageService: jest.Mocked; session: jest.Mocked>; + applicationName: 'kibana-.kibana'; + kibanaFeatures: []; }; beforeEach(() => { logger = loggingSystemMock.createLogger(); @@ -107,6 +109,8 @@ describe('AuthenticationService', () => { loggers: loggingSystemMock.create(), featureUsageService: securityFeatureUsageServiceMock.createStartContract(), session: sessionMock.create(), + applicationName: 'kibana-.kibana', + kibanaFeatures: [], }; (mockStartAuthenticationParams.http.basePath.get as jest.Mock).mockImplementation( () => mockStartAuthenticationParams.http.basePath.serverBasePath diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index ed461b0148a89..0318fe3823a46 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -14,6 +14,7 @@ import type { Logger, LoggerFactory, } from '@kbn/core/server'; +import type { KibanaFeature } from '@kbn/features-plugin/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { AuthenticatedUser, SecurityLicense } from '../../common'; @@ -49,6 +50,8 @@ interface AuthenticationServiceStartParams { featureUsageService: SecurityFeatureUsageServiceStart; session: PublicMethodsOf; loggers: LoggerFactory; + applicationName: string; + kibanaFeatures: KibanaFeature[]; } export interface InternalAuthenticationServiceStart extends AuthenticationServiceStart { @@ -296,11 +299,15 @@ export class AuthenticationService { http, loggers, session, + applicationName, + kibanaFeatures, }: AuthenticationServiceStartParams): InternalAuthenticationServiceStart { const apiKeys = new APIKeys({ clusterClient, logger: this.logger.get('api-key'), license: this.license, + applicationName, + kibanaFeatures, }); /** diff --git a/x-pack/plugins/security/server/lib/index.ts b/x-pack/plugins/security/server/lib/index.ts new file mode 100644 index 0000000000000..1a1ae84e2af65 --- /dev/null +++ b/x-pack/plugins/security/server/lib/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { ElasticsearchPrivilegesType, KibanaPrivilegesType } from './role_schema'; +export { elasticsearchRoleSchema, getKibanaRoleSchema } from './role_schema'; +export { + validateKibanaPrivileges, + transformPrivilegesToElasticsearchPrivileges, +} from './role_utils'; diff --git a/x-pack/plugins/security/server/lib/role_schema.ts b/x-pack/plugins/security/server/lib/role_schema.ts new file mode 100644 index 0000000000000..135a24cf04085 --- /dev/null +++ b/x-pack/plugins/security/server/lib/role_schema.ts @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import _ from 'lodash'; + +import type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +import { GLOBAL_RESOURCE } from '../../common/constants'; + +/** + * Elasticsearch specific portion of the role definition. + * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. + */ +export const elasticsearchRoleSchema = schema.object({ + /** + * An optional list of cluster privileges. These privileges define the cluster level actions that + * users with this role are able to execute + */ + cluster: schema.maybe(schema.arrayOf(schema.string())), + + /** + * An optional list of indices permissions entries. + */ + indices: schema.maybe( + schema.arrayOf( + schema.object({ + /** + * Required list of indices (or index name patterns) to which the permissions in this + * entry apply. + */ + names: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional set of the document fields that the owners of the role have read access to. + */ + field_security: schema.maybe( + schema.recordOf( + schema.oneOf([schema.literal('grant'), schema.literal('except')]), + schema.arrayOf(schema.string()) + ) + ), + + /** + * Required list of the index level privileges that the owners of the role have on the + * specified indices. + */ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional search query that defines the documents the owners of the role have read access + * to. A document within the specified indices must match this query in order for it to be + * accessible by the owners of the role. + */ + query: schema.maybe(schema.string()), + + /** + * An optional flag used to indicate if index pattern wildcards or regexps should cover + * restricted indices. + */ + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + + /** + * An optional list of users that the owners of this role can impersonate. + */ + run_as: schema.maybe(schema.arrayOf(schema.string())), +}); + +const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { + minSize: 1, + maxSize: 1, +}); + +/** + * Schema for the list of space IDs used within Kibana specific role definition. + */ +const spacesSchema = schema.oneOf( + [ + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + if (!/^[a-z0-9_-]+$/.test(value)) { + return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ), + ], + { defaultValue: [GLOBAL_RESOURCE] } +); + +const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; + +/** + * Kibana specific portion of the role definition. It's represented as a list of base and/or + * feature Kibana privileges. None of the entries should apply to the same spaces. + */ +export const getKibanaRoleSchema = ( + getBasePrivilegeNames: () => { global: string[]; space: string[] } +) => + schema.arrayOf( + schema.object( + { + /** + * An optional list of space IDs to which the permissions in this entry apply. If not + * specified it defaults to special "global" space ID (all spaces). + */ + spaces: spacesSchema, + + /** + * An optional list of Kibana base privileges. If this entry applies to special "global" + * space (all spaces) then specified base privileges should be within known base "global" + * privilege list, otherwise - within known "space" privilege list. Base privileges + * definition isn't allowed when feature privileges are defined and required otherwise. + */ + base: schema.maybe( + schema.conditional( + schema.siblingRef('spaces'), + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + const globalPrivileges = getBasePrivilegeNames().global; + if (!globalPrivileges.some((privilege) => privilege === value)) { + return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; + } + }, + }) + ), + schema.arrayOf( + schema.string({ + validate(value) { + const spacePrivileges = getBasePrivilegeNames().space; + if (!spacePrivileges.some((privilege) => privilege === value)) { + return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; + } + }, + }) + ) + ) + ), + + /** + * An optional dictionary of Kibana feature privileges where the key is the ID of the + * feature and the value is a list of feature specific privilege IDs. Both feature and + * privilege IDs should consist of allowed set of characters. Feature privileges + * definition isn't allowed when base privileges are defined and required otherwise. + */ + feature: schema.maybe( + schema.recordOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }), + schema.arrayOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ) + ) + ), + }, + { + validate(value) { + if ( + (value.base === undefined || value.base.length === 0) && + (value.feature === undefined || Object.values(value.feature).flat().length === 0) + ) { + return 'either [base] or [feature] is expected, but none of them specified'; + } + + if ( + value.base !== undefined && + value.base.length > 0 && + value.feature !== undefined && + Object.keys(value.feature).length > 0 + ) { + return `definition of [feature] isn't allowed when non-empty [base] is defined.`; + } + }, + } + ), + { + validate(value) { + for (const [indexA, valueA] of value.entries()) { + for (const valueB of value.slice(indexA + 1)) { + const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); + if (spaceIntersection.length !== 0) { + return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; + } + } + } + }, + } + ); + +export type ElasticsearchPrivilegesType = TypeOf; +export type KibanaPrivilegesType = TypeOf>; diff --git a/x-pack/plugins/security/server/lib/role_utils.test.ts b/x-pack/plugins/security/server/lib/role_utils.test.ts new file mode 100644 index 0000000000000..6c04b6121c2d1 --- /dev/null +++ b/x-pack/plugins/security/server/lib/role_utils.test.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ALL_SPACES_ID } from '../../common/constants'; +import { transformPrivilegesToElasticsearchPrivileges } from './role_utils'; + +describe('transformPrivilegesToElasticsearchPrivileges', () => { + test('returns expected result', () => { + expect( + transformPrivilegesToElasticsearchPrivileges('kibana,-kibana', [ + { + spaces: [ALL_SPACES_ID], + feature: { + uptime: ['all'], + }, + }, + ]) + ).toEqual([ + { application: 'kibana,-kibana', privileges: ['feature_uptime.all'], resources: ['*'] }, + ]); + }); +}); diff --git a/x-pack/plugins/security/server/lib/role_utils.ts b/x-pack/plugins/security/server/lib/role_utils.ts new file mode 100644 index 0000000000000..c852079081821 --- /dev/null +++ b/x-pack/plugins/security/server/lib/role_utils.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaFeature } from '@kbn/features-plugin/server'; + +import { ALL_SPACES_ID, GLOBAL_RESOURCE } from '../../common/constants'; +import { PrivilegeSerializer } from '../authorization/privilege_serializer'; +import { ResourceSerializer } from '../authorization/resource_serializer'; +import type { KibanaPrivilegesType } from './role_schema'; + +export const transformPrivilegesToElasticsearchPrivileges = ( + application: string, + kibanaPrivileges: KibanaPrivilegesType = [] +) => { + return kibanaPrivileges.map(({ base, feature, spaces }) => { + if (spaces.length === 1 && spaces[0] === GLOBAL_RESOURCE) { + return { + privileges: [ + ...(base + ? base.map((privilege) => PrivilegeSerializer.serializeGlobalBasePrivilege(privilege)) + : []), + ...(feature + ? Object.entries(feature) + .map(([featureName, featurePrivileges]) => + featurePrivileges.map((privilege) => + PrivilegeSerializer.serializeFeaturePrivilege(featureName, privilege) + ) + ) + .flat() + : []), + ], + application, + resources: [GLOBAL_RESOURCE], + }; + } + + return { + privileges: [ + ...(base + ? base.map((privilege) => PrivilegeSerializer.serializeSpaceBasePrivilege(privilege)) + : []), + ...(feature + ? Object.entries(feature) + .map(([featureName, featurePrivileges]) => + featurePrivileges.map((privilege) => + PrivilegeSerializer.serializeFeaturePrivilege(featureName, privilege) + ) + ) + .flat() + : []), + ], + application, + resources: (spaces as string[]).map((resource) => + ResourceSerializer.serializeSpaceResource(resource) + ), + }; + }); +}; + +export const validateKibanaPrivileges = ( + kibanaFeatures: KibanaFeature[], + kibanaPrivileges: KibanaPrivilegesType = [] +) => { + const validationErrors = kibanaPrivileges.flatMap((priv) => { + const forAllSpaces = priv.spaces.includes(ALL_SPACES_ID); + + return Object.entries(priv.feature ?? {}).flatMap(([featureId, feature]) => { + const errors: string[] = []; + const kibanaFeature = kibanaFeatures.find((f) => f.id === featureId); + if (!kibanaFeature) return errors; + + if (feature.includes('all')) { + if (kibanaFeature.privileges?.all.disabled) { + errors.push(`Feature [${featureId}] does not support privilege [all].`); + } + + if (kibanaFeature.privileges?.all.requireAllSpaces && !forAllSpaces) { + errors.push( + `Feature privilege [${featureId}.all] requires all spaces to be selected but received [${priv.spaces.join( + ',' + )}]` + ); + } + } + + if (feature.includes('read')) { + if (kibanaFeature.privileges?.read.disabled) { + errors.push(`Feature [${featureId}] does not support privilege [read].`); + } + + if (kibanaFeature.privileges?.read.requireAllSpaces && !forAllSpaces) { + errors.push( + `Feature privilege [${featureId}.read] requires all spaces to be selected but received [${priv.spaces.join( + ',' + )}]` + ); + } + } + + return errors; + }); + }); + + return { validationErrors }; +}; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 6e3b67b3eedba..07b3e1ea232ec 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -366,9 +366,15 @@ export class SecurityPlugin http: core.http, loggers: this.initializerContext.logger, session, + applicationName: this.authorizationSetup!.applicationName, + kibanaFeatures: features.getKibanaFeatures(), }); - this.authorizationService.start({ features, clusterClient, online$: watchOnlineStatus$() }); + this.authorizationService.start({ + features, + clusterClient, + online$: watchOnlineStatus$(), + }); this.anonymousAccessStart = this.anonymousAccessService.start({ capabilities: core.capabilities, diff --git a/x-pack/plugins/security/server/routes/api_keys/create.test.ts b/x-pack/plugins/security/server/routes/api_keys/create.test.ts index dbf4c93639e47..22e4bb3df96d5 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.test.ts @@ -42,6 +42,7 @@ describe('Create API Key route', () => { }); describe('failure', () => { + test.todo('actually exercise different types of payload validation'); test('returns result of license checker', async () => { const mockContext = getMockContext({ state: 'invalid', message: 'test forbidden message' }); const response = await routeHandler( diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts index b9e927592a492..8bb097ee01259 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.ts @@ -8,29 +8,53 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '..'; +import { CreateApiKeyValidationError } from '../../authentication/api_keys'; import { wrapIntoCustomErrorResponse } from '../../errors'; +import { elasticsearchRoleSchema, getKibanaRoleSchema } from '../../lib'; import { createLicensedRouteHandler } from '../licensed_route_handler'; +const bodySchema = schema.object({ + name: schema.string(), + expiration: schema.maybe(schema.string()), + role_descriptors: schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' }), { + defaultValue: {}, + }), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), +}); + +const getBodySchemaWithKibanaPrivileges = ( + getBasePrivilegeNames: () => { global: string[]; space: string[] } +) => + schema.object({ + name: schema.string(), + expiration: schema.maybe(schema.string()), + kibana_role_descriptors: schema.recordOf( + schema.string(), + schema.object({ + elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), + kibana: getKibanaRoleSchema(getBasePrivilegeNames), + }) + ), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), + }); + export function defineCreateApiKeyRoutes({ router, + authz, getAuthenticationService, }: RouteDefinitionParams) { + const bodySchemaWithKibanaPrivileges = getBodySchemaWithKibanaPrivileges(() => { + const privileges = authz.privileges.get(); + return { + global: Object.keys(privileges.global), + space: Object.keys(privileges.space), + }; + }); router.post( { path: '/internal/security/api_key', validate: { - body: schema.object({ - name: schema.string(), - expiration: schema.maybe(schema.string()), - role_descriptors: schema.recordOf( - schema.string(), - schema.object({}, { unknowns: 'allow' }), - { - defaultValue: {}, - } - ), - metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), - }), + body: schema.oneOf([bodySchema, bodySchemaWithKibanaPrivileges]), }, }, createLicensedRouteHandler(async (context, request, response) => { @@ -43,6 +67,9 @@ export function defineCreateApiKeyRoutes({ return response.ok({ body: apiKey }); } catch (error) { + if (error instanceof CreateApiKeyValidationError) { + return response.badRequest({ body: { message: error.message } }); + } return response.customError(wrapIntoCustomErrorResponse(error)); } }) diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index 428cd9b49dac4..8a8b688fd9bb5 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -8,9 +8,9 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '../..'; +import { transformElasticsearchRoleToRole } from '../../../authorization'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import { transformElasticsearchRoleToRole } from './model'; export function defineGetRolesRoutes({ router, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index 757903c3e3dbe..c6407e3784763 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -6,9 +6,9 @@ */ import type { RouteDefinitionParams } from '../..'; +import { transformElasticsearchRoleToRole } from '../../../authorization'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import { transformElasticsearchRoleToRole } from './model'; export function defineGetAllRolesRoutes({ router, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts index ef27f20f09a55..f42d6f01573a9 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts @@ -5,10 +5,5 @@ * 2.0. */ -export type { ElasticsearchRole } from '../../../../authorization'; -export { transformElasticsearchRoleToRole } from '../../../../authorization'; -export { - getPutPayloadSchema, - transformPutPayloadToElasticsearchRole, - validateKibanaPrivileges, -} from './put_payload'; +export type { RolePayloadSchemaType } from './put_payload'; +export { transformPutPayloadToElasticsearchRole, getPutPayloadSchema } from './put_payload'; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts index 7600c99a1caf7..842a3b74853b7 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts @@ -8,7 +8,8 @@ import { KibanaFeature } from '@kbn/features-plugin/common'; import { ALL_SPACES_ID } from '../../../../../common/constants'; -import { getPutPayloadSchema, validateKibanaPrivileges } from './put_payload'; +import { validateKibanaPrivileges } from '../../../../lib'; +import { getPutPayloadSchema } from './put_payload'; const basePrivilegeNamesMap = { global: ['all', 'read'], diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts index 62e4a499e2e1c..19ce403b77d86 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts @@ -5,233 +5,18 @@ * 2.0. */ -import _ from 'lodash'; - import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import type { KibanaFeature } from '@kbn/features-plugin/common'; - -import type { ElasticsearchRole } from '.'; -import { ALL_SPACES_ID, GLOBAL_RESOURCE } from '../../../../../common/constants'; -import { PrivilegeSerializer } from '../../../../authorization/privilege_serializer'; -import { ResourceSerializer } from '../../../../authorization/resource_serializer'; - -/** - * Elasticsearch specific portion of the role definition. - * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. - */ -const elasticsearchRoleSchema = schema.object({ - /** - * An optional list of cluster privileges. These privileges define the cluster level actions that - * users with this role are able to execute - */ - cluster: schema.maybe(schema.arrayOf(schema.string())), - - /** - * An optional list of indices permissions entries. - */ - indices: schema.maybe( - schema.arrayOf( - schema.object({ - /** - * Required list of indices (or index name patterns) to which the permissions in this - * entry apply. - */ - names: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional set of the document fields that the owners of the role have read access to. - */ - field_security: schema.maybe( - schema.recordOf( - schema.oneOf([schema.literal('grant'), schema.literal('except')]), - schema.arrayOf(schema.string()) - ) - ), - - /** - * Required list of the index level privileges that the owners of the role have on the - * specified indices. - */ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional search query that defines the documents the owners of the role have read access - * to. A document within the specified indices must match this query in order for it to be - * accessible by the owners of the role. - */ - query: schema.maybe(schema.string()), - - /** - * An optional flag used to indicate if index pattern wildcards or regexps should cover - * restricted indices. - */ - allow_restricted_indices: schema.maybe(schema.boolean()), - }) - ) - ), - - /** - * An optional list of users that the owners of this role can impersonate. - */ - run_as: schema.maybe(schema.arrayOf(schema.string())), -}); - -const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { - minSize: 1, - maxSize: 1, -}); - -/** - * Schema for the list of space IDs used within Kibana specific role definition. - */ -const spacesSchema = schema.oneOf( - [ - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - if (!/^[a-z0-9_-]+$/.test(value)) { - return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ), - ], - { defaultValue: [GLOBAL_RESOURCE] } -); - -const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; - -type PutPayloadSchemaType = TypeOf>; -export function getPutPayloadSchema( - getBasePrivilegeNames: () => { global: string[]; space: string[] } -) { - return schema.object({ - /** - * An optional meta-data dictionary. Within the metadata, keys that begin with _ are reserved - * for system usage. - */ - metadata: schema.maybe(schema.recordOf(schema.string(), schema.any())), - - /** - * Elasticsearch specific portion of the role definition. - */ - elasticsearch: elasticsearchRoleSchema, - - /** - * Kibana specific portion of the role definition. It's represented as a list of base and/or - * feature Kibana privileges. None of the entries should apply to the same spaces. - */ - kibana: schema.maybe( - schema.arrayOf( - schema.object( - { - /** - * An optional list of space IDs to which the permissions in this entry apply. If not - * specified it defaults to special "global" space ID (all spaces). - */ - spaces: spacesSchema, - /** - * An optional list of Kibana base privileges. If this entry applies to special "global" - * space (all spaces) then specified base privileges should be within known base "global" - * privilege list, otherwise - within known "space" privilege list. Base privileges - * definition isn't allowed when feature privileges are defined and required otherwise. - */ - base: schema.maybe( - schema.conditional( - schema.siblingRef('spaces'), - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - const globalPrivileges = getBasePrivilegeNames().global; - if (!globalPrivileges.some((privilege) => privilege === value)) { - return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; - } - }, - }) - ), - schema.arrayOf( - schema.string({ - validate(value) { - const spacePrivileges = getBasePrivilegeNames().space; - if (!spacePrivileges.some((privilege) => privilege === value)) { - return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; - } - }, - }) - ) - ) - ), - - /** - * An optional dictionary of Kibana feature privileges where the key is the ID of the - * feature and the value is a list of feature specific privilege IDs. Both feature and - * privilege IDs should consist of allowed set of characters. Feature privileges - * definition isn't allowed when base privileges are defined and required otherwise. - */ - feature: schema.maybe( - schema.recordOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }), - schema.arrayOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ) - ) - ), - }, - { - validate(value) { - if ( - (value.base === undefined || value.base.length === 0) && - (value.feature === undefined || Object.values(value.feature).flat().length === 0) - ) { - return 'either [base] or [feature] is expected, but none of them specified'; - } - - if ( - value.base !== undefined && - value.base.length > 0 && - value.feature !== undefined && - Object.keys(value.feature).length > 0 - ) { - return `definition of [feature] isn't allowed when non-empty [base] is defined.`; - } - }, - } - ), - { - validate(value) { - for (const [indexA, valueA] of value.entries()) { - for (const valueB of value.slice(indexA + 1)) { - const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); - if (spaceIntersection.length !== 0) { - return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; - } - } - } - }, - } - ) - ), - }); -} +import type { ElasticsearchRole } from '../../../../authorization'; +import { + elasticsearchRoleSchema, + getKibanaRoleSchema, + transformPrivilegesToElasticsearchPrivileges, +} from '../../../../lib'; export const transformPutPayloadToElasticsearchRole = ( - rolePayload: PutPayloadSchemaType, + rolePayload: RolePayloadSchemaType, application: string, allExistingApplications: ElasticsearchRole['applications'] = [] ) => { @@ -255,98 +40,26 @@ export const transformPutPayloadToElasticsearchRole = ( } as Omit; }; -const transformPrivilegesToElasticsearchPrivileges = ( - application: string, - kibanaPrivileges: PutPayloadSchemaType['kibana'] = [] -) => { - return kibanaPrivileges.map(({ base, feature, spaces }) => { - if (spaces.length === 1 && spaces[0] === GLOBAL_RESOURCE) { - return { - privileges: [ - ...(base - ? base.map((privilege) => PrivilegeSerializer.serializeGlobalBasePrivilege(privilege)) - : []), - ...(feature - ? Object.entries(feature) - .map(([featureName, featurePrivileges]) => - featurePrivileges.map((privilege) => - PrivilegeSerializer.serializeFeaturePrivilege(featureName, privilege) - ) - ) - .flat() - : []), - ], - application, - resources: [GLOBAL_RESOURCE], - }; - } - - return { - privileges: [ - ...(base - ? base.map((privilege) => PrivilegeSerializer.serializeSpaceBasePrivilege(privilege)) - : []), - ...(feature - ? Object.entries(feature) - .map(([featureName, featurePrivileges]) => - featurePrivileges.map((privilege) => - PrivilegeSerializer.serializeFeaturePrivilege(featureName, privilege) - ) - ) - .flat() - : []), - ], - application, - resources: (spaces as string[]).map((resource) => - ResourceSerializer.serializeSpaceResource(resource) - ), - }; - }); -}; - -export const validateKibanaPrivileges = ( - kibanaFeatures: KibanaFeature[], - kibanaPrivileges: PutPayloadSchemaType['kibana'] -) => { - const validationErrors = (kibanaPrivileges ?? []).flatMap((priv) => { - const forAllSpaces = priv.spaces.includes(ALL_SPACES_ID); - - return Object.entries(priv.feature ?? {}).flatMap(([featureId, feature]) => { - const errors: string[] = []; - const kibanaFeature = kibanaFeatures.find((f) => f.id === featureId); - if (!kibanaFeature) return errors; - - if (feature.includes('all')) { - if (kibanaFeature.privileges?.all.disabled) { - errors.push(`Feature [${featureId}] does not support privilege [all].`); - } - - if (kibanaFeature.privileges?.all.requireAllSpaces && !forAllSpaces) { - errors.push( - `Feature privilege [${featureId}.all] requires all spaces to be selected but received [${priv.spaces.join( - ',' - )}]` - ); - } - } - - if (feature.includes('read')) { - if (kibanaFeature.privileges?.read.disabled) { - errors.push(`Feature [${featureId}] does not support privilege [read].`); - } +export function getPutPayloadSchema( + getBasePrivilegeNames: () => { global: string[]; space: string[] } +) { + return schema.object({ + /** + * An optional meta-data dictionary. Within the metadata, keys that begin with _ are reserved + * for system usage. + */ + metadata: schema.maybe(schema.recordOf(schema.string(), schema.any())), - if (kibanaFeature.privileges?.read.requireAllSpaces && !forAllSpaces) { - errors.push( - `Feature privilege [${featureId}.read] requires all spaces to be selected but received [${priv.spaces.join( - ',' - )}]` - ); - } - } + /** + * Elasticsearch specific portion of the role definition. + */ + elasticsearch: elasticsearchRoleSchema, - return errors; - }); + /** + * Kibana specific portion of the role definition. + */ + kibana: schema.maybe(getKibanaRoleSchema(getBasePrivilegeNames)), }); +} - return { validationErrors }; -}; +export type RolePayloadSchemaType = TypeOf>; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 03fbdf30dc767..a6c9ae8a15fd9 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -5,23 +5,17 @@ * 2.0. */ -import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; import type { KibanaFeature } from '@kbn/features-plugin/common'; import type { RouteDefinitionParams } from '../..'; import { wrapIntoCustomErrorResponse } from '../../../errors'; +import { validateKibanaPrivileges } from '../../../lib'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import { - getPutPayloadSchema, - transformPutPayloadToElasticsearchRole, - validateKibanaPrivileges, -} from './model'; +import type { RolePayloadSchemaType } from './model'; +import { getPutPayloadSchema, transformPutPayloadToElasticsearchRole } from './model'; -const roleGrantsSubFeaturePrivileges = ( - features: KibanaFeature[], - role: TypeOf> -) => { +const roleGrantsSubFeaturePrivileges = (features: KibanaFeature[], role: RolePayloadSchemaType) => { if (!role.kibana) { return false; } diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts index 3395f56359a2f..e39950699cb4a 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts @@ -5,7 +5,7 @@ * 2.0. */ import { schema } from '@kbn/config-schema'; -import { SavedObject } from '@kbn/core/server'; +import { SavedObject, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { ConfigKey, MonitorFields, @@ -35,14 +35,28 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ return response.badRequest({ body: { message, attributes: { details, ...payload } } }); } - const newMonitor: SavedObject = - await savedObjectsClient.create( + let newMonitor: SavedObject | null = null; + + try { + newMonitor = await savedObjectsClient.create( syntheticsMonitorType, formatSecrets({ ...monitor, revision: 1, }) ); + } catch (getErr) { + if (SavedObjectsErrorHelpers.isForbiddenError(getErr)) { + return response.forbidden({ body: getErr }); + } + } + + if (!newMonitor) { + return response.customError({ + body: { message: 'Unable to create monitor' }, + statusCode: 500, + }); + } const { syntheticsService } = server; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts index 68ebda333e6e6..d072cbaff2bcd 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts @@ -22,7 +22,7 @@ import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; export const serviceApiKeyPrivileges = { cluster: ['monitor', 'read_ilm', 'read_pipeline'] as SecurityClusterPrivilege[], - index: [ + indices: [ { names: ['synthetics-*'], privileges: [ @@ -32,6 +32,7 @@ export const serviceApiKeyPrivileges = { ] as SecurityIndexPrivilege[], }, ], + run_as: [], }; export const getAPIKeyForSyntheticsService = async ({ @@ -137,7 +138,7 @@ export const getSyntheticsEnablement = async ({ 'manage_own_api_key', ...serviceApiKeyPrivileges.cluster, ], - index: serviceApiKeyPrivileges.index, + index: serviceApiKeyPrivileges.indices, }, }), security.authc.apiKeys.areAPIKeysEnabled(), diff --git a/x-pack/test/api_integration/apis/security/api_keys.ts b/x-pack/test/api_integration/apis/security/api_keys.ts index 98ef83c437863..28d9be63c2db0 100644 --- a/x-pack/test/api_integration/apis/security/api_keys.ts +++ b/x-pack/test/api_integration/apis/security/api_keys.ts @@ -6,6 +6,8 @@ */ import expect from '@kbn/expect'; +import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; +import { serviceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -64,5 +66,38 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + + describe('with kibana privileges', () => { + describe('POST /internal/security/api_key', () => { + it('should allow an API Key to be created', async () => { + await supertest + .post('/internal/security/api_key') + .set('kbn-xsrf', 'xxx') + .send({ + name: 'test_api_key', + expiration: '12d', + kibana_role_descriptors: { + uptime_save: { + elasticsearch: serviceApiKeyPrivileges, + kibana: [ + { + base: [], + spaces: [ALL_SPACES_ID], + feature: { + uptime: ['all'], + }, + }, + ], + }, + }, + }) + .expect(200) + .then((response: Record) => { + const { name } = response.body; + expect(name).to.eql('test_api_key'); + }); + }); + }); + }); }); } diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts index 14fce8f974e92..ed90086b1bb0a 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts @@ -9,12 +9,16 @@ import expect from '@kbn/expect'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; import { HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; +import { format as formatUrl } from 'url'; +import supertest from 'supertest'; +import { serviceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { getFixtureJson } from './helper/get_fixture_json'; export default function ({ getService }: FtrProviderContext) { describe('[POST] /internal/uptime/service/monitors', () => { - const supertest = getService('supertest'); + const supertestAPI = getService('supertest'); let _httpMonitorJson: HTTPFields; let httpMonitorJson: HTTPFields; @@ -30,7 +34,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns the newly added monitor', async () => { const newMonitor = httpMonitorJson; - const apiResponse = await supertest + const apiResponse = await supertestAPI .post(API_URLS.SYNTHETICS_MONITORS) .set('kbn-xsrf', 'true') .send(newMonitor); @@ -42,7 +46,7 @@ export default function ({ getService }: FtrProviderContext) { // Delete a required property to make payload invalid const newMonitor = { ...httpMonitorJson, 'check.request.headers': undefined }; - const apiResponse = await supertest + const apiResponse = await supertestAPI .post(API_URLS.SYNTHETICS_MONITORS) .set('kbn-xsrf', 'true') .send(newMonitor); @@ -53,7 +57,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns bad request if monitor type is invalid', async () => { const newMonitor = { ...httpMonitorJson, type: 'invalid-data-steam' }; - const apiResponse = await supertest + const apiResponse = await supertestAPI .post(API_URLS.SYNTHETICS_MONITORS) .set('kbn-xsrf', 'true') .send(newMonitor); @@ -61,5 +65,94 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.status).eql(400); expect(apiResponse.body.message).eql('Monitor type is invalid'); }); + + it('can create monitor with API key with proper permissions', async () => { + await supertestAPI + .post('/internal/security/api_key') + .set('kbn-xsrf', 'xxx') + .send({ + name: 'test_api_key', + expiration: '12d', + kibana_role_descriptors: { + uptime_save: { + elasticsearch: serviceApiKeyPrivileges, + kibana: [ + { + base: [], + spaces: [ALL_SPACES_ID], + feature: { + uptime: ['all'], + }, + }, + ], + }, + }, + }) + .expect(200) + .then(async (response: Record) => { + const { name, encoded: apiKey } = response.body; + expect(name).to.eql('test_api_key'); + + const config = getService('config'); + + const { hostname, protocol, port } = config.get('servers.kibana'); + const kibanaServerUrl = formatUrl({ hostname, protocol, port }); + const supertestNoAuth = supertest(kibanaServerUrl); + + const apiResponse = await supertestNoAuth + .post(API_URLS.SYNTHETICS_MONITORS) + .auth(name, apiKey) + .set('kbn-xsrf', 'true') + .set('Authorization', `ApiKey ${apiKey}`) + .send(httpMonitorJson); + + expect(apiResponse.status).eql(200); + }); + }); + + it('can not create monitor with API key without proper permissions', async () => { + await supertestAPI + .post('/internal/security/api_key') + .set('kbn-xsrf', 'xxx') + .send({ + name: 'test_api_key', + expiration: '12d', + kibana_role_descriptors: { + uptime_save: { + elasticsearch: serviceApiKeyPrivileges, + kibana: [ + { + base: [], + spaces: [ALL_SPACES_ID], + feature: { + uptime: ['read'], + }, + }, + ], + }, + }, + }) + .expect(200) + .then(async (response: Record) => { + const { name, encoded: apiKey } = response.body; + expect(name).to.eql('test_api_key'); + + const config = getService('config'); + + const { hostname, protocol, port } = config.get('servers.kibana'); + const kibanaServerUrl = formatUrl({ hostname, protocol, port }); + const supertestNoAuth = supertest(kibanaServerUrl); + + const apiResponse = await supertestNoAuth + .post(API_URLS.SYNTHETICS_MONITORS) + .auth(name, apiKey) + .set('kbn-xsrf', 'true') + .set('Authorization', `ApiKey ${apiKey}`) + .send(httpMonitorJson); + + expect(apiResponse.status).eql(403); + expect(apiResponse.body.message).eql('Unable to create synthetics-monitor'); + }); + }); }); } diff --git a/x-pack/test/api_integration/apis/uptime/rest/synthetics_enablement.ts b/x-pack/test/api_integration/apis/uptime/rest/synthetics_enablement.ts index c5e2c1d339ad5..9ac40532538e7 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/synthetics_enablement.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/synthetics_enablement.ts @@ -39,7 +39,7 @@ export default function ({ getService }: FtrProviderContext) { ], elasticsearch: { cluster: [privilege, ...serviceApiKeyPrivileges.cluster], - indices: serviceApiKeyPrivileges.index, + indices: serviceApiKeyPrivileges.indices, }, }); @@ -125,7 +125,7 @@ export default function ({ getService }: FtrProviderContext) { ], elasticsearch: { cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster], - indices: serviceApiKeyPrivileges.index, + indices: serviceApiKeyPrivileges.indices, }, }); @@ -224,7 +224,7 @@ export default function ({ getService }: FtrProviderContext) { ], elasticsearch: { cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster], - indices: serviceApiKeyPrivileges.index, + indices: serviceApiKeyPrivileges.indices, }, }); @@ -332,7 +332,7 @@ export default function ({ getService }: FtrProviderContext) { ], elasticsearch: { cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster], - indices: serviceApiKeyPrivileges.index, + indices: serviceApiKeyPrivileges.indices, }, }); From a487d7c99484f23eb55cd5b7716fc2a550e39d0d Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Mon, 23 May 2022 16:51:04 +0200 Subject: [PATCH 14/71] [APM] Add an internal endpoint for debugging telemetry (#132511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [APM] Add telemetry to service groups quries * Add service groups in telemetry schema * Add an internal route to test apm telemetry * Update endpoint to run telemetry jobs and display data * Update telemetry README * Move service_groups task work to another PR * Clean up * Use versioned link in x-pack/plugins/apm/dev_docs/telemetry.md Co-authored-by: Søren Louv-Jansen * Update x-pack/plugins/apm/server/routes/debug_telemetry/route.ts Co-authored-by: Søren Louv-Jansen * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: Søren Louv-Jansen Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/apm/dev_docs/telemetry.md | 22 +-- .../generate_sample_documents.ts | 126 -------------- .../scripts/upload_telemetry_data/index.ts | 156 ------------------ .../collect_data_telemetry/index.ts | 7 +- .../collect_data_telemetry/tasks.ts | 1 - .../apm/server/lib/apm_telemetry/index.ts | 4 +- .../get_global_apm_server_route_repository.ts | 3 +- .../server/routes/debug_telemetry/route.ts | 35 ++++ 8 files changed, 54 insertions(+), 300 deletions(-) delete mode 100644 x-pack/plugins/apm/scripts/upload_telemetry_data/generate_sample_documents.ts delete mode 100644 x-pack/plugins/apm/scripts/upload_telemetry_data/index.ts create mode 100644 x-pack/plugins/apm/server/routes/debug_telemetry/route.ts diff --git a/x-pack/plugins/apm/dev_docs/telemetry.md b/x-pack/plugins/apm/dev_docs/telemetry.md index 27b9e57447467..0085f64cdec18 100644 --- a/x-pack/plugins/apm/dev_docs/telemetry.md +++ b/x-pack/plugins/apm/dev_docs/telemetry.md @@ -19,7 +19,7 @@ to the telemetry cluster using the During the APM server-side plugin's setup phase a [Saved Object](https://www.elastic.co/guide/en/kibana/master/managing-saved-objects.html) for APM telemetry is registered and a -[task manager](../../task_manager/server/README.md) task is registered and started. +[task manager](../../task_manager/README.md) task is registered and started. The task periodically queries the APM indices and saves the results in the Saved Object, and the usage collector periodically gets the data from the saved object and uploads it to the telemetry cluster. @@ -27,23 +27,19 @@ and uploads it to the telemetry cluster. Once uploaded to the telemetry cluster, the data telemetry is stored in `stack_stats.kibana.plugins.apm` in the xpack-phone-home index. -### Generating sample data +### Collect a new telemetry field -The script in `scripts/upload_telemetry_data` can generate sample telemetry data and upload it to a cluster of your choosing. +In order to collect a new telemetry field you need to add a task which performs the query that collects the data from the cluster. -You'll need to set the `GITHUB_TOKEN` environment variable to a token that has `repo` scope so it can read from the -[elastic/telemetry](https://github.com/elastic/telemetry) repository. (You probably have a token that works for this in -~/.backport/config.json.) +All the available tasks are [here](https://github.com/elastic/kibana/blob/ba84602455671f0f6175bbc0fd2e8f302c60bbe6/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts) -The script will run as the `elastic` user using the elasticsearch hosts and password settings from the config/kibana.yml -and/or config/kibana.dev.yml files. +### Debug telemetry -Running the script with `--clear` will delete the index first. +The following endpoint will run the `apm-telemetry-task` which is responsible for collecting the telemetry data and once it's completed it will return the telemetry attributes. -If you're using an Elasticsearch instance without TLS verification (if you have `elasticsearch.ssl.verificationMode: none` set in your kibana.yml) -you can run the script with `env NODE_TLS_REJECT_UNAUTHORIZED=0` to avoid TLS connection errors. - -After running the script you should see sample telemetry data in the "xpack-phone-home" index. +``` +GET /internal/apm/debug-telemetry +``` ### Updating Data Telemetry Mappings diff --git a/x-pack/plugins/apm/scripts/upload_telemetry_data/generate_sample_documents.ts b/x-pack/plugins/apm/scripts/upload_telemetry_data/generate_sample_documents.ts deleted file mode 100644 index fe4fe9de5d22a..0000000000000 --- a/x-pack/plugins/apm/scripts/upload_telemetry_data/generate_sample_documents.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DeepPartial } from 'utility-types'; -import { - merge, - omit, - defaultsDeep, - range, - mapValues, - isPlainObject, - flatten, -} from 'lodash'; -import uuid from 'uuid'; -import { - CollectTelemetryParams, - collectDataTelemetry, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../server/lib/apm_telemetry/collect_data_telemetry'; - -interface GenerateOptions { - days: number; - instances: number; - variation: { - min: number; - max: number; - }; -} - -const randomize = ( - value: unknown, - instanceVariation: number, - dailyGrowth: number -) => { - if (typeof value === 'boolean') { - return Math.random() > 0.5; - } - if (typeof value === 'number') { - return Math.round(instanceVariation * dailyGrowth * value); - } - return value; -}; - -const mapValuesDeep = ( - obj: Record, - iterator: (value: unknown, key: string, obj: Record) => unknown -): Record => - mapValues(obj, (val, key) => - isPlainObject(val) ? mapValuesDeep(val, iterator) : iterator(val, key!, obj) - ); - -export async function generateSampleDocuments( - options: DeepPartial & { - collectTelemetryParams: CollectTelemetryParams; - } -) { - const { collectTelemetryParams, ...preferredOptions } = options; - - const opts: GenerateOptions = defaultsDeep( - { - days: 100, - instances: 50, - variation: { - min: 0.1, - max: 4, - }, - }, - preferredOptions - ); - - const sample = await collectDataTelemetry(collectTelemetryParams); - - console.log('Collected telemetry'); // eslint-disable-line no-console - console.log('\n' + JSON.stringify(sample, null, 2)); // eslint-disable-line no-console - - const dateOfScriptExecution = new Date(); - - return flatten( - range(0, opts.instances).map(() => { - const instanceId = uuid.v4(); - const defaults = { - cluster_uuid: instanceId, - stack_stats: { - kibana: { - versions: { - version: '8.0.0', - }, - }, - }, - }; - - const instanceVariation = - Math.random() * (opts.variation.max - opts.variation.min) + - opts.variation.min; - - return range(0, opts.days).map((dayNo) => { - const dailyGrowth = Math.pow(1.005, opts.days - 1 - dayNo); - - const timestamp = Date.UTC( - dateOfScriptExecution.getFullYear(), - dateOfScriptExecution.getMonth(), - -dayNo - ); - - const generated = mapValuesDeep(omit(sample, 'versions'), (value) => - randomize(value, instanceVariation, dailyGrowth) - ); - - return merge({}, defaults, { - timestamp, - stack_stats: { - kibana: { - plugins: { - apm: merge({}, sample, generated), - }, - }, - }, - }); - }); - }) - ); -} diff --git a/x-pack/plugins/apm/scripts/upload_telemetry_data/index.ts b/x-pack/plugins/apm/scripts/upload_telemetry_data/index.ts deleted file mode 100644 index 0f8a6f874d72f..0000000000000 --- a/x-pack/plugins/apm/scripts/upload_telemetry_data/index.ts +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// This script downloads the telemetry mapping, runs the APM telemetry tasks, -// generates a bunch of randomized data based on the downloaded sample, -// and uploads it to a cluster of your choosing in the same format as it is -// stored in the telemetry cluster. Its purpose is twofold: -// - Easier testing of the telemetry tasks -// - Validate whether we can run the queries we want to on the telemetry data - -import { merge, chunk, flatten, omit } from 'lodash'; -import { argv } from 'yargs'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Logger } from '@kbn/core/server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { CollectTelemetryParams } from '../../server/lib/apm_telemetry/collect_data_telemetry'; -import { downloadTelemetryTemplate } from '../shared/download_telemetry_template'; -import { mergeApmTelemetryMapping } from '../../common/apm_telemetry'; -import { generateSampleDocuments } from './generate_sample_documents'; -import { readKibanaConfig } from '../shared/read_kibana_config'; -import { getHttpAuth } from '../shared/get_http_auth'; -import { createOrUpdateIndex } from '../shared/create_or_update_index'; -import { getEsClient } from '../shared/get_es_client'; - -async function uploadData() { - const githubToken = process.env.GITHUB_TOKEN; - - if (!githubToken) { - throw new Error('GITHUB_TOKEN was not provided.'); - } - - const xpackTelemetryIndexName = 'xpack-phone-home'; - const telemetryTemplate = await downloadTelemetryTemplate({ - githubToken, - }); - - const config = readKibanaConfig(); - - const httpAuth = getHttpAuth(config); - - const client = getEsClient({ - node: config['elasticsearch.hosts'], - ...(httpAuth - ? { - auth: { ...httpAuth, username: 'elastic' }, - } - : {}), - }); - - // The new template is the template downloaded from the telemetry repo, with - // our current telemetry mapping merged in, with the "index_patterns" key - // (which cannot be used when creating an index) removed. - const newTemplate = omit( - mergeApmTelemetryMapping( - merge(telemetryTemplate, { - index_patterns: undefined, - settings: { - index: { mapping: { total_fields: { limit: 10000 } } }, - }, - }) - ), - 'index_patterns' - ); - - await createOrUpdateIndex({ - indexName: xpackTelemetryIndexName, - client, - template: newTemplate, - clear: !!argv.clear, - }); - - const sampleDocuments = await generateSampleDocuments({ - collectTelemetryParams: { - logger: console as unknown as Logger, - indices: { - transaction: config['xpack.apm.indices.transaction'], - metric: config['xpack.apm.indices.metric'], - error: config['xpack.apm.indices.error'], - span: config['xpack.apm.indices.span'], - onboarding: config['xpack.apm.indices.onboarding'], - sourcemap: config['xpack.apm.indices.sourcemap'], - apmCustomLinkIndex: '.apm-custom-links', - apmAgentConfigurationIndex: '.apm-agent-configuration', - }, - search: (body) => { - return client.search(body) as Promise; - }, - indicesStats: (body) => { - return client.indices.stats(body); - }, - transportRequest: ((params) => { - return; - }) as CollectTelemetryParams['transportRequest'], - }, - }); - - const chunks = chunk(sampleDocuments, 250); - - await chunks.reduce>((prev, documents) => { - return prev.then(async () => { - const body = flatten( - documents.map((doc) => [ - { index: { _index: xpackTelemetryIndexName } }, - doc, - ]) - ); - - return client - .bulk({ - body, - refresh: 'wait_for', - }) - .then((response: any) => { - if (response.errors) { - const firstError = response.items.filter( - (item: any) => item.index.status >= 400 - )[0].index.error; - throw new Error( - `Failed to upload documents: ${firstError.reason} ` - ); - } - }); - }); - }, Promise.resolve()); -} - -uploadData() - .catch((e) => { - if ('response' in e) { - if (typeof e.response === 'string') { - // eslint-disable-next-line no-console - console.log(e.response); - } else { - // eslint-disable-next-line no-console - console.log( - JSON.stringify( - e.response, - ['status', 'statusText', 'headers', 'data'], - 2 - ) - ); - } - } else { - // eslint-disable-next-line no-console - console.log(e); - } - process.exit(1); - }) - .then(() => { - // eslint-disable-next-line no-console - console.log('Finished uploading generated telemetry data'); - }); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts index 2a196c3e04638..c870ddc070f59 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts @@ -6,7 +6,7 @@ */ import { merge } from 'lodash'; -import { Logger } from '@kbn/core/server'; +import { Logger, SavedObjectsClient } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ESSearchRequest, @@ -16,6 +16,8 @@ import { ApmIndicesConfig } from '../../../routes/settings/apm_indices/get_apm_i import { tasks } from './tasks'; import { APMDataTelemetry } from '../types'; +type ISavedObjectsClient = Pick; + type TelemetryTaskExecutor = (params: { indices: ApmIndicesConfig; search( @@ -37,6 +39,7 @@ type TelemetryTaskExecutor = (params: { path: string; method: 'get'; }) => Promise; + savedObjectsClient: ISavedObjectsClient; }) => Promise; export interface TelemetryTask { @@ -54,6 +57,7 @@ export function collectDataTelemetry({ logger, indicesStats, transportRequest, + savedObjectsClient, }: CollectTelemetryParams) { return tasks.reduce((prev, task) => { return prev.then(async (data) => { @@ -65,6 +69,7 @@ export function collectDataTelemetry({ indices, indicesStats, transportRequest, + savedObjectsClient, }); const took = process.hrtime(time); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index f06226c864a98..54157296da270 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -44,7 +44,6 @@ import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { Span } from '../../../../typings/es_schemas/ui/span'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { APMTelemetry } from '../types'; - const TIME_RANGES = ['1d', 'all'] as const; type TimeRange = typeof TIME_RANGES[number]; diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts index d8e1638ad4a57..c45ed100402b9 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { Observable, firstValueFrom } from 'rxjs'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { CoreSetup, Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; @@ -27,7 +26,7 @@ import { import { APMUsage } from './types'; import { apmSchema } from './schema'; -const APM_TELEMETRY_TASK_NAME = 'apm-telemetry-task'; +export const APM_TELEMETRY_TASK_NAME = 'apm-telemetry-task'; export async function createApmTelemetry({ core, @@ -93,6 +92,7 @@ export async function createApmTelemetry({ logger, indicesStats, transportRequest, + savedObjectsClient, }); await savedObjectsClient.create( diff --git a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts index 7224a58fda982..521e5887091ac 100644 --- a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts @@ -38,7 +38,7 @@ import { eventMetadataRouteRepository } from '../event_metadata/route'; import { suggestionsRouteRepository } from '../suggestions/route'; import { agentKeysRouteRepository } from '../agent_keys/route'; import { spanLinksRouteRepository } from '../span_links/route'; - +import { debugTelemetryRoute } from '../debug_telemetry/route'; function getTypedGlobalApmServerRouteRepository() { const repository = { ...dataViewRouteRepository, @@ -69,6 +69,7 @@ function getTypedGlobalApmServerRouteRepository() { ...eventMetadataRouteRepository, ...agentKeysRouteRepository, ...spanLinksRouteRepository, + ...debugTelemetryRoute, }; return repository; diff --git a/x-pack/plugins/apm/server/routes/debug_telemetry/route.ts b/x-pack/plugins/apm/server/routes/debug_telemetry/route.ts new file mode 100644 index 0000000000000..b3c8a9347708e --- /dev/null +++ b/x-pack/plugins/apm/server/routes/debug_telemetry/route.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { APM_TELEMETRY_TASK_NAME } from '../../lib/apm_telemetry'; +import { APMTelemetry } from '../../lib/apm_telemetry/types'; +import { + APM_TELEMETRY_SAVED_OBJECT_ID, + APM_TELEMETRY_SAVED_OBJECT_TYPE, +} from '../../../common/apm_saved_object_constants'; +export const debugTelemetryRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/debug-telemetry', + options: { + tags: ['access:apm', 'access:apm_write'], + }, + handler: async (resources): Promise => { + const { plugins, context } = resources; + const coreContext = await context.core; + const taskManagerStart = await plugins.taskManager?.start(); + const savedObjectsClient = coreContext.savedObjects.client; + + await taskManagerStart?.runNow?.(APM_TELEMETRY_TASK_NAME); + + const apmTelemetryObject = await savedObjectsClient.get( + APM_TELEMETRY_SAVED_OBJECT_TYPE, + APM_TELEMETRY_SAVED_OBJECT_ID + ); + + return apmTelemetryObject.attributes; + }, +}); From dedbeecc0654771b264b34df1fcdb86abc17bb6f Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 23 May 2022 11:46:54 -0400 Subject: [PATCH 15/71] [Fleet] Allow to schedule upgrade (#132653) * [Fleet] Allow to schedule upgrade for agents * Add scheduled upgrade in current upgrade callout * Fix i18n * Fix tests * Fix start_time validation * Make selected datetime format more user friendly * Address code review comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: criamico --- .../fleet/common/types/models/agent.ts | 1 + .../fleet/common/types/rest_spec/agent.ts | 1 + .../components/bulk_actions.tsx | 24 ++++++-- .../current_bulk_upgrade_callout.tsx | 54 ++++++++++++---- .../components/agent_upgrade_modal/index.tsx | 61 ++++++++++++++++--- .../server/routes/agent/upgrade_handler.ts | 2 + .../fleet/server/services/agents/upgrade.ts | 13 +++- .../fleet/server/types/rest_spec/agent.ts | 10 +++ .../apis/agents/current_upgrades.ts | 5 ++ 9 files changed, 144 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index a26f63eba755b..fee6f4c2ae4c4 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -99,6 +99,7 @@ export interface CurrentUpgrade { nbAgents: number; nbAgentsAck: number; version: string; + startTime: string; } // Generated from FleetServer schema.json diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 886730d38f831..77416f5e1db5d 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -90,6 +90,7 @@ export interface PostBulkAgentUpgradeRequest { source_uri?: string; version: string; rollout_duration_seconds?: number; + start_time?: string; }; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index e27c647e25f70..7df96dad06a88 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -55,7 +55,7 @@ export const AgentBulkActions: React.FunctionComponent = ({ // Actions states const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); - const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); + const [updateModalState, setUpgradeModalState] = useState({ isOpen: false, isScheduled: false }); // Check if user is working with only inactive agents const atLeastOneActiveAgentSelected = @@ -109,7 +109,22 @@ export const AgentBulkActions: React.FunctionComponent = ({ disabled: !atLeastOneActiveAgentSelected, onClick: () => { closeMenu(); - setIsUpgradeModalOpen(true); + setUpgradeModalState({ isOpen: true, isScheduled: false }); + }, + }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setUpgradeModalState({ isOpen: true, isScheduled: true }); }, }, ], @@ -145,13 +160,14 @@ export const AgentBulkActions: React.FunctionComponent = ({ /> )} - {isUpgradeModalOpen && ( + {updateModalState.isOpen && ( { - setIsUpgradeModalOpen(false); + setUpgradeModalState({ isOpen: false, isScheduled: false }); refreshAgents(); }} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/current_bulk_upgrade_callout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/current_bulk_upgrade_callout.tsx index a77c26f8fef2f..a4931cbd6f362 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/current_bulk_upgrade_callout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/current_bulk_upgrade_callout.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useCallback, useState, useMemo } from 'react'; +import { FormattedMessage, FormattedDate, FormattedTime } from '@kbn/i18n-react'; import { EuiCallOut, EuiLink, @@ -14,6 +14,7 @@ import { EuiFlexItem, EuiButton, EuiLoadingSpinner, + EuiIcon, } from '@elastic/eui'; import { useStartServices } from '../../../../hooks'; @@ -39,6 +40,44 @@ export const CurrentBulkUpgradeCallout: React.FunctionComponent { + const now = Date.now(); + const startDate = new Date(currentUpgrade.startTime).getTime(); + + return startDate > now; + }, [currentUpgrade]); + + const calloutTitle = isScheduled ? ( + + +   + + + ), + }} + /> + ) : ( + + ); return (
- + {isScheduled ? : }    - + {calloutTitle}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx index 2122abb5e2785..6708c12cf8b97 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; +import moment from 'moment'; import { EuiConfirmModal, EuiComboBox, @@ -17,6 +18,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiCallOut, + EuiDatePicker, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -36,6 +38,7 @@ interface Props { onClose: () => void; agents: Agent[] | string; agentCount: number; + isScheduled?: boolean; } const getVersion = (version: Array>) => version[0].value as string; @@ -44,6 +47,7 @@ export const AgentUpgradeAgentModal: React.FunctionComponent = ({ onClose, agents, agentCount, + isScheduled = false, }) => { const { notifications } = useStartServices(); const kibanaVersion = useKibanaVersion(); @@ -61,7 +65,8 @@ export const AgentUpgradeAgentModal: React.FunctionComponent = ({ value: option, }) ); - const maintainanceWindows = isSmallBatch ? [0].concat(MAINTAINANCE_VALUES) : MAINTAINANCE_VALUES; + const maintainanceWindows = + isSmallBatch && !isScheduled ? [0].concat(MAINTAINANCE_VALUES) : MAINTAINANCE_VALUES; const maintainanceOptions: Array> = maintainanceWindows.map( (option) => ({ label: @@ -81,14 +86,18 @@ export const AgentUpgradeAgentModal: React.FunctionComponent = ({ maintainanceOptions[0], ]); + const initialDatetime = useMemo(() => moment(), []); + const [startDatetime, setStartDatetime] = useState(initialDatetime); + async function onSubmit() { const version = getVersion(selectedVersion); - const rolloutOptions = - selectedMantainanceWindow.length > 0 && (selectedMantainanceWindow[0]?.value as number) > 0 - ? { - rollout_duration_seconds: selectedMantainanceWindow[0].value, - } - : {}; + const rolloutOptions = { + rollout_duration_seconds: + selectedMantainanceWindow.length > 0 && (selectedMantainanceWindow[0]?.value as number) > 0 + ? selectedMantainanceWindow[0].value + : undefined, + start_time: startDatetime.toISOString(), + }; try { setIsSubmitting(true); @@ -177,12 +186,18 @@ export const AgentUpgradeAgentModal: React.FunctionComponent = ({ {isSingleAgent ? ( + ) : isScheduled ? ( + ) : ( )} @@ -203,6 +218,11 @@ export const AgentUpgradeAgentModal: React.FunctionComponent = ({ id="xpack.fleet.upgradeAgents.confirmSingleButtonLabel" defaultMessage="Upgrade agent" /> + ) : isScheduled ? ( + ) : ( = ({ }} /> + {isScheduled && ( + <> + + + setStartDatetime(date as moment.Moment)} + /> + + + )} {!isSingleAgent ? ( ((acc, so) => { diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index d7f2735e2d284..87007b9ce880a 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -84,6 +84,7 @@ export async function sendUpgradeAgentsActions( sourceUri?: string | undefined; force?: boolean; upgradeDurationSeconds?: number; + startTime?: string; } ) { // Full set of agents @@ -166,9 +167,11 @@ export async function sendUpgradeAgentsActions( const rollingUpgradeOptions = options?.upgradeDurationSeconds ? { - start_time: now, + start_time: options.startTime ?? now, minimum_execution_duration: MINIMUM_EXECUTION_DURATION_SECONDS, - expiration: moment().add(options?.upgradeDurationSeconds, 'seconds').toISOString(), + expiration: moment(options.startTime ?? now) + .add(options?.upgradeDurationSeconds, 'seconds') + .toISOString(), } : {}; @@ -311,6 +314,11 @@ async function _getUpgradeActions(esClient: ElasticsearchClient, now = new Date( field: 'agents', }, }, + { + exists: { + field: 'start_time', + }, + }, { range: { expiration: { gte: now }, @@ -334,6 +342,7 @@ async function _getUpgradeActions(esClient: ElasticsearchClient, now = new Date( complete: false, nbAgentsAck: 0, version: hit._source.data?.version as string, + startTime: hit._source.start_time as string, }; } diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index e080fe66f7e2c..7c1078ba8dbd8 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -6,6 +6,7 @@ */ import { schema } from '@kbn/config-schema'; +import moment from 'moment'; import { NewAgentActionSchema } from '../models'; @@ -78,6 +79,15 @@ export const PostBulkAgentUpgradeRequestSchema = { version: schema.string(), force: schema.maybe(schema.boolean()), rollout_duration_seconds: schema.maybe(schema.number({ min: 600 })), + start_time: schema.maybe( + schema.string({ + validate: (v: string) => { + if (!moment(v).isValid()) { + return 'not a valid date'; + } + }, + }) + ), }), }; diff --git a/x-pack/test/fleet_api_integration/apis/agents/current_upgrades.ts b/x-pack/test/fleet_api_integration/apis/agents/current_upgrades.ts index f1a5666875e5a..8d060666cd1ee 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/current_upgrades.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/current_upgrades.ts @@ -45,6 +45,7 @@ export default function (providerContext: FtrProviderContext) { type: 'UPGRADE', action_id: 'action1', agents: ['agent1', 'agent2', 'agent3'], + start_time: moment().toISOString(), expiration: moment().add(1, 'day').toISOString(), }, }); @@ -57,6 +58,7 @@ export default function (providerContext: FtrProviderContext) { type: 'UPGRADE', action_id: 'action2', agents: ['agent1', 'agent2', 'agent3'], + start_time: moment().toISOString(), expiration: moment().add(1, 'day').toISOString(), }, }); @@ -68,6 +70,7 @@ export default function (providerContext: FtrProviderContext) { type: 'UPGRADE', action_id: 'action2', agents: ['agent4', 'agent5'], + start_time: moment().toISOString(), expiration: moment().add(1, 'day').toISOString(), }, }); @@ -79,6 +82,7 @@ export default function (providerContext: FtrProviderContext) { type: 'UPGRADE', action_id: 'action3', agents: ['agent1', 'agent2'], + start_time: moment().toISOString(), expiration: moment().add(1, 'day').toISOString(), }, }); @@ -129,6 +133,7 @@ export default function (providerContext: FtrProviderContext) { type: 'UPGRADE', action_id: 'action5', agents: ['agent1', 'agent2', 'agent3'], + start_time: moment().toISOString(), expiration: moment().add(1, 'day').toISOString(), }, }); From 3c648df094741caa09b5c3715a4193cec5f595e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Mon, 23 May 2022 17:58:51 +0200 Subject: [PATCH 16/71] [Unified Observability] Add Page load distribution chart to overview page (#132258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add serviceName to breakdown select * add exploratory view to ux section * fix chart height * fix types * Use datepicker values for page load distribution * use translations * memoize exploratory embeddable * fix tests * fix types * remove memoization * Update chart height Co-authored-by: Casper Hübertz * remove actions * minor improvements * Undo AppDataType enum Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Casper Hübertz --- .../public/application/index.tsx | 2 + .../components/app/section/apm/index.test.tsx | 4 ++ .../components/app/section/ux/index.tsx | 43 +++++++++++++++++++ .../rum/data_distribution_config.ts | 8 +++- .../public/context/plugin_context.tsx | 5 ++- .../pages/overview/overview.stories.tsx | 3 ++ .../public/pages/rules/index.test.tsx | 4 ++ .../public/utils/test_helper.tsx | 11 +++++ 8 files changed, 78 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index c48a663fefe5b..ff23f5c103d2d 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -92,6 +92,8 @@ export const renderApp = ({ value={{ appMountParameters, config, + core, + plugins, observabilityRuleTypeRegistry, ObservabilityPageTemplate, kibanaFeatures, diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index 179e8ef70deb1..138c0008df400 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -8,6 +8,8 @@ import React from 'react'; import * as fetcherHook from '../../../../hooks/use_fetcher'; import { render, data as dataMock } from '../../../../utils/test_helper'; +import { CoreStart } from '@kbn/core/public'; +import { ObservabilityPublicPluginsStart } from '../../../../plugin'; import { APMSection } from '.'; import { response } from './mock_data/apm.mock'; import * as hasDataHook from '../../../../hooks/use_has_data'; @@ -51,6 +53,8 @@ describe('APMSection', () => { rules: { enabled: true }, }, }, + core: {} as CoreStart, + plugins: {} as ObservabilityPublicPluginsStart, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: KibanaPageTemplate, kibanaFeatures: [], diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx index 430285a0941cf..da5bffd6cb186 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx @@ -7,13 +7,21 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; +import type { AppDataType } from '../../../shared/exploratory_view/types'; import { SectionContainer } from '..'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; import { useDatePickerContext } from '../../../../hooks/use_date_picker_context'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; import CoreVitals from '../../../shared/core_web_vitals'; import { BucketSize } from '../../../../pages/overview'; +import { getExploratoryViewEmbeddable } from '../../../shared/exploratory_view/embeddable'; +import { AllSeries } from '../../../shared/exploratory_view/hooks/use_series_storage'; +import { + SERVICE_NAME, + TRANSACTION_DURATION, +} from '../../../shared/exploratory_view/configurations/constants/elasticsearch_fieldnames'; interface Props { bucketSize: BucketSize; @@ -21,11 +29,30 @@ interface Props { export function UXSection({ bucketSize }: Props) { const { forceUpdate, hasDataMap } = useHasData(); + const { core, plugins } = usePluginContext(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } = useDatePickerContext(); const uxHasDataResponse = hasDataMap.ux; const serviceName = uxHasDataResponse?.serviceName as string; + const ExploratoryViewEmbeddable = getExploratoryViewEmbeddable(core, plugins); + + const seriesList: AllSeries = [ + { + name: PAGE_LOAD_DISTRIBUTION_TITLE, + time: { + from: relativeStart, + to: relativeEnd, + }, + reportDefinitions: { + [SERVICE_NAME]: ['ALL_VALUES'], + }, + breakdown: SERVICE_NAME, + dataType: 'ux' as AppDataType, + selectedMetricField: TRANSACTION_DURATION, + }, + ]; + const { data, status } = useFetcher( () => { if (serviceName && bucketSize && absoluteStart && absoluteEnd) { @@ -72,6 +99,15 @@ export function UXSection({ bucketSize }: Props) { }} hasError={status === FETCH_STATUS.FAILURE} > +
+ +
+ ); } + +const PAGE_LOAD_DISTRIBUTION_TITLE = i18n.translate( + 'xpack.observability.overview.ux.pageLoadDistribution.title', + { + defaultMessage: 'Page load distribution', + } +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts index 97b6d2ddf7199..ff2939213bbc1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts @@ -74,7 +74,13 @@ export function getRumDistributionConfig({ dataView }: ConfigProps): SeriesConfi }, LABEL_FIELDS_FILTER, ], - breakdownFields: [USER_AGENT_NAME, USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, USER_AGENT_DEVICE], + breakdownFields: [ + USER_AGENT_NAME, + USER_AGENT_OS, + CLIENT_GEO_COUNTRY_NAME, + USER_AGENT_DEVICE, + SERVICE_NAME, + ], definitionFields: [SERVICE_NAME, SERVICE_ENVIRONMENT], metricOptions: [ { label: PAGE_LOAD_TIME_LABEL, id: TRANSACTION_DURATION, field: TRANSACTION_DURATION }, diff --git a/x-pack/plugins/observability/public/context/plugin_context.tsx b/x-pack/plugins/observability/public/context/plugin_context.tsx index 37dffe4daa178..d77edb7d9de73 100644 --- a/x-pack/plugins/observability/public/context/plugin_context.tsx +++ b/x-pack/plugins/observability/public/context/plugin_context.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import { AppMountParameters } from '@kbn/core/public'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { createContext } from 'react'; import { KibanaFeature } from '@kbn/features-plugin/common'; +import { ObservabilityPublicPluginsStart } from '../plugin'; import { ConfigSchema } from '..'; import { ObservabilityRuleTypeRegistry } from '../rules/create_observability_rule_type_registry'; import type { LazyObservabilityPageTemplateProps } from '../components/shared/page_template/lazy_page_template'; @@ -15,6 +16,8 @@ import type { LazyObservabilityPageTemplateProps } from '../components/shared/pa export interface PluginContextValue { appMountParameters: AppMountParameters; config: ConfigSchema; + core: CoreStart; + plugins: ObservabilityPublicPluginsStart; observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; ObservabilityPageTemplate: React.ComponentType; kibanaFeatures: KibanaFeature[]; diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 097d0d0845dca..d775e0102a5d7 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -14,6 +14,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/public'; import { createKibanaReactContext, KibanaPageTemplate } from '@kbn/kibana-react-plugin/public'; import { HasDataContextProvider } from '../../context/has_data_context'; import { PluginContext } from '../../context/plugin_context'; +import { ObservabilityPublicPluginsStart } from '../../plugin'; import { registerDataHandler, unregisterDataHandler } from '../../data_handler'; import { OverviewPage } from '.'; import { alertsFetchData } from './mock/alerts.mock'; @@ -88,6 +89,8 @@ const withCore = makeDecorator({ rules: { enabled: true }, }, }, + core: {} as CoreStart, + plugins: {} as ObservabilityPublicPluginsStart, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: KibanaPageTemplate, kibanaFeatures: [], diff --git a/x-pack/plugins/observability/public/pages/rules/index.test.tsx b/x-pack/plugins/observability/public/pages/rules/index.test.tsx index 6987026b3b9bd..e932154526155 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.test.tsx @@ -9,6 +9,8 @@ import React from 'react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; +import { CoreStart } from '@kbn/core/public'; +import { ObservabilityPublicPluginsStart } from '../../plugin'; import { RulesPage } from '.'; import { RulesTable } from './components/rules_table'; import { kibanaStartMock } from '../../utils/kibana_react.mock'; @@ -52,6 +54,8 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: KibanaPageTemplate, kibanaFeatures: [], + core: {} as CoreStart, + plugins: {} as ObservabilityPublicPluginsStart, })); const { useFetchRules } = jest.requireMock('../../hooks/use_fetch_rules'); diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx index bdbb9dd71164a..c4071070b73ce 100644 --- a/x-pack/plugins/observability/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -7,6 +7,7 @@ import { render as testLibRender } from '@testing-library/react'; import { AppMountParameters } from '@kbn/core/public'; + import { coreMock } from '@kbn/core/public/mocks'; import React from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; @@ -14,6 +15,7 @@ import { KibanaContextProvider, KibanaPageTemplate } from '@kbn/kibana-react-plu import translations from '@kbn/translations-plugin/translations/ja-JP.json'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { ObservabilityPublicPluginsStart } from '../plugin'; import { PluginContext } from '../context/plugin_context'; import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock'; @@ -21,6 +23,13 @@ const appMountParameters = { setHeaderActionMenu: () => {} } as unknown as AppMo export const core = coreMock.createStart(); export const data = dataPluginMock.createStartContract(); +const dataViewsMock = () => { + return {}; +}; + +const plugins = { + dataViews: dataViewsMock, +} as unknown as ObservabilityPublicPluginsStart; const config = { unsafe: { @@ -40,6 +49,8 @@ export const render = (component: React.ReactNode) => { value={{ appMountParameters, config, + core, + plugins, observabilityRuleTypeRegistry, ObservabilityPageTemplate: KibanaPageTemplate, kibanaFeatures: [], From a6370f3bd5438776e90ead157ea34374dc083aa1 Mon Sep 17 00:00:00 2001 From: Desmond Davis <99669693+dejadavi-el@users.noreply.github.com> Date: Mon, 23 May 2022 11:46:10 -0500 Subject: [PATCH 17/71] Update Prebuilt Telemetry Alert Filterlist (#132643) * first commit * Update x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts Updating nested fields to top level keys Co-authored-by: Pete Hampton Co-authored-by: Pete Hampton --- .../lib/telemetry/filterlists/prebuilt_rules_alerts.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts index fa0547fbf0125..15d4c8a608fd9 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts @@ -54,6 +54,12 @@ export const prebuiltRuleAllowlistFields: AllowlistFields = { 'kibana.alert.workflow_status': true, 'kibana.space_ids': true, 'kibana.version': true, + job_id: true, + causes: true, + typical: true, + multi_bucket_impact: true, + partition_field_name: true, + partition_field_value: true, // Alert specific filter entries agent: { id: true, From 23a345e78ddf94a4f05df32a1509ccbec153ced8 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 23 May 2022 18:52:58 +0200 Subject: [PATCH 18/71] [Synthetics] Getting started page (#132013) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 1 + .../common/constants/data_test_subjects.ts | 10 ++ .../plugins/synthetics/common/constants/ui.ts | 1 + .../plugins/synthetics/e2e/journeys/index.ts | 1 + .../e2e/journeys/monitor_name.journey.ts | 7 - .../synthetics/getting_started.journey.ts | 52 +++++++ .../e2e/journeys/synthetics/index.ts | 8 ++ .../e2e/page_objects/synthetics_app.tsx | 87 ++++++++++++ .../common/pages/synthetics_page_template.tsx | 13 +- .../form_fields/service_locations.tsx | 68 +++++++++ .../getting_started/getting_started_page.tsx | 97 +++++++++++++ .../simple_monitor_form.test.tsx | 84 +++++++++++ .../getting_started/simple_monitor_form.tsx | 134 ++++++++++++++++++ .../getting_started/use_simple_monitor.ts | 62 ++++++++ .../monitor_management_page.tsx | 21 ++- .../monitor_management/show_sync_errors.tsx | 12 +- .../monitor_management/use_breadcrumbs.ts | 9 +- .../components/overview/overview_page.tsx | 19 ++- .../public/apps/synthetics/hooks/index.ts | 2 - .../synthetics/hooks/use_no_data_config.ts | 47 ------ .../apps/synthetics/hooks/use_telemetry.ts | 46 ------ .../public/apps/synthetics/routes.tsx | 47 +++--- .../public/apps/synthetics/state/index.ts | 2 +- .../state/index_status/selectors.ts | 4 +- .../state/monitor_management/api.ts | 57 ++++++++ .../state/monitor_management/effects.ts | 34 +++++ .../state/monitor_management/monitor_list.ts | 37 +++++ .../state/monitor_management/selectors.ts | 12 ++ .../monitor_management/service_locations.ts | 45 ++++++ .../apps/synthetics/state/root_effect.ts | 7 +- .../apps/synthetics/state/root_reducer.ts | 6 +- .../apps/synthetics/state/ui/selectors.ts | 4 +- .../apps/synthetics/state/utils/actions.ts | 19 +++ .../synthetics/state/utils/fetch_effect.ts | 8 +- .../public/apps/synthetics/synthetics_app.tsx | 25 ++-- .../__mocks__/syncthetics_store.mock.ts | 31 +++- .../public/hooks/use_form_wrapped.tsx | 32 +++++ .../action_bar/action_bar.tsx | 4 +- .../legacy_uptime/hooks/use_telemetry.ts | 2 +- x-pack/plugins/synthetics/public/plugin.ts | 2 +- .../routes/monitor_cruds/add_monitor.ts | 6 +- yarn.lock | 5 + 42 files changed, 987 insertions(+), 183 deletions(-) create mode 100644 x-pack/plugins/synthetics/common/constants/data_test_subjects.ts create mode 100644 x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts create mode 100644 x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts create mode 100644 x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts rename x-pack/plugins/synthetics/public/{legacy_uptime => apps/synthetics}/components/monitor_management/show_sync_errors.tsx (90%) delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_no_data_config.ts delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_telemetry.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/effects.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/monitor_list.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/selectors.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/service_locations.ts create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts create mode 100644 x-pack/plugins/synthetics/public/hooks/use_form_wrapped.tsx diff --git a/package.json b/package.json index e5fffb5b3a394..7cc2500e8dd6e 100644 --- a/package.json +++ b/package.json @@ -367,6 +367,7 @@ "react-dropzone": "^4.2.9", "react-fast-compare": "^2.0.4", "react-grid-layout": "^0.16.2", + "react-hook-form": "^7.30.0", "react-intl": "^2.8.0", "react-is": "^16.13.1", "react-markdown": "^4.3.1", diff --git a/x-pack/plugins/synthetics/common/constants/data_test_subjects.ts b/x-pack/plugins/synthetics/common/constants/data_test_subjects.ts new file mode 100644 index 0000000000000..f7e124c5a340e --- /dev/null +++ b/x-pack/plugins/synthetics/common/constants/data_test_subjects.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const syntheticsTestSubjects = { + urlsInput: 'urls-input', +}; diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts index a736e3296161e..994cc20536723 100644 --- a/x-pack/plugins/synthetics/common/constants/ui.ts +++ b/x-pack/plugins/synthetics/common/constants/ui.ts @@ -14,6 +14,7 @@ export const MONITOR_EDIT_ROUTE = '/edit-monitor/:monitorId'; export const MONITOR_MANAGEMENT_ROUTE = '/manage-monitors'; export const OVERVIEW_ROUTE = '/'; +export const GETTING_STARTED_ROUTE = '/manage-monitors/getting-started'; export const SETTINGS_ROUTE = '/settings'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/index.ts b/x-pack/plugins/synthetics/e2e/journeys/index.ts index 1fe02882fcd89..6f1d733e10537 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +export * from './synthetics'; export * from './data_view_permissions'; export * from './uptime.journey'; export * from './step_duration.journey'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts index a9dd2c4633402..4957df75aba13 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts @@ -1,10 +1,3 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts new file mode 100644 index 0000000000000..acef9e96e7f2d --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { journey, step, expect, before, Page } from '@elastic/synthetics'; +import { syntheticsAppPageProvider } from '../../page_objects/synthetics_app'; +import { byTestId } from '../utils'; + +journey(`Getting Started Page`, async ({ page, params }: { page: Page; params: any }) => { + const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); + + const createBasicMonitor = async () => { + await syntheticsApp.fillFirstMonitorDetails({ + url: 'https://www.elastic.co', + locations: ['us_central'], + apmServiceName: 'synthetics', + }); + }; + + before(async () => { + await syntheticsApp.waitForLoadingToFinish(); + }); + + step('Go to monitor-management', async () => { + await syntheticsApp.navigateToMonitorManagement(); + }); + + step('login to Kibana', async () => { + await syntheticsApp.loginToKibana(); + const invalid = await page.locator(`text=Username or password is incorrect. Please try again.`); + expect(await invalid.isVisible()).toBeFalsy(); + }); + + step('shows validation error on touch', async () => { + await page.click(byTestId('urls-input')); + await page.click(byTestId('comboBoxInput')); + expect(await page.isVisible('text=URL is required')).toBeTruthy(); + }); + + step('create basic monitor', async () => { + await createBasicMonitor(); + await syntheticsApp.confirmAndSave(); + }); + + step('it navigates to details page after saving', async () => { + await page.click('text=Dismiss'); + expect(await page.isVisible('text=My first monitor')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts new file mode 100644 index 0000000000000..1783ced950ca1 --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './getting_started.journey'; diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx new file mode 100644 index 0000000000000..1444e4282012f --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Page } from '@elastic/synthetics'; +import { loginPageProvider } from './login'; +import { utilsPageProvider } from './utils'; + +export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kibanaUrl: string }) { + const remoteKibanaUrl = process.env.SYNTHETICS_REMOTE_KIBANA_URL; + const remoteUsername = process.env.SYNTHETICS_REMOTE_KIBANA_USERNAME; + const remotePassword = process.env.SYNTHETICS_REMOTE_KIBANA_PASSWORD; + const isRemote = Boolean(process.env.SYNTHETICS_REMOTE_ENABLED); + const basePath = isRemote ? remoteKibanaUrl : kibanaUrl; + const monitorManagement = `${basePath}/app/synthetics/manage-monitors`; + const addMonitor = `${basePath}/app/uptime/add-monitor`; + return { + ...loginPageProvider({ + page, + isRemote, + username: isRemote ? remoteUsername : 'elastic', + password: isRemote ? remotePassword : 'changeme', + }), + ...utilsPageProvider({ page }), + + async navigateToMonitorManagement() { + await page.goto(monitorManagement, { + waitUntil: 'networkidle', + }); + await this.waitForMonitorManagementLoadingToFinish(); + }, + + async waitForMonitorManagementLoadingToFinish() { + while (true) { + if ((await page.$(this.byTestId('uptimeLoader'))) === null) break; + await page.waitForTimeout(5 * 1000); + } + }, + + async getAddMonitorButton() { + return await this.findByTestSubj('syntheticsAddMonitorBtn'); + }, + + async navigateToAddMonitor() { + await page.goto(addMonitor, { + waitUntil: 'networkidle', + }); + }, + + async ensureIsOnMonitorConfigPage() { + await page.isVisible('[data-test-subj=monitorSettingsSection]'); + }, + + async confirmAndSave(isEditPage?: boolean) { + await this.ensureIsOnMonitorConfigPage(); + if (isEditPage) { + await page.click('text=Update monitor'); + } else { + await page.click('text=Create monitor'); + } + return await this.findByText('Monitor added successfully.'); + }, + + async selectLocations({ locations }: { locations: string[] }) { + for (let i = 0; i < locations.length; i++) { + await page.click(this.byTestId(`syntheticsServiceLocation--${locations[i]}`)); + } + }, + + async fillFirstMonitorDetails({ + url, + apmServiceName, + locations, + }: { + url: string; + apmServiceName: string; + locations: string[]; + }) { + await this.fillByTestSubj('urls-input', url); + await page.click(this.byTestId('comboBoxInput')); + await this.selectLocations({ locations }); + await page.click(this.byTestId('urls-input')); + }, + }; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx index 44b38236fc2a2..50497c4c9214c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/synthetics_page_template.tsx @@ -11,7 +11,6 @@ import { EuiPageHeaderProps, EuiPageTemplateProps } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; import { ClientPluginsStart } from '../../../../../plugin'; -import { useNoDataConfig } from '../../../hooks/use_no_data_config'; import { EmptyStateLoading } from '../../overview/empty_state/empty_state_loading'; import { EmptyStateError } from '../../overview/empty_state/empty_state_error'; import { useHasData } from '../../overview/empty_state/use_has_data'; @@ -51,8 +50,6 @@ export const SyntheticsPageTemplateComponent: React.FC {showLoading && } -
- {children} -
+
{children}
); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx new file mode 100644 index 0000000000000..252b650cc7058 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/form_fields/service_locations.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiComboBox, EuiFormRow } from '@elastic/eui'; +import { Controller, FieldErrors, Control } from 'react-hook-form'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { useSelector } from 'react-redux'; +import { serviceLocationsSelector } from '../../../state/monitor_management/selectors'; +import { SimpleFormData } from '../simple_monitor_form'; +import { ConfigKey } from '../../../../../../common/constants/monitor_management'; + +export const ServiceLocationsField = ({ + errors, + control, +}: { + errors: FieldErrors; + control: Control; +}) => { + const locations = useSelector(serviceLocationsSelector); + + return ( + + ( + ({ + ...location, + 'data-test-subj': `syntheticsServiceLocation--${location.id}`, + }))} + selectedOptions={field.value} + isClearable={true} + data-test-subj="syntheticsServiceLocations" + {...field} + isInvalid={!!errors?.[ConfigKey.LOCATIONS]} + /> + )} + /> + + ); +}; + +const SELECT_ONE_OR_MORE_LOCATIONS = i18n.translate( + 'xpack.synthetics.monitorManagement.selectOneOrMoreLocations', + { + defaultMessage: 'Select one or more locations', + } +); + +const LOCATIONS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.locationsLabel', { + defaultMessage: 'Locations', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx new file mode 100644 index 0000000000000..767a48f22dae9 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/getting_started_page.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { EuiEmptyPrompt, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { useDispatch } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { useBreadcrumbs } from '../../hooks'; +import { fetchServiceLocationsAction } from '../../state/monitor_management/service_locations'; +import { SimpleMonitorForm } from './simple_monitor_form'; +import { MONITORING_OVERVIEW_LABEL } from '../../../../legacy_uptime/routes'; + +export const GettingStartedPage = () => { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchServiceLocationsAction.get()); + }, [dispatch]); + + useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview + + return ( + + {CREATE_SINGLE_PAGE_LABEL}} + layout="horizontal" + color="plain" + body={ + <> + + {OR_LABEL}{' '} + {SELECT_DIFFERENT_MONITOR} + {i18n.translate('xpack.synthetics.gettingStarted.createSingle.description', { + defaultMessage: ' to get started with Elastic Synthetics Monitoring', + })} + + + + + } + footer={ + <> + + {FOR_MORE_INFO_LABEL} + {' '} + + {GETTING_STARTED_LABEL} + + + } + /> + + ); +}; + +const Wrapper = styled.div` + &&& { + .euiEmptyPrompt__content { + max-width: 40em; + padding: 0; + } + } +`; + +const FOR_MORE_INFO_LABEL = i18n.translate('xpack.synthetics.gettingStarted.forMoreInfo', { + defaultMessage: 'For more information, read our', +}); + +const CREATE_SINGLE_PAGE_LABEL = i18n.translate( + 'xpack.synthetics.gettingStarted.createSinglePageLabel', + { + defaultMessage: 'Create a single page browser monitor', + } +); + +const GETTING_STARTED_LABEL = i18n.translate( + 'xpack.synthetics.gettingStarted.gettingStartedLabel', + { + defaultMessage: 'Getting Started Guide', + } +); + +const SELECT_DIFFERENT_MONITOR = i18n.translate( + 'xpack.synthetics.gettingStarted.gettingStartedLabel.selectDifferentMonitor', + { + defaultMessage: 'select a different monitor type', + } +); + +const OR_LABEL = i18n.translate('xpack.synthetics.gettingStarted.orLabel', { + defaultMessage: 'Or', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx new file mode 100644 index 0000000000000..0ab9834f190bd --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + CREATE_MONITOR_LABEL, + SimpleMonitorForm, + URL_REQUIRED_LABEL, + WEBSITE_URL_HELP_TEXT, + WEBSITE_URL_LABEL, +} from './simple_monitor_form'; +import { screen } from '@testing-library/react'; +import { render } from '../../utils/testing'; +import React from 'react'; +import { act, fireEvent, waitFor } from '@testing-library/react'; +import { syntheticsTestSubjects } from '../../../../../common/constants/data_test_subjects'; +import { apiService } from '../../../../utils/api_service'; + +describe('SimpleMonitorForm', () => { + const apiSpy = jest.spyOn(apiService, 'post'); + it('renders', async () => { + render(); + expect(screen.getByText(WEBSITE_URL_LABEL)).toBeInTheDocument(); + expect(screen.getByText(WEBSITE_URL_HELP_TEXT)).toBeInTheDocument(); + }); + + it('do not show validation error on touch', async () => { + render(); + await act(async () => { + fireEvent.click(screen.getByTestId(syntheticsTestSubjects.urlsInput)); + }); + + await act(async () => { + fireEvent.click(screen.getByTestId('comboBoxInput')); + }); + + expect(await screen.queryByText(URL_REQUIRED_LABEL)).not.toBeInTheDocument(); + }); + + it('shows the validation errors on submit', async () => { + render(); + + await act(async () => { + fireEvent.click(screen.getByTestId(syntheticsTestSubjects.urlsInput)); + }); + + await act(async () => { + fireEvent.click(screen.getByText(CREATE_MONITOR_LABEL)); + }); + + expect(screen.getByText('Please address the highlighted errors.')).toBeInTheDocument(); + }); + + it('submits valid monitor', async () => { + render(); + + await act(async () => { + fireEvent.input(screen.getByTestId(syntheticsTestSubjects.urlsInput), { + target: { value: 'https://www.elastic.co' }, + }); + }); + + await act(async () => { + fireEvent.click(screen.getByTestId('comboBoxInput')); + }); + + expect(screen.getByText('US Central')).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(screen.getByTestId(`syntheticsServiceLocation--us_central`)); + }); + + await act(async () => { + fireEvent.click(screen.getByText(CREATE_MONITOR_LABEL)); + }); + + await waitFor(async () => { + expect(apiSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx new file mode 100644 index 0000000000000..77dc0e35effb6 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFieldText, + EuiFormRow, + EuiForm, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useSimpleMonitor } from './use_simple_monitor'; +import { ServiceLocationsField } from './form_fields/service_locations'; +import { ConfigKey, ServiceLocations } from '../../../../../common/runtime_types'; +import { useFormWrapped } from '../../../../hooks/use_form_wrapped'; + +export interface SimpleFormData { + urls: string; + locations: ServiceLocations; +} + +export const SimpleMonitorForm = () => { + const { + control, + register, + handleSubmit, + formState: { errors, isValid, isSubmitted }, + } = useFormWrapped({ + mode: 'onSubmit', + reValidateMode: 'onChange', + shouldFocusError: true, + defaultValues: { urls: '', locations: [] as ServiceLocations }, + }); + + const [monitorData, setMonitorData] = useState(); + + const onSubmit = (data: SimpleFormData) => { + setMonitorData(data); + }; + + const { loading } = useSimpleMonitor({ monitorData }); + + const hasURLError = !!errors?.[ConfigKey.URLS]; + + return ( + + + + + + + + + + + {CREATE_MONITOR_LABEL} + + + + + ); +}; + +export const MY_FIRST_MONITOR = i18n.translate( + 'xpack.synthetics.monitorManagement.myFirstMonitor', + { + defaultMessage: 'My first monitor', + } +); + +export const WEBSITE_URL_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.websiteUrlLabel', + { + defaultMessage: 'Website URL', + } +); + +export const WEBSITE_URL_PLACEHOLDER = i18n.translate( + 'xpack.synthetics.monitorManagement.websiteUrlPlaceholder', + { + defaultMessage: 'Enter a website URL', + } +); + +export const WEBSITE_URL_HELP_TEXT = i18n.translate( + 'xpack.synthetics.monitorManagement.websiteUrlHelpText', + { + defaultMessage: `For example, your company's homepage or https://elastic.co`, + } +); + +export const CREATE_MONITOR_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.createMonitorLabel', + { defaultMessage: 'Create monitor' } +); + +export const MONITOR_SUCCESS_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.monitorAddedSuccessMessage', + { + defaultMessage: 'Monitor added successfully.', + } +); + +export const URL_REQUIRED_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.urlRequiredLabel', + { + defaultMessage: 'URL is required', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts new file mode 100644 index 0000000000000..81585a9f26a99 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useFetcher } from '@kbn/observability-plugin/public'; +import { useEffect } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useSelector } from 'react-redux'; +import { serviceLocationsSelector } from '../../state/monitor_management/selectors'; +import { showSyncErrors } from '../monitor_management/show_sync_errors'; +import { createMonitorAPI } from '../../state/monitor_management/api'; +import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults'; +import { ConfigKey } from '../../../../../common/constants/monitor_management'; +import { DataStream, SyntheticsMonitorWithId } from '../../../../../common/runtime_types'; +import { MONITOR_SUCCESS_LABEL, MY_FIRST_MONITOR, SimpleFormData } from './simple_monitor_form'; +import { kibanaService } from '../../../../utils/kibana_service'; + +export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData }) => { + const { application } = useKibana().services; + const locationsList = useSelector(serviceLocationsSelector); + + const { data, loading } = useFetcher(() => { + if (!monitorData) { + return new Promise((resolve) => resolve(undefined)); + } + const { urls, locations } = monitorData; + + return createMonitorAPI({ + monitor: { + ...DEFAULT_FIELDS.browser, + 'source.inline.script': `step('Go to ${urls}', async () => { + await page.goto('${urls}'); +});`, + [ConfigKey.MONITOR_TYPE]: DataStream.BROWSER, + [ConfigKey.NAME]: MY_FIRST_MONITOR, + [ConfigKey.LOCATIONS]: locations, + [ConfigKey.URLS]: urls, + }, + }); + }, [monitorData]); + + useEffect(() => { + const newMonitor = data as SyntheticsMonitorWithId; + const hasErrors = data && 'attributes' in data && data.attributes.errors?.length > 0; + if (hasErrors && !loading) { + showSyncErrors(data.attributes.errors, locationsList, kibanaService.toasts); + } + + if (!loading && newMonitor?.id) { + kibanaService.toasts.addSuccess({ + title: MONITOR_SUCCESS_LABEL, + toastLifeTimeMs: 3000, + }); + application?.navigateToApp('uptime', { path: `/monitor/${btoa(newMonitor.id)}` }); + } + }, [application, data, loading, locationsList]); + + return { data, loading }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/monitor_management_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/monitor_management_page.tsx index c4ca665563f0d..7c20fcfe1c143 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/monitor_management_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/monitor_management_page.tsx @@ -5,8 +5,13 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { useTrackPageview } from '@kbn/observability-plugin/public'; +import { useDispatch, useSelector } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import { monitorListSelector } from '../../state/monitor_management/selectors'; +import { fetchMonitorListAction } from '../../state/monitor_management/monitor_list'; +import { GETTING_STARTED_ROUTE } from '../../../../../common/constants'; import { useMonitorManagementBreadcrumbs } from './use_breadcrumbs'; export const MonitorManagementPage: React.FC = () => { @@ -14,9 +19,21 @@ export const MonitorManagementPage: React.FC = () => { useTrackPageview({ app: 'synthetics', path: 'manage-monitors', delay: 15000 }); useMonitorManagementBreadcrumbs(); + const dispatch = useDispatch(); + + const { total } = useSelector(monitorListSelector); + + useEffect(() => { + dispatch(fetchMonitorListAction.get()); + }, [dispatch]); + + if (total === 0) { + return ; + } + return ( <> -

Monitor Management List page (Monitor Management Page)

+

This page is under construction and will be updated in a future release

); }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/show_sync_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/show_sync_errors.tsx similarity index 90% rename from x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/show_sync_errors.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/show_sync_errors.tsx index 0fde06c764c08..2d4412b71f230 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/show_sync_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/show_sync_errors.tsx @@ -8,13 +8,17 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { kibanaService } from '../../state/kibana_service'; -import { ServiceLocationErrors, ServiceLocations } from '../../../../common/runtime_types'; +import { IToasts } from '@kbn/core/public'; +import { ServiceLocationErrors, ServiceLocations } from '../../../../../common/runtime_types'; -export const showSyncErrors = (errors: ServiceLocationErrors, locations: ServiceLocations) => { +export const showSyncErrors = ( + errors: ServiceLocationErrors, + locations: ServiceLocations, + toasts: IToasts +) => { Object.values(errors).forEach((location) => { const { status: responseStatus, reason } = location.error || {}; - kibanaService.toasts.addWarning({ + toasts.addWarning({ title: i18n.translate('xpack.synthetics.monitorManagement.service.error.title', { defaultMessage: `Unable to sync monitor config`, }), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/use_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/use_breadcrumbs.ts index be94df18ef7d2..30d23128d1e82 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/use_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_management/use_breadcrumbs.ts @@ -22,9 +22,6 @@ export const useMonitorManagementBreadcrumbs = () => { ]); }; -const MONITOR_MANAGEMENT_CRUMB = i18n.translate( - 'xpack.synthetics.monitorManagementPage.monitorManagementCrumb', - { - defaultMessage: 'Monitor Management', - } -); +const MONITOR_MANAGEMENT_CRUMB = i18n.translate('xpack.synthetics.monitorsPage.monitorCrumb', { + defaultMessage: 'Monitors', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/overview/overview_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/overview/overview_page.tsx index 4a370fc024423..9e229308a402e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/overview/overview_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/overview/overview_page.tsx @@ -6,8 +6,13 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useTrackPageview } from '@kbn/observability-plugin/public'; +import { useDispatch, useSelector } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import { monitorListSelector } from '../../state/monitor_management/selectors'; +import { GETTING_STARTED_ROUTE } from '../../../../../common/constants'; +import { fetchMonitorListAction } from '../../state/monitor_management/monitor_list'; import { useSyntheticsSettingsContext } from '../../contexts'; import { useOverviewBreadcrumbs } from './use_breadcrumbs'; @@ -17,6 +22,18 @@ export const OverviewPage: React.FC = () => { useOverviewBreadcrumbs(); const { basePath } = useSyntheticsSettingsContext(); + const dispatch = useDispatch(); + + const { total } = useSelector(monitorListSelector); + + useEffect(() => { + dispatch(fetchMonitorListAction.get()); + }, [dispatch]); + + if (total === 0) { + return ; + } + return ( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts index 15079dc68823b..c3cde2eaffec5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/index.ts @@ -7,7 +7,5 @@ export * from './use_url_params'; export * from './use_breadcrumbs'; -export * from './use_telemetry'; export * from '../../../hooks/use_breakpoints'; export * from './use_service_allowed'; -export * from './use_no_data_config'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_no_data_config.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_no_data_config.ts deleted file mode 100644 index 6243d2f5d8c8a..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_no_data_config.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { useContext } from 'react'; -import { useSelector } from 'react-redux'; -import { KibanaPageTemplateProps, useKibana } from '@kbn/kibana-react-plugin/public'; -import { SyntheticsSettingsContext } from '../contexts'; -import { ClientPluginsStart } from '../../../plugin'; -import { selectIndexState } from '../state'; - -export function useNoDataConfig(): KibanaPageTemplateProps['noDataConfig'] { - const { basePath } = useContext(SyntheticsSettingsContext); - - const { - services: { docLinks }, - } = useKibana(); - - const { data } = useSelector(selectIndexState); - - // Returns no data config when there is no historical data - if (data && !data.indexExists) { - return { - solution: i18n.translate('xpack.synthetics.noDataConfig.solutionName', { - defaultMessage: 'Observability', - }), - actions: { - beats: { - title: i18n.translate('xpack.synthetics.noDataConfig.beatsCard.title', { - defaultMessage: 'Add monitors with Heartbeat', - }), - description: i18n.translate('xpack.synthetics.noDataConfig.beatsCard.description', { - defaultMessage: - 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users experience.', - }), - href: basePath + `/app/home#/tutorial/uptimeMonitors`, - }, - }, - docsLink: docLinks!.links.observability.guide, - }; - } -} -// TODO: Change no data config for Synthetics diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_telemetry.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_telemetry.ts deleted file mode 100644 index 64ecabaff5d5a..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_telemetry.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useEffect } from 'react'; -import { useGetUrlParams } from './use_url_params'; -import { apiService } from '../../../utils/api_service'; -// import { API_URLS } from '../../../common/constants'; - -export enum SyntheticsPage { - Overview = 'Overview', - Monitor = 'Monitor', - MonitorAdd = 'AddMonitor', - MonitorEdit = 'EditMonitor', - MonitorManagement = 'MonitorManagement', - Settings = 'Settings', - Certificates = 'Certificates', - StepDetail = 'StepDetail', - SyntheticCheckStepsPage = 'SyntheticCheckStepsPage', - NotFound = '__not-found__', -} - -export const useSyntheticsTelemetry = (page?: SyntheticsPage) => { - const { dateRangeStart, dateRangeEnd, autorefreshInterval, autorefreshIsPaused } = - useGetUrlParams(); - - useEffect(() => { - if (!apiService.http) throw new Error('Core http services are not defined'); - - /* - TODO: Add/Modify telemetry endpoint for synthetics - const params = { - page, - autorefreshInterval: autorefreshInterval / 1000, // divide by 1000 to keep it in secs - dateStart: dateRangeStart, - dateEnd: dateRangeEnd, - autoRefreshEnabled: !autorefreshIsPaused, - };*/ - setTimeout(() => { - // apiService.post(API_URLS.LOG_PAGE_VIEW, params); - }, 100); - }, [autorefreshInterval, autorefreshIsPaused, dateRangeEnd, dateRangeStart, page]); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 7f04b3992885b..27f2599fbc102 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -12,27 +12,27 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { useInspectorContext } from '@kbn/observability-plugin/public'; +import { GettingStartedPage } from './components/getting_started/getting_started_page'; import { MonitorAddEditPage } from './components/monitor_add_edit/monitor_add_edit_page'; import { OverviewPage } from './components/overview/overview_page'; import { SyntheticsPageTemplateComponent } from './components/common/pages/synthetics_page_template'; import { NotFoundPage } from './components/common/pages/not_found'; import { ServiceAllowedWrapper } from './components/common/wrappers/service_allowed_wrapper'; import { + GETTING_STARTED_ROUTE, MONITOR_ADD_ROUTE, MONITOR_MANAGEMENT_ROUTE, OVERVIEW_ROUTE, } from '../../../common/constants'; import { MonitorManagementPage } from './components/monitor_management/monitor_management_page'; import { apiService } from '../../utils/api_service'; -import { SyntheticsPage, useSyntheticsTelemetry } from './hooks/use_telemetry'; type RouteProps = { path: string; component: React.FC; dataTestSubj: string; title: string; - telemetryId: SyntheticsPage; - pageHeader: { + pageHeader?: { pageTitle: string | JSX.Element; children?: JSX.Element; rightSideItems?: JSX.Element[]; @@ -43,15 +43,22 @@ const baseTitle = i18n.translate('xpack.synthetics.routes.baseTitle', { defaultMessage: 'Synthetics - Kibana', }); -export const MONITOR_MANAGEMENT_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.heading', - { - defaultMessage: 'Monitor Management', - } -); - const getRoutes = (): RouteProps[] => { return [ + { + title: i18n.translate('xpack.synthetics.gettingStartedRoute.title', { + defaultMessage: 'Synthetics Getting Started | {baseTitle}', + values: { baseTitle }, + }), + path: GETTING_STARTED_ROUTE, + component: () => , + dataTestSubj: 'syntheticsGettingStartedPage', + template: 'centeredBody', + pageContentProps: { + paddingSize: 'none', + hasShadow: false, + }, + }, { title: i18n.translate('xpack.synthetics.overviewRoute.title', { defaultMessage: 'Synthetics Overview | {baseTitle}', @@ -60,7 +67,6 @@ const getRoutes = (): RouteProps[] => { path: OVERVIEW_ROUTE, component: () => , dataTestSubj: 'syntheticsOverviewPage', - telemetryId: SyntheticsPage.Overview, pageHeader: { pageTitle: ( @@ -89,14 +95,13 @@ const getRoutes = (): RouteProps[] => { ), dataTestSubj: 'syntheticsMonitorManagementPage', - telemetryId: SyntheticsPage.MonitorManagement, pageHeader: { pageTitle: ( @@ -118,7 +123,6 @@ const getRoutes = (): RouteProps[] => { ), dataTestSubj: 'syntheticsMonitorAddPage', - telemetryId: SyntheticsPage.MonitorAdd, pageHeader: { pageTitle: ( { ]; }; -const RouteInit: React.FC> = ({ - path, - title, - telemetryId, -}) => { - useSyntheticsTelemetry(telemetryId); +const RouteInit: React.FC> = ({ path, title }) => { useEffect(() => { document.title = title; }, [path, title]); @@ -159,14 +158,12 @@ export const PageRouter: FC = () => { path, component: RouteComponent, dataTestSubj, - telemetryId, pageHeader, ...pageTemplateProps }) => ( - +
- {/* TODO: See if the callout is needed for Synthetics App as well */} - + appState.indexStatus; +const getState = (appState: SyntheticsAppState) => appState.indexStatus; export const selectIndexState = createSelector(getState, (state) => state); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts new file mode 100644 index 0000000000000..777e72069f6f2 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/api.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ServiceLocationsState } from './service_locations'; +import { apiService } from '../../../../utils/api_service'; +import { + EncryptedSyntheticsMonitor, + FetchMonitorManagementListQueryArgs, + MonitorManagementListResult, + MonitorManagementListResultCodec, + ServiceLocationErrors, + ServiceLocationsApiResponseCodec, + SyntheticsMonitor, + SyntheticsMonitorWithId, +} from '../../../../../common/runtime_types'; +import { API_URLS } from '../../../../../common/constants'; + +export const createMonitorAPI = async ({ + monitor, +}: { + monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor; +}): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => { + return await apiService.post(API_URLS.SYNTHETICS_MONITORS, monitor); +}; + +export const updateMonitorAPI = async ({ + monitor, + id, +}: { + monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor; + id: string; +}): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitorWithId> => { + return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor); +}; + +export const fetchServiceLocations = async (): Promise => { + const { throttling, locations } = await apiService.get( + API_URLS.SERVICE_LOCATIONS, + undefined, + ServiceLocationsApiResponseCodec + ); + return { throttling, locations }; +}; + +export const fetchMonitorManagementList = async ( + params: FetchMonitorManagementListQueryArgs +): Promise => { + return await apiService.get( + API_URLS.SYNTHETICS_MONITORS, + params, + MonitorManagementListResultCodec + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/effects.ts new file mode 100644 index 0000000000000..924fb8baf1da0 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/effects.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { takeLeading } from 'redux-saga/effects'; +import { fetchMonitorListAction } from './monitor_list'; +import { fetchMonitorManagementList, fetchServiceLocations } from './api'; +import { fetchEffectFactory } from '../utils/fetch_effect'; +import { fetchServiceLocationsAction } from './service_locations'; + +export function* fetchServiceLocationsEffect() { + yield takeLeading( + String(fetchServiceLocationsAction.get), + fetchEffectFactory( + fetchServiceLocations, + fetchServiceLocationsAction.success, + fetchServiceLocationsAction.fail + ) + ); +} + +export function* fetchMonitorListEffect() { + yield takeLeading( + String(fetchMonitorListAction.get), + fetchEffectFactory( + fetchMonitorManagementList, + fetchMonitorListAction.success, + fetchMonitorListAction.fail + ) + ); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/monitor_list.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/monitor_list.ts new file mode 100644 index 0000000000000..2493f9eb173d8 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/monitor_list.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createReducer } from '@reduxjs/toolkit'; +import { IHttpFetchError } from '@kbn/core/public'; +import { createAsyncAction, Nullable } from '../utils/actions'; +import { MonitorManagementListResult } from '../../../../../common/runtime_types'; + +export const fetchMonitorListAction = createAsyncAction( + 'fetchMonitorListAction' +); + +export const monitorListReducer = createReducer( + { + data: {} as MonitorManagementListResult, + loading: false, + error: null as Nullable, + }, + (builder) => { + builder + .addCase(fetchMonitorListAction.get, (state, action) => { + state.loading = true; + }) + .addCase(fetchMonitorListAction.success, (state, action) => { + state.loading = false; + state.data = action.payload; + }) + .addCase(fetchMonitorListAction.fail, (state, action) => { + state.loading = false; + state.error = action.payload; + }); + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/selectors.ts new file mode 100644 index 0000000000000..5c7dc8360ec9f --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/selectors.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { SyntheticsAppState } from '../root_reducer'; + +export const monitorListSelector = (state: SyntheticsAppState) => state.monitorList.data; + +export const serviceLocationsSelector = (state: SyntheticsAppState) => + state.serviceLocations.locations; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/service_locations.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/service_locations.ts new file mode 100644 index 0000000000000..572d00ce3892f --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_management/service_locations.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createReducer, PayloadAction } from '@reduxjs/toolkit'; +import { IHttpFetchError } from '@kbn/core/public'; +import { createAsyncAction, Nullable } from '../utils/actions'; +import { ServiceLocations, ThrottlingOptions } from '../../../../../common/runtime_types'; + +export const fetchServiceLocationsAction = createAsyncAction( + 'fetchServiceLocationsAction' +); + +export interface ServiceLocationsState { + throttling: ThrottlingOptions | undefined; + locations: ServiceLocations; +} + +export const serviceLocationReducer = createReducer( + { + locations: [] as ServiceLocations, + loading: false, + error: null as Nullable, + }, + (builder) => { + builder + .addCase(fetchServiceLocationsAction.get, (state, action) => { + state.loading = true; + }) + .addCase( + fetchServiceLocationsAction.success, + (state, action: PayloadAction) => { + state.loading = false; + state.locations = action.payload.locations; + } + ) + .addCase(fetchServiceLocationsAction.fail, (state, action) => { + state.loading = false; + state.error = action.payload; + }); + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts index 7d1aaa60aa4f3..9a66b4e6b9e74 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts @@ -6,8 +6,13 @@ */ import { all, fork } from 'redux-saga/effects'; +import { fetchMonitorListEffect, fetchServiceLocationsEffect } from './monitor_management/effects'; import { fetchIndexStatusEffect } from './index_status'; export const rootEffect = function* root(): Generator { - yield all([fork(fetchIndexStatusEffect)]); + yield all([ + fork(fetchIndexStatusEffect), + fork(fetchServiceLocationsEffect), + fork(fetchMonitorListEffect), + ]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts index a57c0af9e6fdb..1c8ed190fd80e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts @@ -7,12 +7,16 @@ import { combineReducers } from '@reduxjs/toolkit'; +import { monitorListReducer } from './monitor_management/monitor_list'; +import { serviceLocationReducer } from './monitor_management/service_locations'; import { uiReducer } from './ui'; import { indexStatusReducer } from './index_status'; export const rootReducer = combineReducers({ ui: uiReducer, indexStatus: indexStatusReducer, + serviceLocations: serviceLocationReducer, + monitorList: monitorListReducer, }); -export type RooState = ReturnType; +export type SyntheticsAppState = ReturnType; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts index 52d9841cde961..8896e85f68290 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts @@ -6,9 +6,9 @@ */ import { createSelector } from 'reselect'; -import type { RooState } from '../root_reducer'; +import type { SyntheticsAppState } from '../root_reducer'; -const uiStateSelector = (appState: RooState) => appState.ui; +const uiStateSelector = (appState: SyntheticsAppState) => appState.ui; export const selectBasePath = createSelector(uiStateSelector, ({ basePath }) => basePath); export const selectIsIntegrationsPopupOpen = createSelector( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts new file mode 100644 index 0000000000000..d8354ee2887a0 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IHttpFetchError } from '@kbn/core/public'; +import { createAction } from '@reduxjs/toolkit'; + +export function createAsyncAction(actionStr: string) { + return { + get: createAction(actionStr), + success: createAction(`${actionStr}_SUCCESS`), + fail: createAction(`${actionStr}_FAIL`), + }; +} + +export type Nullable = T | null; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts index 387ceeb56a514..a7b3dbc9c72b3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts @@ -6,7 +6,7 @@ */ import { call, put } from 'redux-saga/effects'; -import { Action } from 'redux-actions'; +import { PayloadAction } from '@reduxjs/toolkit'; import { IHttpFetchError } from '@kbn/core/public'; /** @@ -22,10 +22,10 @@ import { IHttpFetchError } from '@kbn/core/public'; */ export function fetchEffectFactory( fetch: (request: T) => Promise, - success: (response: R) => Action, - fail: (error: IHttpFetchError) => Action + success: (response: R) => PayloadAction, + fail: (error: IHttpFetchError) => PayloadAction ) { - return function* (action: Action): Generator { + return function* (action: PayloadAction): Generator { try { const response = yield call(fetch, action.payload); if (response instanceof Error) { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx index 07fb3604abd42..808444c1f8ec8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx @@ -24,7 +24,6 @@ import { SyntheticsSettingsContextProvider, SyntheticsThemeContextProvider, SyntheticsStartupPluginsContextProvider, - SyntheticsDataViewContextProvider, } from './contexts'; import { PageRouter } from './routes'; @@ -91,19 +90,17 @@ const Application = (props: SyntheticsAppProps) => { - -
- - - - - - -
-
+
+ + + + + + +
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts index adf2a15e70fa6..3b357cb18fa7c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/syncthetics_store.mock.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { AppState } from '../../../state'; +import { SyntheticsAppState } from '../../../state/root_reducer'; /** * NOTE: This variable name MUST start with 'mock*' in order for * Jest to accept its use within a jest.mock() */ -export const mockState: AppState = { +export const mockState: SyntheticsAppState = { ui: { alertFlyoutVisible: false, basePath: 'yyz', @@ -25,6 +25,33 @@ export const mockState: AppState = { error: null, loading: false, }, + serviceLocations: { + locations: [ + { + id: 'us_central', + label: 'US Central', + geo: { + lat: 41.25, + lon: -95.86, + }, + url: 'https://test.elastic.dev', + isServiceManaged: true, + }, + ], + loading: false, + error: null, + }, + monitorList: { + data: { + total: 0, + monitors: [], + perPage: 0, + page: 0, + syncErrors: [], + }, + error: null, + loading: false, + }, }; // TODO: Complete mock state diff --git a/x-pack/plugins/synthetics/public/hooks/use_form_wrapped.tsx b/x-pack/plugins/synthetics/public/hooks/use_form_wrapped.tsx new file mode 100644 index 0000000000000..5e80eaee2c031 --- /dev/null +++ b/x-pack/plugins/synthetics/public/hooks/use_form_wrapped.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback } from 'react'; +import { FieldValues, useForm, UseFormProps } from 'react-hook-form'; + +export function useFormWrapped( + props?: UseFormProps +) { + const { register, ...restOfForm } = useForm(props); + + const euiRegister = useCallback( + (name, ...registerArgs) => { + const { ref, ...restOfRegister } = register(name, ...registerArgs); + + return { + inputRef: ref, + ...restOfRegister, + }; + }, + [register] + ); + + return { + register: euiRegister, + ...restOfForm, + }; +} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx index 80e614eb4d77f..0963500a168ba 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx @@ -31,7 +31,7 @@ import { TestRun } from '../test_now_mode/test_now_mode'; import { monitorManagementListSelector } from '../../../state/selectors'; import { kibanaService } from '../../../state/kibana_service'; -import { showSyncErrors } from '../show_sync_errors'; +import { showSyncErrors } from '../../../../apps/synthetics/components/monitor_management/show_sync_errors'; export interface ActionBarProps { monitor: SyntheticsMonitor; @@ -103,7 +103,7 @@ export const ActionBar = ({ }); setIsSuccessful(true); } else if (hasErrors && !loading) { - showSyncErrors(data.attributes.errors, locations); + showSyncErrors(data.attributes.errors, locations, kibanaService.toasts); setIsSuccessful(true); } }, [data, status, isSaving, isValid, monitorId, hasErrors, locations, loading]); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_telemetry.ts b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_telemetry.ts index 78062bb1ff7eb..fc221dedeeef2 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_telemetry.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_telemetry.ts @@ -11,7 +11,7 @@ import { apiService } from '../state/api/utils'; import { API_URLS } from '../../../common/constants'; export enum UptimePage { - Overview = 'Overview', + Overview = 'GettingStarted', MappingError = 'MappingError', Monitor = 'Monitor', MonitorAdd = 'AddMonitor', diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index 0daf2fbe74de2..127655f95e8a6 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -263,7 +263,7 @@ function registerSyntheticsRoutesWithNavigation( }), app: 'synthetics', path: '/manage-monitors', - matchFullPath: true, + matchFullPath: false, ignoreTrailingSlash: true, }, ], diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts index e39950699cb4a..2e3a76cdad964 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts @@ -82,7 +82,11 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ if (errors && errors.length > 0) { return response.ok({ - body: { message: 'error pushing monitor to the service', attributes: { errors } }, + body: { + message: 'error pushing monitor to the service', + attributes: { errors }, + id: newMonitor.id, + }, }); } diff --git a/yarn.lock b/yarn.lock index 35c60d9444f32..267029dfe8068 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23694,6 +23694,11 @@ react-helmet-async@^1.0.7: react-fast-compare "^3.2.0" shallowequal "^1.1.0" +react-hook-form@^7.30.0: + version "7.30.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.30.0.tgz#c9e2fd54d3627e43bd94bf38ef549df2e80c1371" + integrity sha512-DzjiM6o2vtDGNMB9I4yCqW8J21P314SboNG1O0obROkbg7KVS0I7bMtwSdKyapnCPjHgnxc3L7E5PEdISeEUcQ== + react-input-autosize@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2" From 7a5fef193eba1d39ef9914938c327245ba2afdda Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 23 May 2022 19:06:04 +0200 Subject: [PATCH 19/71] [ML] Context for recovered alerts (#132496) * recovered context for ad alerting rule * datafeed report for recovered alerts * mml report for recovered alerts * update executor for setting recovered context * update jest tests, fix mml check * update error messages check * update jest tests * update delayed data test * fix the mml check * enable doesSetRecoveryContext * add rule.name to the default message * fix datafeed check * recovered message * refactor, update anomaly explorer URL time range for recovered alerts * update message for recovered errorMessage alert * update delayedDataRecoveryMessage * fix time range * update message for recovered anomaly detection alert * update mml messages --- .../ml/public/alerting/register_ml_alerts.ts | 2 +- .../ml/server/lib/alerts/alerting_service.ts | 301 ++++++++++++------ .../lib/alerts/jobs_health_service.test.ts | 25 +- .../server/lib/alerts/jobs_health_service.ts | 227 ++++++++----- .../register_anomaly_detection_alert_type.ts | 32 +- .../register_jobs_monitoring_rule_type.ts | 19 +- 6 files changed, 399 insertions(+), 207 deletions(-) diff --git a/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts b/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts index b3a4c5420ca46..75aea773f662b 100644 --- a/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts +++ b/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts @@ -104,7 +104,7 @@ export function registerMlAlerts( defaultActionMessage: i18n.translate( 'xpack.ml.alertTypes.anomalyDetection.defaultActionMessage', { - defaultMessage: `Elastic Stack Machine Learning Alert: + defaultMessage: `[\\{\\{rule.name\\}\\}] Elastic Stack Machine Learning Alert: - Job IDs: \\{\\{context.jobIds\\}\\} - Time: \\{\\{context.timestampIso8601\\}\\} - Anomaly score: \\{\\{context.score\\}\\} diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index bd0d8df872125..d354e0a4eca3c 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -6,10 +6,10 @@ */ import Boom from '@hapi/boom'; +import { i18n } from '@kbn/i18n'; import rison from 'rison-node'; import { Duration } from 'moment/moment'; import { memoize } from 'lodash'; -import type { MlDatafeed } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { FIELD_FORMAT_IDS, IFieldFormat, @@ -51,8 +51,101 @@ type AggResultsResponse = { key?: number } & { }; }; +interface AnomalyESQueryParams { + resultType: AnomalyResultType; + /** Appropriate score field for requested result type. */ + anomalyScoreField: string; + anomalyScoreThreshold: number; + jobIds: string[]; + topNBuckets: number; + maxBucketInSeconds: number; + lookBackTimeInterval: string; + includeInterimResults: boolean; + /** Source index from the datafeed. Required for retrieving field types for formatting results. */ + indexPattern: string; +} + const TIME_RANGE_PADDING = 10; +/** + * TODO Replace with URL generator when https://github.com/elastic/kibana/issues/59453 is resolved + */ +export function buildExplorerUrl( + jobIds: string[], + timeRange: { from: string; to: string; mode?: string }, + type: AnomalyResultType, + r?: AlertExecutionResult +): string { + const isInfluencerResult = type === ANOMALY_RESULT_TYPE.INFLUENCER; + + /** + * Disabled until Anomaly Explorer page is fixed and properly + * support single point time selection + */ + const highlightSwimLaneSelection = false; + + const globalState = { + ml: { + jobIds, + }, + time: { + from: timeRange.from, + to: timeRange.to, + mode: timeRange.mode ?? 'absolute', + }, + }; + + const appState = { + explorer: { + mlExplorerFilter: { + ...(r && isInfluencerResult + ? { + filterActive: true, + filteredFields: [ + r.topInfluencers![0].influencer_field_name, + r.topInfluencers![0].influencer_field_value, + ], + influencersFilterQuery: { + bool: { + minimum_should_match: 1, + should: [ + { + match_phrase: { + [r.topInfluencers![0].influencer_field_name]: + r.topInfluencers![0].influencer_field_value, + }, + }, + ], + }, + }, + queryString: `${r.topInfluencers![0].influencer_field_name}:"${ + r.topInfluencers![0].influencer_field_value + }"`, + } + : {}), + }, + mlExplorerSwimlane: { + ...(r && highlightSwimLaneSelection + ? { + selectedLanes: [ + isInfluencerResult ? r.topInfluencers![0].influencer_field_value : 'Overall', + ], + selectedTimes: r.timestampEpoch, + selectedType: isInfluencerResult ? 'viewBy' : 'overall', + ...(isInfluencerResult + ? { viewByFieldName: r.topInfluencers![0].influencer_field_name } + : {}), + ...(isInfluencerResult ? {} : { showTopFieldValues: true }), + } + : {}), + }, + }, + }; + return `/app/ml/explorer/?_g=${encodeURIComponent( + rison.encode(globalState) + )}&_a=${encodeURIComponent(rison.encode(appState))}`; +} + /** * Mapping for result types and corresponding score fields. */ @@ -79,11 +172,11 @@ export function alertingServiceProvider( * Provides formatters based on the data view of the datafeed index pattern * and set of default formatters for fallback. */ - const getFormatters = memoize(async (datafeed: MlDatafeed) => { + const getFormatters = memoize(async (indexPattern: string) => { const fieldFormatsRegistry = await getFieldsFormatRegistry(); const numberFormatter = fieldFormatsRegistry.deserialize({ id: FIELD_FORMAT_IDS.NUMBER }); - const fieldFormatMap = await getFieldsFormatMap(datafeed.indices[0]); + const fieldFormatMap = await getFieldsFormatMap(indexPattern); const fieldFormatters = fieldFormatMap ? Object.entries(fieldFormatMap).reduce((acc, [fieldName, config]) => { @@ -313,8 +406,10 @@ export function alertingServiceProvider( return { count: aggTypeResults.doc_count, key: v.key, - message: - 'Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.', + message: i18n.translate('xpack.ml.alertTypes.anomalyDetectionAlertingRule.alertMessage', { + defaultMessage: + 'Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.', + }), alertInstanceKey, jobIds: [...new Set(requestedAnomalies.map((h) => h._source.job_id))], isInterim: requestedAnomalies.some((h) => h._source.is_interim), @@ -463,7 +558,7 @@ export function alertingServiceProvider( const resultsLabel = getAggResultsLabel(params.resultType); - const fieldsFormatters = await getFormatters(datafeeds![0]!); + const fieldsFormatters = await getFormatters(datafeeds![0]!.indices[0]); const formatter = getResultsFormatter( params.resultType, @@ -495,14 +590,14 @@ export function alertingServiceProvider( }; /** - * Fetches the most recent anomaly according the top N buckets within the lookback interval - * that satisfies a rule criteria. + * Gets ES query params for fetching anomalies. * - * @param params - Alert params + * @param params {MlAnomalyDetectionAlertParams} + * @return Params required for performing ES query for anomalies. */ - const fetchResult = async ( + const getQueryParams = async ( params: MlAnomalyDetectionAlertParams - ): Promise => { + ): Promise => { const jobAndGroupIds = [ ...(params.jobSelection.jobIds ?? []), ...(params.jobSelection.groupIds ?? []), @@ -534,6 +629,40 @@ export function alertingServiceProvider( const topNBuckets: number = params.topNBuckets ?? getTopNBuckets(jobsResponse[0]); + return { + jobIds, + topNBuckets, + maxBucketInSeconds, + lookBackTimeInterval, + anomalyScoreField: resultTypeScoreMapping[params.resultType], + includeInterimResults: params.includeInterim, + resultType: params.resultType, + indexPattern: datafeeds![0]!.indices[0], + anomalyScoreThreshold: params.severity, + }; + }; + + /** + * Fetches the most recent anomaly according the top N buckets within the lookback interval + * that satisfies a rule criteria. + * + * @param params - Alert params + */ + const fetchResult = async ( + params: AnomalyESQueryParams + ): Promise => { + const { + resultType, + jobIds, + maxBucketInSeconds, + topNBuckets, + lookBackTimeInterval, + anomalyScoreField, + includeInterimResults, + anomalyScoreThreshold, + indexPattern, + } = params; + const requestBody = { size: 0, query: { @@ -554,7 +683,7 @@ export function alertingServiceProvider( }, }, }, - ...(params.includeInterim + ...(includeInterimResults ? [] : [ { @@ -576,10 +705,10 @@ export function alertingServiceProvider( aggs: { max_score: { max: { - field: resultTypeScoreMapping[params.resultType], + field: anomalyScoreField, }, }, - ...getResultTypeAggRequest(params.resultType, params.severity), + ...getResultTypeAggRequest(resultType, anomalyScoreThreshold), truncate: { bucket_sort: { size: topNBuckets, @@ -622,113 +751,73 @@ export function alertingServiceProvider( prev.max_score.value > current.max_score.value ? prev : current ); - const formatters = await getFormatters(datafeeds![0]); + const formatters = await getFormatters(indexPattern); return getResultsFormatter(params.resultType, false, formatters)(topResult); }; - /** - * TODO Replace with URL generator when https://github.com/elastic/kibana/issues/59453 is resolved - * @param r - * @param type - */ - const buildExplorerUrl = (r: AlertExecutionResult, type: AnomalyResultType): string => { - const isInfluencerResult = type === ANOMALY_RESULT_TYPE.INFLUENCER; - - /** - * Disabled until Anomaly Explorer page is fixed and properly - * support single point time selection - */ - const highlightSwimLaneSelection = false; - - const globalState = { - ml: { - jobIds: r.jobIds, - }, - time: { - from: r.bucketRange.start, - to: r.bucketRange.end, - mode: 'absolute', - }, - }; - - const appState = { - explorer: { - mlExplorerFilter: { - ...(isInfluencerResult - ? { - filterActive: true, - filteredFields: [ - r.topInfluencers![0].influencer_field_name, - r.topInfluencers![0].influencer_field_value, - ], - influencersFilterQuery: { - bool: { - minimum_should_match: 1, - should: [ - { - match_phrase: { - [r.topInfluencers![0].influencer_field_name]: - r.topInfluencers![0].influencer_field_value, - }, - }, - ], - }, - }, - queryString: `${r.topInfluencers![0].influencer_field_name}:"${ - r.topInfluencers![0].influencer_field_value - }"`, - } - : {}), - }, - mlExplorerSwimlane: { - ...(highlightSwimLaneSelection - ? { - selectedLanes: [ - isInfluencerResult ? r.topInfluencers![0].influencer_field_value : 'Overall', - ], - selectedTimes: r.timestampEpoch, - selectedType: isInfluencerResult ? 'viewBy' : 'overall', - ...(isInfluencerResult - ? { viewByFieldName: r.topInfluencers![0].influencer_field_name } - : {}), - ...(isInfluencerResult ? {} : { showTopFieldValues: true }), - } - : {}), - }, - }, - }; - return `/app/ml/explorer/?_g=${encodeURIComponent( - rison.encode(globalState) - )}&_a=${encodeURIComponent(rison.encode(appState))}`; - }; - return { /** * Return the result of an alert condition execution. * * @param params - Alert params - * @param startedAt - * @param previousStartedAt */ execute: async ( - params: MlAnomalyDetectionAlertParams, - startedAt: Date, - previousStartedAt: Date | null - ): Promise => { - const result = await fetchResult(params); + params: MlAnomalyDetectionAlertParams + ): Promise< + { context: AnomalyDetectionAlertContext; name: string; isHealthy: boolean } | undefined + > => { + const queryParams = await getQueryParams(params); - if (!result) return; + if (!queryParams) { + return; + } - const anomalyExplorerUrl = buildExplorerUrl(result, params.resultType); + const result = await fetchResult(queryParams); - const executionResult = { - ...result, - name: result.alertInstanceKey, - anomalyExplorerUrl, - }; + if (result) { + const anomalyExplorerUrl = buildExplorerUrl( + result.jobIds, + { from: result.bucketRange.start, to: result.bucketRange.end }, + params.resultType, + result + ); - return executionResult; + const executionResult = { + ...result, + anomalyExplorerUrl, + }; + + return { context: executionResult, name: result.alertInstanceKey, isHealthy: false }; + } + + return { + name: '', + isHealthy: true, + context: { + anomalyExplorerUrl: buildExplorerUrl( + queryParams.jobIds, + { + from: `now-${queryParams.lookBackTimeInterval}`, + to: 'now', + mode: 'relative', + }, + queryParams.resultType + ), + jobIds: queryParams.jobIds, + message: i18n.translate( + 'xpack.ml.alertTypes.anomalyDetectionAlertingRule.recoveredMessage', + { + defaultMessage: + 'No anomalies have been found in the past {lookbackInterval} that exceed the severity threshold of {severity}.', + values: { + severity: queryParams.anomalyScoreThreshold, + lookbackInterval: queryParams.lookBackTimeInterval, + }, + } + ), + } as AnomalyDetectionAlertContext, + }; }, /** * Checks how often the alert condition will fire an alert instance diff --git a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts index f1ca545da947b..bfb9279e62d48 100644 --- a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts +++ b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts @@ -248,7 +248,16 @@ describe('JobsHealthService', () => { expect(logger.warn).not.toHaveBeenCalled(); expect(logger.debug).toHaveBeenCalledWith(`Performing health checks for job IDs: test_job_01`); expect(datafeedsService.getDatafeedByJobId).not.toHaveBeenCalled(); - expect(executionResult).toEqual([]); + expect(executionResult).toEqual([ + { + context: { + message: 'No errors in the jobs messages.', + results: [], + }, + isHealthy: true, + name: 'Errors in job messages', + }, + ]); }); test('takes into account delayed data params', async () => { @@ -287,6 +296,7 @@ describe('JobsHealthService', () => { expect(executionResult).toEqual([ { + isHealthy: false, name: 'Data delay has occurred', context: { results: [ @@ -342,6 +352,7 @@ describe('JobsHealthService', () => { expect(executionResult).toEqual([ { + isHealthy: false, name: 'Datafeed is not started', context: { results: [ @@ -356,6 +367,7 @@ describe('JobsHealthService', () => { }, }, { + isHealthy: false, name: 'Model memory limit reached', context: { results: [ @@ -370,10 +382,11 @@ describe('JobsHealthService', () => { }, ], message: - 'Job test_job_01 reached the hard model memory limit. Assign the job more memory and restore from a snapshot from prior to reaching the hard limit.', + 'Job test_job_01 reached the hard model memory limit. Assign more memory to the job and restore it from a snapshot taken prior to reaching the hard limit.', }, }, { + isHealthy: false, name: 'Data delay has occurred', context: { results: [ @@ -395,6 +408,14 @@ describe('JobsHealthService', () => { message: 'Jobs test_job_01, test_job_02 are suffering from delayed data.', }, }, + { + isHealthy: true, + name: 'Errors in job messages', + context: { + message: 'No errors in the jobs messages.', + results: [], + }, + }, ]); }); }); diff --git a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts index bd2503c183c6c..4c19846794de1 100644 --- a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { groupBy, keyBy, memoize } from 'lodash'; +import { groupBy, keyBy, memoize, partition } from 'lodash'; import { KibanaRequest, Logger, SavedObjectsClientContract } from '@kbn/core/server'; import { i18n } from '@kbn/i18n'; import { MlJob } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -37,9 +37,13 @@ import { } from '../../models/job_audit_messages/job_audit_messages'; import type { FieldFormatsRegistryProvider } from '../../../common/types/kibana'; -interface TestResult { +export interface TestResult { name: string; context: AnomalyDetectionJobsHealthAlertContext; + /** + * Indicates if the health check is successful. + */ + isHealthy: boolean; } type TestsResults = TestResult[]; @@ -152,7 +156,7 @@ export function jobsHealthServiceProvider( * Gets not started datafeeds for opened jobs. * @param jobIds */ - async getNotStartedDatafeeds(jobIds: string[]): Promise { + async getDatafeedsReport(jobIds: string[]): Promise { const datafeeds = await getDatafeeds(jobIds); if (datafeeds) { @@ -176,13 +180,13 @@ export function jobsHealthServiceProvider( }; }) .filter((datafeedStat) => { - // Find opened jobs with not started datafeeds - return datafeedStat.job_state === 'opened' && datafeedStat.datafeed_state !== 'started'; + // Find opened jobs + return datafeedStat.job_state === 'opened'; }); } }, /** - * Gets jobs that reached soft or hard model memory limits. + * Gets the model memory report for opened jobs. * @param jobIds */ async getMmlReport(jobIds: string[]): Promise { @@ -191,7 +195,7 @@ export function jobsHealthServiceProvider( const { dateFormatter, bytesFormatter } = await getFormatters(); return jobsStats - .filter((j) => j.state === 'opened' && j.model_size_stats.memory_status !== 'ok') + .filter((j) => j.state === 'opened') .map(({ job_id: jobId, model_size_stats: modelSizeStats }) => { return { job_id: jobId, @@ -210,12 +214,14 @@ export function jobsHealthServiceProvider( * @param jobs * @param timeInterval - Custom time interval provided by the user. * @param docsCount - The threshold for a number of missing documents to alert upon. + * + * @return {Promise<[DelayedDataResponse[], DelayedDataResponse[]]>} - Collections of annotations exceeded and not exceeded the docs threshold. */ async getDelayedDataReport( jobs: MlJob[], timeInterval: string | null, docsCount: number | null - ): Promise { + ): Promise<[DelayedDataResponse[], DelayedDataResponse[]]> { const jobIds = getJobIds(jobs); const datafeeds = await getDatafeeds(jobIds); @@ -231,13 +237,14 @@ export function jobsHealthServiceProvider( const { dateFormatter } = await getFormatters(); - return ( + const annotationsData = ( await annotationService.getDelayedDataAnnotations({ jobIds: resultJobIds, earliestMs, }) ) .map((v) => { + // TODO Update when https://github.com/elastic/elasticsearch/issues/76088 is resolved. const match = v.annotation.match(/Datafeed has missed (\d+)\s/); const missedDocsCount = match ? parseInt(match[1], 10) : 0; return { @@ -255,14 +262,12 @@ export function jobsHealthServiceProvider( const job = jobsMap[v.job_id]; const datafeed = datafeedsMap[v.job_id]; - const isDocCountExceededThreshold = docsCount ? v.missed_docs_count >= docsCount : true; - const jobLookbackInterval = resolveLookbackInterval([job], [datafeed]); const isEndTimestampWithinRange = v.end_timestamp > getDelayedDataLookbackTimestamp(timeInterval, jobLookbackInterval); - return isDocCountExceededThreshold && isEndTimestampWithinRange; + return isEndTimestampWithinRange; }) .map((v) => { return { @@ -270,6 +275,11 @@ export function jobsHealthServiceProvider( end_timestamp: dateFormatter(v.end_timestamp), }; }); + + return partition(annotationsData, (v) => { + const isDocCountExceededThreshold = docsCount ? v.missed_docs_count >= docsCount : true; + return isDocCountExceededThreshold; + }); }, /** * Retrieves a list of the latest errors per jobs. @@ -322,21 +332,39 @@ export function jobsHealthServiceProvider( logger.debug(`Performing health checks for job IDs: ${jobIds.join(', ')}`); if (config.datafeed.enabled) { - const response = await this.getNotStartedDatafeeds(jobIds); + const response = await this.getDatafeedsReport(jobIds); if (response && response.length > 0) { - const { count, jobsString } = getJobsAlertingMessageValues(response); + const [startedDatafeeds, notStartedDatafeeds] = partition( + response, + (datafeedStat) => datafeedStat.datafeed_state === 'started' + ); + + const isHealthy = notStartedDatafeeds.length === 0; + const datafeedResults = isHealthy ? startedDatafeeds : notStartedDatafeeds; + const { count, jobsString } = getJobsAlertingMessageValues(datafeedResults); + results.push({ + isHealthy, name: HEALTH_CHECK_NAMES.datafeed.name, context: { - results: response, - message: i18n.translate( - 'xpack.ml.alertTypes.jobsHealthAlertingRule.datafeedStateMessage', - { - defaultMessage: - 'Datafeed is not started for {count, plural, one {job} other {jobs}} {jobsString}', - values: { count, jobsString }, - } - ), + results: datafeedResults, + message: isHealthy + ? i18n.translate( + 'xpack.ml.alertTypes.jobsHealthAlertingRule.datafeedRecoveryMessage', + { + defaultMessage: + 'Datafeed is started for {count, plural, one {job} other {jobs}} {jobsString}', + values: { count, jobsString }, + } + ) + : i18n.translate( + 'xpack.ml.alertTypes.jobsHealthAlertingRule.datafeedStateMessage', + { + defaultMessage: + 'Datafeed is not started for {count, plural, one {job} other {jobs}} {jobsString}', + values: { count, jobsString }, + } + ), }, }); } @@ -345,49 +373,62 @@ export function jobsHealthServiceProvider( if (config.mml.enabled) { const response = await this.getMmlReport(jobIds); if (response && response.length > 0) { - const { hard_limit: hardLimitJobs, soft_limit: softLimitJobs } = groupBy( - response, - 'memory_status' - ); + const { + hard_limit: hardLimitJobs, + soft_limit: softLimitJobs, + ok: okJobs, + } = groupBy(response, 'memory_status'); - const { count: hardLimitCount, jobsString: hardLimitJobsString } = - getJobsAlertingMessageValues(hardLimitJobs); - const { count: softLimitCount, jobsString: softLimitJobsString } = - getJobsAlertingMessageValues(softLimitJobs); + const isHealthy = !hardLimitJobs?.length && !softLimitJobs?.length; let message = ''; - if (hardLimitCount > 0) { - message = i18n.translate('xpack.ml.alertTypes.jobsHealthAlertingRule.mmlMessage', { - defaultMessage: `{count, plural, one {Job} other {Jobs}} {jobsString} reached the hard model memory limit. Assign the job more memory and restore from a snapshot from prior to reaching the hard limit.`, - values: { - count: hardLimitCount, - jobsString: hardLimitJobsString, - }, - }); - } - - if (softLimitCount > 0) { - if (message.length > 0) { - message += '\n'; - } - message += i18n.translate( - 'xpack.ml.alertTypes.jobsHealthAlertingRule.mmlSoftLimitMessage', + if (isHealthy) { + message = i18n.translate( + 'xpack.ml.alertTypes.jobsHealthAlertingRule.mmlRecoveredMessage', { - defaultMessage: - '{count, plural, one {Job} other {Jobs}} {jobsString} reached the soft model memory limit. Assign the job more memory or edit the datafeed filter to limit scope of analysis.', + defaultMessage: `All jobs are running within configured model memory limits.`, + } + ); + } else { + const { count: hardLimitCount, jobsString: hardLimitJobsString } = + getJobsAlertingMessageValues(hardLimitJobs); + const { count: softLimitCount, jobsString: softLimitJobsString } = + getJobsAlertingMessageValues(softLimitJobs); + + if (hardLimitCount > 0) { + message = i18n.translate('xpack.ml.alertTypes.jobsHealthAlertingRule.mmlMessage', { + defaultMessage: `{count, plural, one {Job} other {Jobs}} {jobsString} reached the hard model memory limit. Assign more memory to the job and restore it from a snapshot taken prior to reaching the hard limit.`, values: { - count: softLimitCount, - jobsString: softLimitJobsString, + count: hardLimitCount, + jobsString: hardLimitJobsString, }, + }); + } + + if (softLimitCount > 0) { + if (message.length > 0) { + message += '\n'; } - ); + message += i18n.translate( + 'xpack.ml.alertTypes.jobsHealthAlertingRule.mmlSoftLimitMessage', + { + defaultMessage: + '{count, plural, one {Job} other {Jobs}} {jobsString} reached the soft model memory limit. Assign more memory to the job or edit the datafeed filter to limit the scope of analysis.', + values: { + count: softLimitCount, + jobsString: softLimitJobsString, + }, + } + ); + } } results.push({ + isHealthy, name: HEALTH_CHECK_NAMES.mml.name, context: { - results: response, + results: isHealthy ? okJobs : [...(hardLimitJobs ?? []), ...(softLimitJobs ?? [])], message, }, }); @@ -395,51 +436,63 @@ export function jobsHealthServiceProvider( } if (config.delayedData.enabled) { - const response = await this.getDelayedDataReport( - jobs, - config.delayedData.timeInterval, - config.delayedData.docsCount - ); - - const { count, jobsString } = getJobsAlertingMessageValues(response); + const [exceededThresholdAnnotations, withinThresholdAnnotations] = + await this.getDelayedDataReport( + jobs, + config.delayedData.timeInterval, + config.delayedData.docsCount + ); - if (response.length > 0) { - results.push({ - name: HEALTH_CHECK_NAMES.delayedData.name, - context: { - results: response, - message: i18n.translate( - 'xpack.ml.alertTypes.jobsHealthAlertingRule.delayedDataMessage', - { + const isHealthy = exceededThresholdAnnotations.length === 0; + const { count, jobsString } = getJobsAlertingMessageValues(exceededThresholdAnnotations); + + results.push({ + isHealthy, + name: HEALTH_CHECK_NAMES.delayedData.name, + context: { + results: isHealthy ? withinThresholdAnnotations : exceededThresholdAnnotations, + message: isHealthy + ? i18n.translate( + 'xpack.ml.alertTypes.jobsHealthAlertingRule.delayedDataRecoveryMessage', + { + defaultMessage: 'No data delay has occurred.', + } + ) + : i18n.translate('xpack.ml.alertTypes.jobsHealthAlertingRule.delayedDataMessage', { defaultMessage: '{count, plural, one {Job} other {Jobs}} {jobsString} {count, plural, one {is} other {are}} suffering from delayed data.', values: { count, jobsString }, - } - ), - }, - }); - } + }), + }, + }); } if (config.errorMessages.enabled && previousStartedAt) { const response = await this.getErrorsReport(jobIds, previousStartedAt); - if (response.length > 0) { - const { count, jobsString } = getJobsAlertingMessageValues(response); - results.push({ - name: HEALTH_CHECK_NAMES.errorMessages.name, - context: { - results: response, - message: i18n.translate( - 'xpack.ml.alertTypes.jobsHealthAlertingRule.errorMessagesMessage', - { + const { count, jobsString } = getJobsAlertingMessageValues(response); + const isHealthy = response.length === 0; + + results.push({ + isHealthy, + name: HEALTH_CHECK_NAMES.errorMessages.name, + context: { + results: response, + message: isHealthy + ? i18n.translate( + 'xpack.ml.alertTypes.jobsHealthAlertingRule.errorMessagesRecoveredMessage', + { + defaultMessage: + 'No errors in the {count, plural, one {job} other {jobs}} messages.', + values: { count }, + } + ) + : i18n.translate('xpack.ml.alertTypes.jobsHealthAlertingRule.errorMessagesMessage', { defaultMessage: '{count, plural, one {Job} other {Jobs}} {jobsString} {count, plural, one {contains} other {contain}} errors in the messages.', values: { count, jobsString }, - } - ), - }, - }); - } + }), + }, + }); } return results; diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index e34f32d603b2a..7aa81e7668c0f 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -23,17 +23,24 @@ import { import { RegisterAlertParams } from './register_ml_alerts'; import { InfluencerAnomalyAlertDoc, RecordAnomalyAlertDoc } from '../../../common/types/alerts'; -export type AnomalyDetectionAlertContext = { - name: string; +/** + * Base Anomaly detection alerting rule context. + * Relevant for both active and recovered alerts. + */ +export type AnomalyDetectionAlertBaseContext = AlertInstanceContext & { jobIds: string[]; + anomalyExplorerUrl: string; + message: string; +}; + +export type AnomalyDetectionAlertContext = AnomalyDetectionAlertBaseContext & { timestampIso8601: string; timestamp: number; score: number; isInterim: boolean; topRecords: RecordAnomalyAlertDoc[]; topInfluencers?: InfluencerAnomalyAlertDoc[]; - anomalyExplorerUrl: string; -} & AlertInstanceContext; +}; export const ANOMALY_SCORE_MATCH_GROUP_ID = 'anomaly_score_match'; @@ -129,18 +136,27 @@ export function registerAnomalyDetectionAlertType({ producer: PLUGIN_ID, minimumLicenseRequired: MINIMUM_FULL_LICENSE, isExportable: true, - async executor({ services, params, alertId, state, previousStartedAt, startedAt }) { + doesSetRecoveryContext: true, + async executor({ services, params, alertId, state, previousStartedAt, startedAt, name }) { const fakeRequest = {} as KibanaRequest; const { execute } = mlSharedServices.alertingServiceProvider( services.savedObjectsClient, fakeRequest ); - const executionResult = await execute(params, startedAt, previousStartedAt); + const executionResult = await execute(params); - if (executionResult) { + if (executionResult && !executionResult.isHealthy) { const alertInstanceName = executionResult.name; const alertInstance = services.alertFactory.create(alertInstanceName); - alertInstance.scheduleActions(ANOMALY_SCORE_MATCH_GROUP_ID, executionResult); + alertInstance.scheduleActions(ANOMALY_SCORE_MATCH_GROUP_ID, executionResult.context); + } + + // Set context for recovered alerts + const { getRecoveredAlerts } = services.alertFactory.done(); + for (const recoveredAlert of getRecoveredAlerts()) { + if (!!executionResult?.isHealthy) { + recoveredAlert.setContext(executionResult.context); + } } }, }); diff --git a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts index 1414262969153..7ea087e1239a6 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts @@ -140,6 +140,7 @@ export function registerJobsMonitoringRuleType({ producer: PLUGIN_ID, minimumLicenseRequired: MINIMUM_FULL_LICENSE, isExportable: true, + doesSetRecoveryContext: true, async executor(options) { const { services, name } = options; @@ -151,18 +152,30 @@ export function registerJobsMonitoringRuleType({ ); const executionResult = await getTestsResults(options); - if (executionResult.length > 0) { + const unhealthyTests = executionResult.filter(({ isHealthy }) => !isHealthy); + + if (unhealthyTests.length > 0) { logger.debug( - `"${name}" rule is scheduling actions for tests: ${executionResult + `"${name}" rule is scheduling actions for tests: ${unhealthyTests .map((v) => v.name) .join(', ')}` ); - executionResult.forEach(({ name: alertInstanceName, context }) => { + unhealthyTests.forEach(({ name: alertInstanceName, context }) => { const alertInstance = services.alertFactory.create(alertInstanceName); alertInstance.scheduleActions(ANOMALY_DETECTION_JOB_REALTIME_ISSUE, context); }); } + + // Set context for recovered alerts + const { getRecoveredAlerts } = services.alertFactory.done(); + for (const recoveredAlert of getRecoveredAlerts()) { + const recoveredAlertId = recoveredAlert.getId(); + const testResult = executionResult.find((v) => v.name === recoveredAlertId); + if (testResult) { + recoveredAlert.setContext(testResult.context); + } + } }, }); } From f9b065e22858a17fefd67c0e4e8e99ed799607b1 Mon Sep 17 00:00:00 2001 From: Pius Date: Mon, 23 May 2022 10:07:56 -0700 Subject: [PATCH 20/71] Cross reference audit log settings (#132359) * Cross reference audit log settings * Update docs/user/security/audit-logging.asciidoc Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- docs/user/security/audit-logging.asciidoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index caa6512955f67..8cd293654c29c 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -15,8 +15,10 @@ by cluster-wide privileges. For more information on enabling audit logging in [NOTE] ============================================================================ Audit logs are **disabled** by default. To enable this functionality, you must -set `xpack.security.audit.enabled` to `true` in `kibana.yml`, and optionally configure -an <> to write the audit log to a location of your choosing. +set `xpack.security.audit.enabled` to `true` in `kibana.yml`. + +You can optionally configure audit logs location, file/rolling file appenders and +ignore filters using <>. ============================================================================ [[xpack-security-ecs-audit-logging]] From 65054ae3b0d4fb713084d85df11e26c37034648c Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Mon, 23 May 2022 18:15:01 +0100 Subject: [PATCH 21/71] Endpoint Timeline telemetry (#132626) * Staging branch. Update. Fix up timeline task types + received logic. switch between internal / current user based on the context of the call. Send to telemetry endpoint. * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Add test for the entire task. * This code shouldn't have been deleted. * Return 100 every 3 hours. * Don't be explicit about types for test. Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../server/endpoint/routes/resolver.ts | 2 +- .../server/endpoint/routes/resolver/entity.ts | 136 - .../routes/resolver/entity/handler.ts | 49 + .../entity/utils/build_resolver_entity.ts | 37 + .../entity/utils/supported_schemas.ts | 75 + .../server/endpoint/routes/resolver/events.ts | 1 - .../resolver/tree/queries/descendants.ts | 11 +- .../routes/resolver/tree/queries/lifecycle.ts | 9 +- .../routes/resolver/tree/queries/stats.ts | 10 +- .../routes/resolver/tree/utils/fetch.ts | 27 +- .../server/lib/telemetry/__mocks__/index.ts | 43 +- .../lib/telemetry/__mocks__/timeline.ts | 2582 +++++++++++++++++ .../server/lib/telemetry/constants.ts | 2 + .../server/lib/telemetry/receiver.ts | 159 +- .../server/lib/telemetry/tasks/index.ts | 2 + .../lib/telemetry/tasks/timelines.test.ts | 40 + .../server/lib/telemetry/tasks/timelines.ts | 149 + .../server/lib/telemetry/types.ts | 18 + 18 files changed, 3195 insertions(+), 157 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/handler.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/build_resolver_entity.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts index 2e50b54f9db70..76336e33cb522 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts @@ -13,7 +13,7 @@ import { } from '../../../common/endpoint/schema/resolver'; import { handleTree } from './resolver/tree/handler'; -import { handleEntities } from './resolver/entity'; +import { handleEntities } from './resolver/entity/handler'; import { handleEvents } from './resolver/events'; export function registerResolverRoutes(router: IRouter) { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts deleted file mode 100644 index ef438667c3647..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import _ from 'lodash'; -import { RequestHandler } from '@kbn/core/server'; -import { TypeOf } from '@kbn/config-schema'; -import { validateEntities } from '../../../../common/endpoint/schema/resolver'; -import { ResolverEntityIndex, ResolverSchema } from '../../../../common/endpoint/types'; - -interface SupportedSchema { - /** - * A name for the schema being used - */ - name: string; - - /** - * A constraint to search for in the documented returned by Elasticsearch - */ - constraints: Array<{ field: string; value: string }>; - - /** - * Schema to return to the frontend so that it can be passed in to call to the /tree API - */ - schema: ResolverSchema; -} - -/** - * This structure defines the preset supported schemas for a resolver graph. We'll probably want convert this - * implementation to something similar to how row renderers is implemented. - */ -const supportedSchemas: SupportedSchema[] = [ - { - name: 'endpoint', - constraints: [ - { - field: 'agent.type', - value: 'endpoint', - }, - ], - schema: { - id: 'process.entity_id', - parent: 'process.parent.entity_id', - ancestry: 'process.Ext.ancestry', - name: 'process.name', - }, - }, - { - name: 'winlogbeat', - constraints: [ - { - field: 'agent.type', - value: 'winlogbeat', - }, - { - field: 'event.module', - value: 'sysmon', - }, - ], - schema: { - id: 'process.entity_id', - parent: 'process.parent.entity_id', - name: 'process.name', - }, - }, -]; - -function getFieldAsString(doc: unknown, field: string): string | undefined { - const value = _.get(doc, field); - if (value === undefined) { - return undefined; - } - - return String(value); -} - -/** - * This is used to get an 'entity_id' which is an internal-to-Resolver concept, from an `_id`, which - * is the artificial ID generated by ES for each document. - */ -export function handleEntities(): RequestHandler> { - return async (context, request, response) => { - const { - query: { _id, indices }, - } = request; - - const esClient = (await context.core).elasticsearch.client; - const queryResponse = await esClient.asCurrentUser.search({ - ignore_unavailable: true, - index: indices, - body: { - // only return 1 match at most - size: 1, - query: { - bool: { - filter: [ - { - // only return documents with the matching _id - ids: { - values: _id, - }, - }, - ], - }, - }, - }, - }); - - const responseBody: ResolverEntityIndex = []; - for (const hit of queryResponse.hits.hits) { - for (const supportedSchema of supportedSchemas) { - let foundSchema = true; - // check that the constraint and id fields are defined and that the id field is not an empty string - const id = getFieldAsString(hit._source, supportedSchema.schema.id); - for (const constraint of supportedSchema.constraints) { - const fieldValue = getFieldAsString(hit._source, constraint.field); - // track that all the constraints are true, if one of them is false then this schema is not valid so mark it - // that we did not find the schema - foundSchema = foundSchema && fieldValue?.toLowerCase() === constraint.value.toLowerCase(); - } - - if (foundSchema && id !== undefined && id !== '') { - responseBody.push({ - name: supportedSchema.name, - schema: supportedSchema.schema, - id, - }); - } - } - } - return response.ok({ body: responseBody }); - }; -} diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/handler.ts new file mode 100644 index 0000000000000..2552cf4754c90 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/handler.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RequestHandler } from '@kbn/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { validateEntities } from '../../../../../common/endpoint/schema/resolver'; +import { ResolverEntityIndex } from '../../../../../common/endpoint/types'; +import { resolverEntity } from './utils/build_resolver_entity'; + +/** + * This is used to get an 'entity_id' which is an internal-to-Resolver concept, from an `_id`, which + * is the artificial ID generated by ES for each document. + */ +export function handleEntities(): RequestHandler> { + return async (context, request, response) => { + const { + query: { _id, indices }, + } = request; + + const esClient = (await context.core).elasticsearch.client; + const queryResponse = await esClient.asCurrentUser.search({ + ignore_unavailable: true, + index: indices, + body: { + // only return 1 match at most + size: 1, + query: { + bool: { + filter: [ + { + // only return documents with the matching _id + ids: { + values: _id, + }, + }, + ], + }, + }, + }, + }); + + const responseBody: ResolverEntityIndex = resolverEntity(queryResponse.hits.hits); + return response.ok({ body: responseBody }); + }; +} diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/build_resolver_entity.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/build_resolver_entity.ts new file mode 100644 index 0000000000000..3c554f1602591 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/build_resolver_entity.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { getFieldAsString, supportedSchemas } from './supported_schemas'; +import { ResolverEntityIndex } from '../../../../../../common/endpoint/types'; + +export function resolverEntity(hits: Array>) { + const responseBody: ResolverEntityIndex = []; + for (const hit of hits) { + for (const supportedSchema of supportedSchemas) { + let foundSchema = true; + // check that the constraint and id fields are defined and that the id field is not an empty string + const id = getFieldAsString(hit._source, supportedSchema.schema.id); + for (const constraint of supportedSchema.constraints) { + const fieldValue = getFieldAsString(hit._source, constraint.field); + // track that all the constraints are true, if one of them is false then this schema is not valid so mark it + // that we did not find the schema + foundSchema = foundSchema && fieldValue?.toLowerCase() === constraint.value.toLowerCase(); + } + + if (foundSchema && id !== undefined && id !== '') { + responseBody.push({ + name: supportedSchema.name, + schema: supportedSchema.schema, + id, + }); + } + } + } + + return responseBody; +} diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts new file mode 100644 index 0000000000000..9c40b2b5024e6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/entity/utils/supported_schemas.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; +import { ResolverSchema } from '../../../../../../common/endpoint/types'; + +interface SupportedSchema { + /** + * A name for the schema being used + */ + name: string; + + /** + * A constraint to search for in the documented returned by Elasticsearch + */ + constraints: Array<{ field: string; value: string }>; + + /** + * Schema to return to the frontend so that it can be passed in to call to the /tree API + */ + schema: ResolverSchema; +} + +/** + * This structure defines the preset supported schemas for a resolver graph. We'll probably want convert this + * implementation to something similar to how row renderers is implemented. + */ +export const supportedSchemas: SupportedSchema[] = [ + { + name: 'endpoint', + constraints: [ + { + field: 'agent.type', + value: 'endpoint', + }, + ], + schema: { + id: 'process.entity_id', + parent: 'process.parent.entity_id', + ancestry: 'process.Ext.ancestry', + name: 'process.name', + }, + }, + { + name: 'winlogbeat', + constraints: [ + { + field: 'agent.type', + value: 'winlogbeat', + }, + { + field: 'event.module', + value: 'sysmon', + }, + ], + schema: { + id: 'process.entity_id', + parent: 'process.parent.entity_id', + name: 'process.name', + }, + }, +]; + +export function getFieldAsString(doc: unknown, field: string): string | undefined { + const value = _.get(doc, field); + if (value === undefined) { + return undefined; + } + + return String(value); +} diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts index 2acc9e6aeb114..9734c89900960 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts @@ -46,7 +46,6 @@ export function handleEvents(): RequestHandler< timeRange: body.timeRange, }); const results = await query.search(client, body.filter); - return res.ok({ body: createEvents(results, PaginationBuilder.buildCursorRequestLimit(limit, results)), }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts index 09da7f87bf6c8..d4206dc853a3c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts @@ -15,6 +15,7 @@ interface DescendantsParams { schema: ResolverSchema; indexPatterns: string | string[]; timeRange: TimeRange; + isInternalRequest: boolean; } /** @@ -25,12 +26,14 @@ export class DescendantsQuery { private readonly indexPatterns: string | string[]; private readonly timeRange: TimeRange; private readonly docValueFields: JsonValue[]; + private readonly isInternalRequest: boolean; - constructor({ schema, indexPatterns, timeRange }: DescendantsParams) { + constructor({ schema, indexPatterns, timeRange, isInternalRequest }: DescendantsParams) { this.docValueFields = docValueFields(schema); this.schema = schema; this.indexPatterns = indexPatterns; this.timeRange = timeRange; + this.isInternalRequest = isInternalRequest; } private query(nodes: NodeID[], size: number): JsonObject { @@ -198,14 +201,16 @@ export class DescendantsQuery { return []; } + const esClient = this.isInternalRequest ? client.asInternalUser : client.asCurrentUser; + let response: estypes.SearchResponse; if (this.schema.ancestry) { - response = await client.asCurrentUser.search({ + response = await esClient.search({ body: this.queryWithAncestryArray(validNodes, this.schema.ancestry, limit), index: this.indexPatterns, }); } else { - response = await client.asCurrentUser.search({ + response = await esClient.search({ body: this.query(validNodes, limit), index: this.indexPatterns, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts index 88b2e842ce8cb..be5a34092e0c5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts @@ -14,6 +14,7 @@ interface LifecycleParams { schema: ResolverSchema; indexPatterns: string | string[]; timeRange: TimeRange; + isInternalRequest: boolean; } /** @@ -24,11 +25,13 @@ export class LifecycleQuery { private readonly indexPatterns: string | string[]; private readonly timeRange: TimeRange; private readonly docValueFields: JsonValue[]; - constructor({ schema, indexPatterns, timeRange }: LifecycleParams) { + private readonly isInternalRequest: boolean; + constructor({ schema, indexPatterns, timeRange, isInternalRequest }: LifecycleParams) { this.docValueFields = docValueFields(schema); this.schema = schema; this.indexPatterns = indexPatterns; this.timeRange = timeRange; + this.isInternalRequest = isInternalRequest; } private query(nodes: NodeID[]): JsonObject { @@ -91,7 +94,9 @@ export class LifecycleQuery { return []; } - const body = await client.asCurrentUser.search({ + const esClient = this.isInternalRequest ? client.asInternalUser : client.asCurrentUser; + + const body = await esClient.search({ body: this.query(validNodes), index: this.indexPatterns, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts index b87b121e5769f..b4143345d9db4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts @@ -29,6 +29,7 @@ interface StatsParams { schema: ResolverSchema; indexPatterns: string | string[]; timeRange: TimeRange; + isInternalRequest: boolean; } /** @@ -38,10 +39,13 @@ export class StatsQuery { private readonly schema: ResolverSchema; private readonly indexPatterns: string | string[]; private readonly timeRange: TimeRange; - constructor({ schema, indexPatterns, timeRange }: StatsParams) { + private readonly isInternalRequest: boolean; + + constructor({ schema, indexPatterns, timeRange, isInternalRequest }: StatsParams) { this.schema = schema; this.indexPatterns = indexPatterns; this.timeRange = timeRange; + this.isInternalRequest = isInternalRequest; } private query(nodes: NodeID[]): JsonObject { @@ -122,8 +126,10 @@ export class StatsQuery { return {}; } + const esClient = this.isInternalRequest ? client.asInternalUser : client.asCurrentUser; + // leaving unknown here because we don't actually need the hits part of the body - const body = await client.asCurrentUser.search({ + const body = await esClient.search({ body: this.query(nodes), index: this.indexPatterns, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/utils/fetch.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/utils/fetch.ts index f7d4b7d97efdb..add798f861c96 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/utils/fetch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/utils/fetch.ts @@ -49,10 +49,13 @@ export class Fetcher { * * @param options the options for retrieving the structure of the tree. */ - public async tree(options: TreeOptions): Promise { + public async tree( + options: TreeOptions, + isInternalRequest: boolean = false + ): Promise { const treeParts = await Promise.all([ - this.retrieveAncestors(options), - this.retrieveDescendants(options), + this.retrieveAncestors(options, isInternalRequest), + this.retrieveDescendants(options, isInternalRequest), ]); const tree = treeParts.reduce((results, partArray) => { @@ -60,12 +63,13 @@ export class Fetcher { return results; }, []); - return this.formatResponse(tree, options); + return this.formatResponse(tree, options, isInternalRequest); } private async formatResponse( treeNodes: FieldsObject[], - options: TreeOptions + options: TreeOptions, + isInternalRequest: boolean ): Promise { const statsIDs: NodeID[] = []; for (const node of treeNodes) { @@ -79,6 +83,7 @@ export class Fetcher { indexPatterns: options.indexPatterns, schema: options.schema, timeRange: options.timeRange, + isInternalRequest, }); const eventStats = await query.search(this.client, statsIDs); @@ -133,12 +138,16 @@ export class Fetcher { return nodes; } - private async retrieveAncestors(options: TreeOptions): Promise { + private async retrieveAncestors( + options: TreeOptions, + isInternalRequest: boolean + ): Promise { const ancestors: FieldsObject[] = []; const query = new LifecycleQuery({ schema: options.schema, indexPatterns: options.indexPatterns, timeRange: options.timeRange, + isInternalRequest, }); let nodes = options.nodes; @@ -179,12 +188,16 @@ export class Fetcher { return ancestors; } - private async retrieveDescendants(options: TreeOptions): Promise { + private async retrieveDescendants( + options: TreeOptions, + isInternalRequest: boolean + ): Promise { const descendants: FieldsObject[] = []; const query = new DescendantsQuery({ schema: options.schema, indexPatterns: options.indexPatterns, timeRange: options.timeRange, + isInternalRequest, }); let nodes: NodeID[] = options.nodes; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts index b28a5213b4326..d91f08f15cedf 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts @@ -5,11 +5,13 @@ * 2.0. */ +import moment from 'moment'; import { ConcreteTaskInstance, TaskStatus } from '@kbn/task-manager-plugin/server'; import { TelemetryEventsSender } from '../sender'; import { TelemetryReceiver } from '../receiver'; import { SecurityTelemetryTaskConfig } from '../task'; import { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/package_policy'; +import { stubEndpointAlertResponse, stubProcessTree, stubFetchTimelineEvents } from './timeline'; export const createMockTelemetryEventsSender = ( enableTelemetry?: boolean @@ -29,13 +31,45 @@ export const createMockTelemetryEventsSender = ( } as unknown as jest.Mocked; }; +const stubClusterInfo = { + name: 'Stub-MacBook-Pro.local', + cluster_name: 'elasticsearch', + cluster_uuid: '5Pr5PXRQQpGJUTn0czAvKQ', + version: { + number: '8.0.0-SNAPSHOT', + build_type: 'tar', + build_hash: '38537ab4a726b42ce8f034aad78d8fca4d4f3e51', + build_date: moment().toISOString(), + build_snapshot: true, + lucene_version: '9.2.0', + minimum_wire_compatibility_version: '7.17.0', + minimum_index_compatibility_version: '7.0.0', + }, + tagline: 'You Know, for Search', +}; + +const stubLicenseInfo = { + status: 'active', + uid: '4a7dde08-e5f8-4e50-80f8-bc85b72b4934', + type: 'trial', + issue_date: moment().toISOString(), + issue_date_in_millis: 1653299879146, + expiry_date: moment().toISOString(), + expiry_date_in_millis: 1655891879146, + max_nodes: 1000, + max_resource_units: null, + issued_to: 'elasticsearch', + issuer: 'elasticsearch', + start_date_in_millis: -1, +}; + export const createMockTelemetryReceiver = ( diagnosticsAlert?: unknown ): jest.Mocked => { return { start: jest.fn(), - fetchClusterInfo: jest.fn(), - fetchLicenseInfo: jest.fn(), + fetchClusterInfo: jest.fn().mockReturnValue(stubClusterInfo), + fetchLicenseInfo: jest.fn().mockReturnValue(stubLicenseInfo), copyLicenseFields: jest.fn(), fetchFleetAgents: jest.fn(), fetchDiagnosticAlerts: jest.fn().mockReturnValue(diagnosticsAlert ?? jest.fn()), @@ -45,6 +79,11 @@ export const createMockTelemetryReceiver = ( fetchEndpointList: jest.fn(), fetchDetectionRules: jest.fn().mockReturnValue({ body: null }), fetchEndpointMetadata: jest.fn(), + fetchTimelineEndpointAlerts: jest + .fn() + .mockReturnValue(Promise.resolve(stubEndpointAlertResponse())), + buildProcessTree: jest.fn().mockReturnValue(Promise.resolve(stubProcessTree())), + fetchTimelineEvents: jest.fn().mockReturnValue(Promise.resolve(stubFetchTimelineEvents())), } as unknown as jest.Mocked; }; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts new file mode 100644 index 0000000000000..2cb1fddaa47cc --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts @@ -0,0 +1,2582 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import type { ResolverNode } from '../../../../common/endpoint/types'; + +export const stubEndpointAlertResponse = () => { + return { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 47, + relation: 'eq', + }, + max_score: 0, + hits: [ + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: '2f4c790211998ec3369f581b778e9761ae5647d041edd7b1245f7311fba06f37', + _score: 0, + _source: { + 'kibana.alert.rule.category': 'Custom Query Rule', + 'kibana.alert.rule.consumer': 'siem', + 'kibana.alert.rule.execution.uuid': 'c92c1a91-9981-4948-8dee-39b263d81f05', + 'kibana.alert.rule.name': 'Endpoint Security', + 'kibana.alert.rule.producer': 'siem', + 'kibana.alert.rule.rule_type_id': 'siem.queryRule', + 'kibana.alert.rule.uuid': 'b35e3af8-da87-11ec-ad90-353e53c6bd3e', + 'kibana.space_ids': ['default'], + 'kibana.alert.rule.tags': ['Elastic', 'Endpoint Security'], + '@timestamp': moment.now(), + registry: { + path: 'HKEY_USERS\\S-1-5-21-2460036010-3910878774-3458087990-1001\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\chrome', + data: { + strings: 'C:/fake_behavior/explorer.exe', + }, + value: 'explorer.exe', + }, + agent: { + id: 'd2529c31-5415-492a-9c9b-87a77e8874d5', + type: 'endpoint', + version: '7.0.1', + }, + process: { + Ext: { + ancestry: ['j0mdzksneq', 'up4f1f87wr'], + code_signature: [ + { + trusted: false, + subject_name: 'bad signer', + }, + ], + user: 'SYSTEM', + token: { + integrity_level_name: 'high', + elevation_level: 'full', + }, + }, + parent: { + pid: 1, + entity_id: 'j0mdzksneq', + }, + group_leader: { + name: 'fake leader', + pid: 112, + entity_id: '3po060bfqd', + }, + session_leader: { + name: 'fake session', + pid: 7, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft Windows', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 139, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + pid: 2, + entity_id: 'p1dbx787xe', + executable: 'C:/fake_behavior/explorer.exe', + }, + dll: [ + { + Ext: { + compile_time: 1534424710, + malware_classification: { + identifier: 'Whitelisted', + score: 0, + threshold: 0, + version: '3.0.0', + }, + mapped_address: 5362483200, + mapped_size: 0, + }, + path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe', + code_signature: { + trusted: true, + subject_name: 'Cybereason Inc', + }, + pe: { + architecture: 'x64', + }, + hash: { + sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d', + sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2', + md5: '1f2d082566b0fc5f2c238a5180db7451', + }, + }, + ], + destination: { + port: 443, + ip: '10.39.10.58', + }, + rule: { + description: 'Behavior rule description', + id: 'e2d719cc-7044-4a46-b2ee-0a2993202096', + }, + source: { + port: 59406, + ip: '10.199.40.10', + }, + network: { + transport: 'tcp', + type: 'ipv4', + direction: 'outgoing', + }, + file: { + path: 'C:/fake_behavior.exe', + name: 'fake_behavior.exe', + }, + ecs: { + version: '1.6.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'endpoint.alerts', + }, + elastic: { + agent: { + id: 'd2529c31-5415-492a-9c9b-87a77e8874d5', + }, + }, + host: { + hostname: 'Host-uu8vmc2z8a', + os: { + Ext: { + variant: 'Windows Server', + }, + name: 'Windows', + family: 'windows', + version: '10.0', + platform: 'Windows', + full: 'Windows Server 2016', + }, + ip: ['10.23.178.108'], + name: 'Host-uu8vmc2z8a', + id: 'c1e90e16-0130-46d4-88de-ee338f13fed7', + mac: ['ee-83-79-cf-1a-13', 'a7-79-da-62-9e-78'], + architecture: 'a4rwx2t7yu', + }, + 'event.agent_id_status': 'auth_metadata_missing', + 'event.sequence': 15, + 'event.ingested': '2022-05-23T11:02:53Z', + 'event.code': 'behavior', + 'event.kind': 'signal', + 'event.module': 'endpoint', + 'event.action': 'rule_detection', + 'event.id': '962dba31-1306-4bb1-82c2-2a6d9ef8962d', + 'event.category': 'behavior', + 'event.type': 'info', + 'event.dataset': 'endpoint.diagnostic.collection', + 'kibana.alert.original_time': '2022-05-23T11:02:59.511Z', + 'kibana.alert.ancestors': [ + { + id: 'juKV8IABsphBWHn-nT4H', + type: 'event', + index: '.ds-logs-endpoint.alerts-default-2022.05.23-000001', + depth: 0, + }, + ], + 'kibana.alert.status': 'active', + 'kibana.alert.workflow_status': 'open', + 'kibana.alert.depth': 1, + 'kibana.alert.reason': + 'behavior event with process explorer.exe, file fake_behavior.exe,:59406,:443, on Host-uu8vmc2z8a created medium alert Endpoint Security.', + 'kibana.alert.severity': 'medium', + 'kibana.alert.risk_score': 47, + 'kibana.alert.rule.actions': [], + 'kibana.alert.rule.author': ['Elastic'], + 'kibana.alert.rule.created_at': '2022-05-23T11:01:34.044Z', + 'kibana.alert.rule.created_by': 'elastic', + 'kibana.alert.rule.description': + 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', + 'kibana.alert.rule.enabled': true, + 'kibana.alert.rule.exceptions_list': [ + { + id: 'endpoint_list', + list_id: 'endpoint_list', + namespace_type: 'agnostic', + type: 'endpoint', + }, + ], + 'kibana.alert.rule.false_positives': [], + 'kibana.alert.rule.from': 'now-10m', + 'kibana.alert.rule.immutable': true, + 'kibana.alert.rule.interval': '5m', + 'kibana.alert.rule.license': 'Elastic License v2', + 'kibana.alert.rule.max_signals': 10000, + 'kibana.alert.rule.references': [], + 'kibana.alert.rule.risk_score_mapping': [ + { + field: 'event.risk_score', + operator: 'equals', + value: '', + }, + ], + 'kibana.alert.rule.rule_id': '9a1a2dae-0b5f-4c3d-8305-a268d404c306', + 'kibana.alert.rule.rule_name_override': 'message', + 'kibana.alert.rule.severity_mapping': [ + { + field: 'event.severity', + operator: 'equals', + severity: 'low', + value: '21', + }, + { + field: 'event.severity', + operator: 'equals', + severity: 'medium', + value: '47', + }, + { + field: 'event.severity', + operator: 'equals', + severity: 'high', + value: '73', + }, + { + field: 'event.severity', + operator: 'equals', + severity: 'critical', + value: '99', + }, + ], + 'kibana.alert.rule.threat': [], + 'kibana.alert.rule.timestamp_override': 'event.ingested', + 'kibana.alert.rule.to': 'now', + 'kibana.alert.rule.type': 'query', + 'kibana.alert.rule.updated_at': '2022-05-23T11:01:34.044Z', + 'kibana.alert.rule.updated_by': 'elastic', + 'kibana.alert.rule.version': 3, + 'kibana.alert.rule.risk_score': 47, + 'kibana.alert.rule.severity': 'medium', + 'kibana.alert.original_event.agent_id_status': 'auth_metadata_missing', + 'kibana.alert.original_event.sequence': 15, + 'kibana.alert.original_event.ingested': '2022-05-23T11:02:53Z', + 'kibana.alert.original_event.code': 'behavior', + 'kibana.alert.original_event.kind': 'alert', + 'kibana.alert.original_event.module': 'endpoint', + 'kibana.alert.original_event.action': 'rule_detection', + 'kibana.alert.original_event.id': '962dba31-1306-4bb1-82c2-2a6d9ef8962d', + 'kibana.alert.original_event.category': 'behavior', + 'kibana.alert.original_event.type': 'info', + 'kibana.alert.original_event.dataset': 'endpoint.diagnostic.collection', + 'kibana.alert.uuid': '2f4c790211998ec3369f581b778e9761ae5647d041edd7b1245f7311fba06f37', + }, + }, + ], + }, + }; +}; + +export const stubProcessTree = (): ResolverNode[] => [ + { + id: 'p1dbx787xe', + parent: 'j0mdzksneq', + name: 'explorer.exe', + data: { + 'process.name': ['explorer.exe'], + '@timestamp': ['2022-05-23T11:02:57.511Z'], + 'process.parent.entity_id': ['j0mdzksneq'], + 'process.Ext.ancestry': ['j0mdzksneq', 'up4f1f87wr'], + 'process.entity_id': ['p1dbx787xe'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'up4f1f87wr', + parent: '3po060bfqd', + name: 'notepad.exe', + data: { + 'process.name': ['notepad.exe'], + '@timestamp': ['2022-05-23T11:02:55.511Z'], + 'process.parent.entity_id': ['3po060bfqd'], + 'process.Ext.ancestry': ['3po060bfqd'], + 'process.entity_id': ['up4f1f87wr'], + }, + stats: { + total: 5, + byCategory: { + registry: 2, + authentication: 1, + driver: 1, + network: 1, + session: 1, + }, + }, + }, + { + id: 'j0mdzksneq', + parent: 'up4f1f87wr', + name: 'notepad.exe', + data: { + 'process.name': ['notepad.exe'], + '@timestamp': ['2022-05-23T11:02:56.511Z'], + 'process.parent.entity_id': ['up4f1f87wr'], + 'process.Ext.ancestry': ['3po060bfqd', 'up4f1f87wr'], + 'process.entity_id': ['j0mdzksneq'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: '3po060bfqd', + name: 'mimikatz.exe', + data: { + 'process.name': ['mimikatz.exe'], + '@timestamp': ['2022-05-23T11:02:54.511Z'], + 'process.entity_id': ['3po060bfqd'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: '72221vhp1s', + parent: 'p1dbx787xe', + name: 'lsass.exe', + data: { + 'process.name': ['lsass.exe'], + '@timestamp': ['2022-05-23T11:03:00.511Z'], + 'process.parent.entity_id': ['p1dbx787xe'], + 'process.Ext.ancestry': ['j0mdzksneq', 'p1dbx787xe'], + 'process.entity_id': ['72221vhp1s'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'o055ylvrqg', + parent: 'p1dbx787xe', + name: 'powershell.exe', + data: { + 'process.name': ['powershell.exe'], + '@timestamp': ['2022-05-23T11:03:09.511Z'], + 'process.parent.entity_id': ['p1dbx787xe'], + 'process.Ext.ancestry': ['j0mdzksneq', 'p1dbx787xe'], + 'process.entity_id': ['o055ylvrqg'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'kemjvigx5w', + parent: 'p1dbx787xe', + name: 'notepad.exe', + data: { + 'process.name': ['notepad.exe'], + '@timestamp': ['2022-05-23T11:03:14.511Z'], + 'process.parent.entity_id': ['p1dbx787xe'], + 'process.Ext.ancestry': ['j0mdzksneq', 'p1dbx787xe'], + 'process.entity_id': ['kemjvigx5w'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: '9xtipos591', + parent: '72221vhp1s', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:01.511Z'], + 'process.parent.entity_id': ['72221vhp1s'], + 'process.Ext.ancestry': ['72221vhp1s', 'p1dbx787xe'], + 'process.entity_id': ['9xtipos591'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'spk93ihzue', + parent: '72221vhp1s', + name: 'explorer.exe', + data: { + 'process.name': ['explorer.exe'], + '@timestamp': ['2022-05-23T11:03:02.511Z'], + 'process.parent.entity_id': ['72221vhp1s'], + 'process.Ext.ancestry': ['72221vhp1s', 'p1dbx787xe'], + 'process.entity_id': ['spk93ihzue'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'eefk3v3tg0', + parent: '72221vhp1s', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:05.511Z'], + 'process.parent.entity_id': ['72221vhp1s'], + 'process.Ext.ancestry': ['72221vhp1s', 'p1dbx787xe'], + 'process.entity_id': ['eefk3v3tg0'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'z7t7ai4mcl', + parent: 'o055ylvrqg', + name: 'powershell.exe', + data: { + 'process.name': ['powershell.exe'], + '@timestamp': ['2022-05-23T11:03:10.511Z'], + 'process.parent.entity_id': ['o055ylvrqg'], + 'process.Ext.ancestry': ['o055ylvrqg', 'p1dbx787xe'], + 'process.entity_id': ['z7t7ai4mcl'], + }, + stats: { + total: 1, + byCategory: { + network: 1, + }, + }, + }, + { + id: '33k536gv9n', + parent: 'o055ylvrqg', + name: 'mimikatz.exe', + data: { + 'process.name': ['mimikatz.exe'], + '@timestamp': ['2022-05-23T11:03:11.511Z'], + 'process.parent.entity_id': ['o055ylvrqg'], + 'process.Ext.ancestry': ['o055ylvrqg', 'p1dbx787xe'], + 'process.entity_id': ['33k536gv9n'], + }, + stats: { + total: 5, + byCategory: { + file: 4, + network: 1, + }, + }, + }, + { + id: 'tbxjoicr50', + parent: 'kemjvigx5w', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:15.511Z'], + 'process.parent.entity_id': ['kemjvigx5w'], + 'process.Ext.ancestry': ['kemjvigx5w', 'p1dbx787xe'], + 'process.entity_id': ['tbxjoicr50'], + }, + stats: { + total: 5, + byCategory: { + authentication: 2, + driver: 2, + session: 2, + file: 1, + }, + }, + }, + { + id: '4kdvfoj2u9', + parent: 'kemjvigx5w', + name: 'notepad.exe', + data: { + 'process.name': ['notepad.exe'], + '@timestamp': ['2022-05-23T11:03:17.511Z'], + 'process.parent.entity_id': ['kemjvigx5w'], + 'process.Ext.ancestry': ['kemjvigx5w', 'p1dbx787xe'], + 'process.entity_id': ['4kdvfoj2u9'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'lfgmzmj99j', + parent: 'kemjvigx5w', + name: 'powershell.exe', + data: { + 'process.name': ['powershell.exe'], + '@timestamp': ['2022-05-23T11:03:19.511Z'], + 'process.parent.entity_id': ['kemjvigx5w'], + 'process.Ext.ancestry': ['kemjvigx5w', 'p1dbx787xe'], + 'process.entity_id': ['lfgmzmj99j'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: '9xtipos591', + parent: '72221vhp1s', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:01.511Z'], + 'process.parent.entity_id': ['72221vhp1s'], + 'process.Ext.ancestry': ['72221vhp1s', 'p1dbx787xe'], + 'process.entity_id': ['9xtipos591'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'spk93ihzue', + parent: '72221vhp1s', + name: 'explorer.exe', + data: { + 'process.name': ['explorer.exe'], + '@timestamp': ['2022-05-23T11:03:02.511Z'], + 'process.parent.entity_id': ['72221vhp1s'], + 'process.Ext.ancestry': ['72221vhp1s', 'p1dbx787xe'], + 'process.entity_id': ['spk93ihzue'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'eefk3v3tg0', + parent: '72221vhp1s', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:05.511Z'], + 'process.parent.entity_id': ['72221vhp1s'], + 'process.Ext.ancestry': ['72221vhp1s', 'p1dbx787xe'], + 'process.entity_id': ['eefk3v3tg0'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'z7t7ai4mcl', + parent: 'o055ylvrqg', + name: 'powershell.exe', + data: { + 'process.name': ['powershell.exe'], + '@timestamp': ['2022-05-23T11:03:10.511Z'], + 'process.parent.entity_id': ['o055ylvrqg'], + 'process.Ext.ancestry': ['o055ylvrqg', 'p1dbx787xe'], + 'process.entity_id': ['z7t7ai4mcl'], + }, + stats: { + total: 1, + byCategory: { + network: 1, + }, + }, + }, + { + id: '33k536gv9n', + parent: 'o055ylvrqg', + name: 'mimikatz.exe', + data: { + 'process.name': ['mimikatz.exe'], + '@timestamp': ['2022-05-23T11:03:11.511Z'], + 'process.parent.entity_id': ['o055ylvrqg'], + 'process.Ext.ancestry': ['o055ylvrqg', 'p1dbx787xe'], + 'process.entity_id': ['33k536gv9n'], + }, + stats: { + total: 5, + byCategory: { + file: 4, + network: 1, + }, + }, + }, + { + id: 'tbxjoicr50', + parent: 'kemjvigx5w', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:15.511Z'], + 'process.parent.entity_id': ['kemjvigx5w'], + 'process.Ext.ancestry': ['kemjvigx5w', 'p1dbx787xe'], + 'process.entity_id': ['tbxjoicr50'], + }, + stats: { + total: 5, + byCategory: { + authentication: 2, + driver: 2, + session: 2, + file: 1, + }, + }, + }, + { + id: '4kdvfoj2u9', + parent: 'kemjvigx5w', + name: 'notepad.exe', + data: { + 'process.name': ['notepad.exe'], + '@timestamp': ['2022-05-23T11:03:17.511Z'], + 'process.parent.entity_id': ['kemjvigx5w'], + 'process.Ext.ancestry': ['kemjvigx5w', 'p1dbx787xe'], + 'process.entity_id': ['4kdvfoj2u9'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'lfgmzmj99j', + parent: 'kemjvigx5w', + name: 'powershell.exe', + data: { + 'process.name': ['powershell.exe'], + '@timestamp': ['2022-05-23T11:03:19.511Z'], + 'process.parent.entity_id': ['kemjvigx5w'], + 'process.Ext.ancestry': ['kemjvigx5w', 'p1dbx787xe'], + 'process.entity_id': ['lfgmzmj99j'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'yx3us23cnz', + parent: 'spk93ihzue', + name: 'notepad.exe', + data: { + 'process.name': ['notepad.exe'], + '@timestamp': ['2022-05-23T11:03:03.511Z'], + 'process.parent.entity_id': ['spk93ihzue'], + 'process.Ext.ancestry': ['72221vhp1s', 'spk93ihzue'], + 'process.entity_id': ['yx3us23cnz'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: '5n8xwwsm4y', + parent: 'spk93ihzue', + name: 'explorer.exe', + data: { + 'process.name': ['explorer.exe'], + '@timestamp': ['2022-05-23T11:03:04.511Z'], + 'process.parent.entity_id': ['spk93ihzue'], + 'process.Ext.ancestry': ['72221vhp1s', 'spk93ihzue'], + 'process.entity_id': ['5n8xwwsm4y'], + }, + stats: { + total: 5, + byCategory: { + authentication: 2, + registry: 2, + session: 2, + driver: 1, + }, + }, + }, + { + id: 'yx2sbsktcr', + parent: 'eefk3v3tg0', + name: 'explorer.exe', + data: { + 'process.name': ['explorer.exe'], + '@timestamp': ['2022-05-23T11:03:06.511Z'], + 'process.parent.entity_id': ['eefk3v3tg0'], + 'process.Ext.ancestry': ['72221vhp1s', 'eefk3v3tg0'], + 'process.entity_id': ['yx2sbsktcr'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'm6681zvabo', + parent: 'eefk3v3tg0', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:07.511Z'], + 'process.parent.entity_id': ['eefk3v3tg0'], + 'process.Ext.ancestry': ['72221vhp1s', 'eefk3v3tg0'], + 'process.entity_id': ['m6681zvabo'], + }, + stats: { + total: 5, + byCategory: { + authentication: 3, + session: 3, + network: 1, + registry: 1, + }, + }, + }, + { + id: '57ega9sp2m', + parent: 'eefk3v3tg0', + name: 'explorer.exe', + data: { + 'process.name': ['explorer.exe'], + '@timestamp': ['2022-05-23T11:03:08.511Z'], + 'process.parent.entity_id': ['eefk3v3tg0'], + 'process.Ext.ancestry': ['72221vhp1s', 'eefk3v3tg0'], + 'process.entity_id': ['57ega9sp2m'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: '2q9pvz4liy', + parent: '33k536gv9n', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:12.511Z'], + 'process.parent.entity_id': ['33k536gv9n'], + 'process.Ext.ancestry': ['33k536gv9n', 'o055ylvrqg'], + 'process.entity_id': ['2q9pvz4liy'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'hpzss8vcwd', + parent: '33k536gv9n', + name: 'explorer.exe', + data: { + 'process.name': ['explorer.exe'], + '@timestamp': ['2022-05-23T11:03:13.511Z'], + 'process.parent.entity_id': ['33k536gv9n'], + 'process.Ext.ancestry': ['33k536gv9n', 'o055ylvrqg'], + 'process.entity_id': ['hpzss8vcwd'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: 'lar8v50hvc', + parent: 'tbxjoicr50', + name: 'notepad.exe', + data: { + 'process.name': ['notepad.exe'], + '@timestamp': ['2022-05-23T11:03:16.511Z'], + 'process.parent.entity_id': ['tbxjoicr50'], + 'process.Ext.ancestry': ['kemjvigx5w', 'tbxjoicr50'], + 'process.entity_id': ['lar8v50hvc'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, + { + id: '22olnc3pqr', + parent: '4kdvfoj2u9', + name: 'iexlorer.exe', + data: { + 'process.name': ['iexlorer.exe'], + '@timestamp': ['2022-05-23T11:03:18.511Z'], + 'process.parent.entity_id': ['4kdvfoj2u9'], + 'process.Ext.ancestry': ['4kdvfoj2u9', 'kemjvigx5w'], + 'process.entity_id': ['22olnc3pqr'], + }, + stats: { + total: 0, + byCategory: {}, + }, + }, +]; + +export const stubFetchTimelineEvents = () => { + return { + took: 2, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 32, + relation: 'eq', + }, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'f-KV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + args: ['"C:\\mimikatz.exe"', '--fd0'], + Ext: { + ancestry: [], + }, + group_leader: { + name: 'fake leader', + pid: 780, + entity_id: '3po060bfqd', + }, + session_leader: { + name: 'fake session', + pid: 124, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 12, + entity_id: '3po060bfqd', + }, + name: 'mimikatz.exe', + pid: 644, + working_directory: '/home/h351qq3jzg/', + entity_id: '3po060bfqd', + executable: 'C:\\mimikatz.exe', + hash: { + md5: '848277f3-026f-4e55-8447-51d2e2c3d16a', + }, + }, + '@timestamp': 1653303774511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 0, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'bffdfe87-d26a-4314-9c76-06ab1f8638e8', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'gOKV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['3po060bfqd'], + }, + parent: { + pid: 644, + entity_id: '3po060bfqd', + }, + group_leader: { + name: 'fake leader', + pid: 997, + entity_id: '3po060bfqd', + }, + pid: 974, + working_directory: '/home/73xgdrsqsl/', + entity_id: 'up4f1f87wr', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--u2n'], + session_leader: { + name: 'fake session', + pid: 753, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 199, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: 'd5d6d125-d2a6-459c-be47-64d226a9fe86', + }, + }, + '@timestamp': 1653303775511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 1, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '157d0d30-aa65-427b-9ddc-66096328d172', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'i-KV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['up4f1f87wr', '3po060bfqd'], + }, + parent: { + pid: 974, + entity_id: 'up4f1f87wr', + }, + group_leader: { + name: 'fake leader', + pid: 253, + entity_id: '3po060bfqd', + }, + pid: 2241, + working_directory: '/home/x6tczq6ibn/', + entity_id: 'j0mdzksneq', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--dl7'], + session_leader: { + name: 'fake session', + pid: 539, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 820, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: '065eb1f0-d255-41d3-a126-701c266a6ef1', + }, + }, + '@timestamp': 1653303776511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 12, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '2f348502-f7fe-4688-8d13-46c1fbfe5c16', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'jOKV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['up4f1f87wr', '3po060bfqd'], + }, + parent: { + pid: 1591, + entity_id: 'up4f1f87wr', + }, + group_leader: { + name: 'fake leader', + pid: 852, + entity_id: '3po060bfqd', + }, + pid: 3696, + working_directory: '/home/s4c3y8wrzj/', + entity_id: 'j0mdzksneq', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--0my'], + session_leader: { + name: 'fake session', + pid: 948, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 570, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: '56c70b52-e25e-46f1-9d1c-407c355daeca', + }, + }, + '@timestamp': 1654124641511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 13, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'b87ef54d-283c-4029-a0f4-d0b1a7d3179c', + category: ['process'], + type: ['end'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'jeKV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['j0mdzksneq', 'up4f1f87wr'], + }, + parent: { + pid: 2241, + entity_id: 'j0mdzksneq', + }, + group_leader: { + name: 'fake leader', + pid: 304, + entity_id: '3po060bfqd', + }, + pid: 3678, + working_directory: '/home/943lu62rw5/', + entity_id: 'p1dbx787xe', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--4p9'], + session_leader: { + name: 'fake session', + pid: 429, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 894, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: 'd2e5bdc0-cb03-4a16-b89d-3cc47cdc4463', + }, + }, + '@timestamp': 1653303777511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 14, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'aaa5cae6-a00c-4081-ae14-b10393e455c1', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'j-KV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['p1dbx787xe', 'j0mdzksneq'], + }, + parent: { + pid: 4555, + entity_id: 'p1dbx787xe', + }, + group_leader: { + name: 'fake leader', + pid: 111, + entity_id: '3po060bfqd', + }, + pid: 1618, + working_directory: '/home/mwler9rdyj/', + entity_id: '72221vhp1s', + executable: 'C:\\lsass.exe', + args: ['"C:\\lsass.exe"', '--448'], + session_leader: { + name: 'fake session', + pid: 259, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 301, + entity_id: '3po060bfqd', + }, + name: 'lsass.exe', + hash: { + md5: '9bdc057b-5b79-4a1f-b96d-5027d1089977', + }, + }, + '@timestamp': 1653303780511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 16, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'e5915b76-0f6b-461b-945c-d9cf9291fa1d', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'kOKV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['72221vhp1s', 'p1dbx787xe'], + }, + parent: { + pid: 4708, + entity_id: '72221vhp1s', + }, + group_leader: { + name: 'fake leader', + pid: 315, + entity_id: '3po060bfqd', + }, + pid: 1156, + working_directory: '/home/ohyrjwqpx7/', + entity_id: '9xtipos591', + executable: 'C:\\iexlorer.exe', + args: ['"C:\\iexlorer.exe"', '--f1s'], + session_leader: { + name: 'fake session', + pid: 390, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 268, + entity_id: '3po060bfqd', + }, + name: 'iexlorer.exe', + hash: { + md5: 'b4211e8c-9e0c-4957-9dac-3e2086f53a36', + }, + }, + '@timestamp': 1653303781511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 17, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'aa384768-c473-472e-94bb-d839100e6918', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'keKV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['72221vhp1s', 'p1dbx787xe'], + }, + parent: { + pid: 3608, + entity_id: '72221vhp1s', + }, + group_leader: { + name: 'fake leader', + pid: 477, + entity_id: '3po060bfqd', + }, + pid: 2464, + working_directory: '/home/bpq5j3fgzq/', + entity_id: 'spk93ihzue', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--9ii'], + session_leader: { + name: 'fake session', + pid: 247, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 493, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: 'b25a9cc0-bf7f-4880-8b16-7da5f545bf22', + }, + }, + '@timestamp': 1653303782511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 18, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'bb7d3758-2274-439d-8fd4-64f7cd3d5411', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'kuKV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['spk93ihzue', '72221vhp1s'], + }, + parent: { + pid: 4087, + entity_id: 'spk93ihzue', + }, + group_leader: { + name: 'fake leader', + pid: 788, + entity_id: '3po060bfqd', + }, + pid: 632, + working_directory: '/home/0qiwrpn3zj/', + entity_id: 'yx3us23cnz', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--1et'], + session_leader: { + name: 'fake session', + pid: 374, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 353, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: '134dbb94-ee50-47bb-8b5b-73b9e21670cb', + }, + }, + '@timestamp': 1653303783511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 19, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'c4962257-1f6c-4098-b13b-9089ea1d0af5', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'k-KV8IABsphBWHn-nT4H', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['spk93ihzue', '72221vhp1s'], + }, + parent: { + pid: 429, + entity_id: 'spk93ihzue', + }, + group_leader: { + name: 'fake leader', + pid: 675, + entity_id: '3po060bfqd', + }, + pid: 2390, + working_directory: '/home/4vo90awsyg/', + entity_id: '5n8xwwsm4y', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--3e2'], + session_leader: { + name: 'fake session', + pid: 543, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 738, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: 'bef94719-099d-4afa-b601-36d0c6c638a0', + }, + }, + '@timestamp': 1653303784511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 20, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '403f5fa4-4a04-4a5d-a2cf-d325d1bdde06', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'nuKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['72221vhp1s', 'p1dbx787xe'], + }, + parent: { + pid: 2493, + entity_id: '72221vhp1s', + }, + group_leader: { + name: 'fake leader', + pid: 130, + entity_id: '3po060bfqd', + }, + pid: 214, + working_directory: '/home/ou0t92en75/', + entity_id: 'eefk3v3tg0', + executable: 'C:\\iexlorer.exe', + args: ['"C:\\iexlorer.exe"', '--vqg'], + session_leader: { + name: 'fake session', + pid: 428, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 564, + entity_id: '3po060bfqd', + }, + name: 'iexlorer.exe', + hash: { + md5: 'a7382338-84c3-47e5-9f47-a737922b2ffa', + }, + }, + '@timestamp': 1653303785511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 31, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '183adae5-e6c7-456c-aca4-bb0675b106c9', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'n-KV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['72221vhp1s', 'p1dbx787xe'], + }, + parent: { + pid: 797, + entity_id: '72221vhp1s', + }, + group_leader: { + name: 'fake leader', + pid: 946, + entity_id: '3po060bfqd', + }, + pid: 9, + working_directory: '/home/6bfscwho95/', + entity_id: 'eefk3v3tg0', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--ai0'], + session_leader: { + name: 'fake session', + pid: 158, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 453, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: 'ae236075-a0d8-4735-897a-94e2b6db13f5', + }, + }, + '@timestamp': 1653579812511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 32, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '48d65fd5-9fc2-4250-8d7a-0719f8c38a93', + category: ['process'], + type: ['end'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'oOKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['eefk3v3tg0', '72221vhp1s'], + }, + parent: { + pid: 458, + entity_id: 'eefk3v3tg0', + }, + group_leader: { + name: 'fake leader', + pid: 728, + entity_id: '3po060bfqd', + }, + pid: 4069, + working_directory: '/home/tqoq4hemri/', + entity_id: 'yx2sbsktcr', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--tru'], + session_leader: { + name: 'fake session', + pid: 903, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 407, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: '67057556-8c28-47ef-856a-54d1fee52bab', + }, + }, + '@timestamp': 1653303786511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 33, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'de276c4b-42b9-4e68-869c-ae39afc27976', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'oeKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['eefk3v3tg0', '72221vhp1s'], + }, + parent: { + pid: 4334, + entity_id: 'eefk3v3tg0', + }, + group_leader: { + name: 'fake leader', + pid: 134, + entity_id: '3po060bfqd', + }, + pid: 82, + working_directory: '/home/40f18m4zzo/', + entity_id: 'm6681zvabo', + executable: 'C:\\iexlorer.exe', + args: ['"C:\\iexlorer.exe"', '--kym'], + session_leader: { + name: 'fake session', + pid: 290, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 141, + entity_id: '3po060bfqd', + }, + name: 'iexlorer.exe', + hash: { + md5: '51281a5c-7eba-42f9-b563-61f0eac87e33', + }, + }, + '@timestamp': 1653303787511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 34, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'd4c6c047-5c70-47bc-9f50-7b143e580caa', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'rOKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['eefk3v3tg0', '72221vhp1s'], + }, + parent: { + pid: 1876, + entity_id: 'eefk3v3tg0', + }, + group_leader: { + name: 'fake leader', + pid: 43, + entity_id: '3po060bfqd', + }, + pid: 1168, + working_directory: '/home/rdk4jzxof4/', + entity_id: '57ega9sp2m', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--ham'], + session_leader: { + name: 'fake session', + pid: 497, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 280, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: '1b12825c-5b27-4172-8a96-c518df53ab26', + }, + }, + '@timestamp': 1653303788511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 45, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'eae70e75-e955-436e-94c9-034a485a4f61', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'reKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['p1dbx787xe', 'j0mdzksneq'], + }, + parent: { + pid: 255, + entity_id: 'p1dbx787xe', + }, + group_leader: { + name: 'fake leader', + pid: 81, + entity_id: '3po060bfqd', + }, + pid: 3316, + working_directory: '/home/5zyydjd6ns/', + entity_id: 'o055ylvrqg', + executable: 'C:\\powershell.exe', + args: ['"C:\\powershell.exe"', '--622'], + session_leader: { + name: 'fake session', + pid: 239, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 805, + entity_id: '3po060bfqd', + }, + name: 'powershell.exe', + hash: { + md5: 'cd25ea58-396f-48f7-a1c3-4d3bafd8348c', + }, + }, + '@timestamp': 1653303789511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 46, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '7b41a46d-5d21-4f40-bd5b-dba8465a4c6c', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'ruKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['o055ylvrqg', 'p1dbx787xe'], + }, + parent: { + pid: 4612, + entity_id: 'o055ylvrqg', + }, + group_leader: { + name: 'fake leader', + pid: 155, + entity_id: '3po060bfqd', + }, + pid: 2147, + working_directory: '/home/4nsogy8ycy/', + entity_id: 'z7t7ai4mcl', + executable: 'C:\\powershell.exe', + args: ['"C:\\powershell.exe"', '--p7b'], + session_leader: { + name: 'fake session', + pid: 159, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 887, + entity_id: '3po060bfqd', + }, + name: 'powershell.exe', + hash: { + md5: '113f90f8-895c-4497-b69e-1ca043011b95', + }, + }, + '@timestamp': 1653303790511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 47, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'c0717b62-387a-4802-b164-45918c790902', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'r-KV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['o055ylvrqg', 'p1dbx787xe'], + }, + parent: { + pid: 4431, + entity_id: 'o055ylvrqg', + }, + group_leader: { + name: 'fake leader', + pid: 11, + entity_id: '3po060bfqd', + }, + pid: 3288, + working_directory: '/home/ji0q863pka/', + entity_id: 'z7t7ai4mcl', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--1j6'], + session_leader: { + name: 'fake session', + pid: 419, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 209, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: 'f1e77430-6074-4bfb-986a-97725bf589c2', + }, + }, + '@timestamp': 1653897359511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 48, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'f48f37fc-a0b0-4c01-b3d8-a9664e46c13a', + category: ['process'], + type: ['end'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'uuKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['o055ylvrqg', 'p1dbx787xe'], + }, + parent: { + pid: 3885, + entity_id: 'o055ylvrqg', + }, + group_leader: { + name: 'fake leader', + pid: 409, + entity_id: '3po060bfqd', + }, + pid: 5, + working_directory: '/home/5q6hfguqxr/', + entity_id: '33k536gv9n', + executable: 'C:\\mimikatz.exe', + args: ['"C:\\mimikatz.exe"', '--3nf'], + session_leader: { + name: 'fake session', + pid: 821, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 597, + entity_id: '3po060bfqd', + }, + name: 'mimikatz.exe', + hash: { + md5: 'a756c0dc-d018-4720-a55d-23080f19afeb', + }, + }, + '@timestamp': 1653303791511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 59, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '95024994-bbf0-4076-8ee0-8d9349d2e2b5', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'xeKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['33k536gv9n', 'o055ylvrqg'], + }, + parent: { + pid: 794, + entity_id: '33k536gv9n', + }, + group_leader: { + name: 'fake leader', + pid: 49, + entity_id: '3po060bfqd', + }, + pid: 1583, + working_directory: '/home/pwkog8tum3/', + entity_id: '2q9pvz4liy', + executable: 'C:\\iexlorer.exe', + args: ['"C:\\iexlorer.exe"', '--8jk'], + session_leader: { + name: 'fake session', + pid: 131, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 707, + entity_id: '3po060bfqd', + }, + name: 'iexlorer.exe', + hash: { + md5: 'ecac2bc0-69d9-4fc0-9af3-8a3256b73f0c', + }, + }, + '@timestamp': 1653303792511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 70, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'c514d95e-aeda-4ac6-8fb0-60e0baec183f', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'xuKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['33k536gv9n', 'o055ylvrqg'], + }, + parent: { + pid: 3084, + entity_id: '33k536gv9n', + }, + group_leader: { + name: 'fake leader', + pid: 47, + entity_id: '3po060bfqd', + }, + pid: 2788, + working_directory: '/home/tw5d4v2dhd/', + entity_id: 'hpzss8vcwd', + executable: 'C:\\explorer.exe', + args: ['"C:\\explorer.exe"', '--wyx'], + session_leader: { + name: 'fake session', + pid: 583, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 713, + entity_id: '3po060bfqd', + }, + name: 'explorer.exe', + hash: { + md5: '2fe80980-3b06-4a51-9cdd-e011a73b7480', + }, + }, + '@timestamp': 1653303793511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 71, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '4b4e7a1d-4b19-47bc-a98c-660ce632d91f', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'x-KV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['33k536gv9n', 'o055ylvrqg'], + }, + parent: { + pid: 2693, + entity_id: '33k536gv9n', + }, + group_leader: { + name: 'fake leader', + pid: 553, + entity_id: '3po060bfqd', + }, + pid: 4777, + working_directory: '/home/t59qkz7ecu/', + entity_id: 'hpzss8vcwd', + executable: 'C:\\mimikatz.exe', + args: ['"C:\\mimikatz.exe"', '--mol'], + session_leader: { + name: 'fake session', + pid: 255, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 354, + entity_id: '3po060bfqd', + }, + name: 'mimikatz.exe', + hash: { + md5: '8fc89958-89e5-43f6-959f-97f9115771d0', + }, + }, + '@timestamp': 1654235413511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 72, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'd3fa03e3-201b-4d65-8faa-0008736d92b6', + category: ['process'], + type: ['end'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'yOKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['p1dbx787xe', 'j0mdzksneq'], + }, + parent: { + pid: 2555, + entity_id: 'p1dbx787xe', + }, + group_leader: { + name: 'fake leader', + pid: 838, + entity_id: '3po060bfqd', + }, + pid: 288, + working_directory: '/home/l6y0ju3us6/', + entity_id: 'kemjvigx5w', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--xci'], + session_leader: { + name: 'fake session', + pid: 350, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 366, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: 'db204c43-247a-4a61-8af5-7f332434173e', + }, + }, + '@timestamp': 1653303794511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 73, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '3b7d327a-a560-4d8a-9b47-749ae83366e8', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'yeKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['p1dbx787xe', 'j0mdzksneq'], + }, + parent: { + pid: 3103, + entity_id: 'p1dbx787xe', + }, + group_leader: { + name: 'fake leader', + pid: 210, + entity_id: '3po060bfqd', + }, + pid: 4492, + working_directory: '/home/5s499lqduj/', + entity_id: 'kemjvigx5w', + executable: 'C:\\mimikatz.exe', + args: ['"C:\\mimikatz.exe"', '--hxr'], + session_leader: { + name: 'fake session', + pid: 71, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 505, + entity_id: '3po060bfqd', + }, + name: 'mimikatz.exe', + hash: { + md5: '2d270895-8718-48c6-acfb-5d6ddfb10bb7', + }, + }, + '@timestamp': 1653571764511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 74, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'b363ec64-a5d7-4589-84e6-9055fd39d122', + category: ['process'], + type: ['end'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: 'yuKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['kemjvigx5w', 'p1dbx787xe'], + }, + parent: { + pid: 3678, + entity_id: 'kemjvigx5w', + }, + group_leader: { + name: 'fake leader', + pid: 451, + entity_id: '3po060bfqd', + }, + pid: 2275, + working_directory: '/home/capo07rwi3/', + entity_id: 'tbxjoicr50', + executable: 'C:\\iexlorer.exe', + args: ['"C:\\iexlorer.exe"', '--lc1'], + session_leader: { + name: 'fake session', + pid: 891, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 595, + entity_id: '3po060bfqd', + }, + name: 'iexlorer.exe', + hash: { + md5: '5dd468cb-85d0-47be-b84a-6bc3dca6767c', + }, + }, + '@timestamp': 1653303795511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 75, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'cafd1046-57fe-4ac1-aadf-0810977d50d6', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: '1eKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['tbxjoicr50', 'kemjvigx5w'], + }, + parent: { + pid: 3523, + entity_id: 'tbxjoicr50', + }, + group_leader: { + name: 'fake leader', + pid: 989, + entity_id: '3po060bfqd', + }, + pid: 507, + working_directory: '/home/wrlu2t6z99/', + entity_id: 'lar8v50hvc', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--ceo'], + session_leader: { + name: 'fake session', + pid: 349, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 411, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: '71ba9887-26ab-467a-900e-28178da95d1b', + }, + }, + '@timestamp': 1653303796511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 86, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'a56a3209-3146-4186-880a-51ef223cc5e5', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: '1uKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['tbxjoicr50', 'kemjvigx5w'], + }, + parent: { + pid: 4126, + entity_id: 'tbxjoicr50', + }, + group_leader: { + name: 'fake leader', + pid: 389, + entity_id: '3po060bfqd', + }, + pid: 1700, + working_directory: '/home/k5k0zkznd4/', + entity_id: 'lar8v50hvc', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--24g'], + session_leader: { + name: 'fake session', + pid: 751, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 693, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: '3f9905cf-4b57-4a14-87d6-cdf24222a164', + }, + }, + '@timestamp': 1654251443511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 87, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '51d0ba93-78c5-4a60-b760-7b1f1c1a02e8', + category: ['process'], + type: ['end'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: '1-KV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['kemjvigx5w', 'p1dbx787xe'], + }, + parent: { + pid: 4339, + entity_id: 'kemjvigx5w', + }, + group_leader: { + name: 'fake leader', + pid: 592, + entity_id: '3po060bfqd', + }, + pid: 3149, + working_directory: '/home/gzb0sjtj79/', + entity_id: '4kdvfoj2u9', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--wlr'], + session_leader: { + name: 'fake session', + pid: 411, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 514, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: '9531cfdc-9634-4169-97b2-d6fcaedf946f', + }, + }, + '@timestamp': 1653303797511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 88, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'a2d38b84-adbe-4e40-9265-109cb0fb9374', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: '2OKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['4kdvfoj2u9', 'kemjvigx5w'], + }, + parent: { + pid: 4637, + entity_id: '4kdvfoj2u9', + }, + group_leader: { + name: 'fake leader', + pid: 312, + entity_id: '3po060bfqd', + }, + pid: 3741, + working_directory: '/home/xl5bdg92xa/', + entity_id: '22olnc3pqr', + executable: 'C:\\iexlorer.exe', + args: ['"C:\\iexlorer.exe"', '--7cj'], + session_leader: { + name: 'fake session', + pid: 549, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 325, + entity_id: '3po060bfqd', + }, + name: 'iexlorer.exe', + hash: { + md5: 'd8754665-d660-4188-bfb4-22f7837b9dd8', + }, + }, + '@timestamp': 1653303798511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 89, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '89782b1c-e288-443c-96fb-858447ae2b4f', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: '2eKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['4kdvfoj2u9', 'kemjvigx5w'], + }, + parent: { + pid: 210, + entity_id: '4kdvfoj2u9', + }, + group_leader: { + name: 'fake leader', + pid: 173, + entity_id: '3po060bfqd', + }, + pid: 1497, + working_directory: '/home/uhaptao1wl/', + entity_id: '22olnc3pqr', + executable: 'C:\\notepad.exe', + args: ['"C:\\notepad.exe"', '--232'], + session_leader: { + name: 'fake session', + pid: 694, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 671, + entity_id: '3po060bfqd', + }, + name: 'notepad.exe', + hash: { + md5: '98d7edc5-42be-4dc5-b1c5-3321c485a1db', + }, + }, + '@timestamp': 1654059315511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 90, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '47a5bf53-99a9-4982-af65-1fcd3abb1d40', + category: ['process'], + type: ['end'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: '2uKV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['kemjvigx5w', 'p1dbx787xe'], + }, + parent: { + pid: 2107, + entity_id: 'kemjvigx5w', + }, + group_leader: { + name: 'fake leader', + pid: 261, + entity_id: '3po060bfqd', + }, + pid: 3715, + working_directory: '/home/f5583b60xu/', + entity_id: 'lfgmzmj99j', + executable: 'C:\\powershell.exe', + args: ['"C:\\powershell.exe"', '--10x'], + session_leader: { + name: 'fake session', + pid: 57, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 430, + entity_id: '3po060bfqd', + }, + name: 'powershell.exe', + hash: { + md5: '91a99525-0bc3-42d8-89f7-1fe5cecb8334', + }, + }, + '@timestamp': 1653303799511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 91, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: 'be3621bb-efc9-4eee-b8de-6d07bace543c', + category: ['process'], + type: ['start'], + }, + }, + }, + { + _index: '.ds-logs-endpoint.events.process-default-2022.05.23-000001', + _id: '2-KV8IABsphBWHn-nT4I', + _score: 0, + _source: { + process: { + Ext: { + ancestry: ['kemjvigx5w', 'p1dbx787xe'], + }, + parent: { + pid: 3754, + entity_id: 'kemjvigx5w', + }, + group_leader: { + name: 'fake leader', + pid: 22, + entity_id: '3po060bfqd', + }, + pid: 4895, + working_directory: '/home/de1ijqnt0h/', + entity_id: 'lfgmzmj99j', + executable: 'C:\\lsass.exe', + args: ['"C:\\lsass.exe"', '--4mb'], + session_leader: { + name: 'fake session', + pid: 808, + entity_id: '3po060bfqd', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + entry_leader: { + name: 'fake entry', + pid: 982, + entity_id: '3po060bfqd', + }, + name: 'lsass.exe', + hash: { + md5: 'eb3ba411-4b8f-402a-bc70-5758b21ad32c', + }, + }, + '@timestamp': 1654060564511, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 92, + ingested: '2022-05-23T11:02:53Z', + kind: 'event', + id: '07dff76f-fb51-42ee-a0af-0f5e49105f8c', + category: ['process'], + type: ['end'], + }, + }, + }, + ], + }, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts index 3dc0fc4558fc5..a3e173098a19f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts @@ -21,6 +21,8 @@ export const TELEMETRY_CHANNEL_ENDPOINT_META = 'endpoint-metadata'; export const TELEMETRY_CHANNEL_DETECTION_ALERTS = 'alerts-detections'; +export const TELEMETRY_CHANNEL_TIMELINE = 'alerts-timeline'; + export const LIST_DETECTION_RULE_EXCEPTION = 'detection_rule_exception'; export const LIST_ENDPOINT_EXCEPTION = 'endpoint_exception'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index 2f1a5fbd5cc7d..8712a51b15069 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -8,6 +8,7 @@ import { Logger, CoreStart, + IScopedClusterClient, ElasticsearchClient, SavedObjectsClientContract, } from '@kbn/core/server'; @@ -37,8 +38,16 @@ import { trustedApplicationToTelemetryEntry, ruleExceptionListItemToTelemetryEvent, } from './helpers'; +import { Fetcher } from '../../endpoint/routes/resolver/tree/utils/fetch'; +import type { TreeOptions } from '../../endpoint/routes/resolver/tree/utils/fetch'; +import type { + ResolverNode, + SafeEndpointEvent, + ResolverSchema, +} from '../../../common/endpoint/types'; import type { TelemetryEvent, + EnhancedAlertEvent, ESLicense, ESClusterInfo, GetEndpointListResponse, @@ -134,6 +143,21 @@ export interface ITelemetryReceiver { }; fetchPrebuiltRuleAlerts(): Promise; + + fetchTimelineEndpointAlerts( + interval: number + ): Promise>>; + + buildProcessTree( + entityId: string, + resolverSchema: ResolverSchema, + startOfDay: string, + endOfDay: string + ): Promise; + + fetchTimelineEvents( + nodeIds: string[] + ): Promise>>; } export class TelemetryReceiver implements ITelemetryReceiver { @@ -146,6 +170,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { private kibanaIndex?: string; private alertsIndex?: string; private clusterInfo?: ESClusterInfo; + private processTreeFetcher?: Fetcher; private readonly maxRecords = 10_000 as const; constructor(logger: Logger) { @@ -168,6 +193,9 @@ export class TelemetryReceiver implements ITelemetryReceiver { this.soClient = core?.savedObjects.createInternalRepository() as unknown as SavedObjectsClientContract; this.clusterInfo = await this.fetchClusterInfo(); + + const elasticsearch = core?.elasticsearch.client as unknown as IScopedClusterClient; + this.processTreeFetcher = new Fetcher(elasticsearch); } public getClusterInfo(): ESClusterInfo | undefined { @@ -176,7 +204,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { public async fetchFleetAgents() { if (this.esClient === undefined || this.esClient === null) { - throw Error('elasticsearch client is unavailable: cannot retrieve fleet policy responses'); + throw Error('elasticsearch client is unavailable: cannot retrieve fleet agents'); } return this.agentClient?.listAgents({ @@ -430,7 +458,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { */ public async fetchDetectionRules() { if (this.esClient === undefined || this.esClient === null) { - throw Error('elasticsearch client is unavailable: cannot retrieve diagnostic alerts'); + throw Error('elasticsearch client is unavailable: cannot retrieve detection rules'); } const query: SearchRequest = { @@ -509,7 +537,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { */ public async fetchPrebuiltRuleAlerts() { if (this.esClient === undefined || this.esClient === null) { - throw Error('elasticsearch client is unavailable: cannot retrieve detection rule alerts'); + throw Error('elasticsearch client is unavailable: cannot retrieve pre-built rule alerts'); } const query: SearchRequest = { @@ -630,6 +658,131 @@ export class TelemetryReceiver implements ITelemetryReceiver { return telemetryEvents; } + public async fetchTimelineEndpointAlerts(interval: number) { + if (this.esClient === undefined || this.esClient === null) { + throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation'); + } + + const query: SearchRequest = { + expand_wildcards: ['open' as const, 'hidden' as const], + index: `${this.alertsIndex}*`, + ignore_unavailable: true, + size: 100, + body: { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match_phrase: { + 'event.module': 'endpoint', + }, + }, + ], + }, + }, + { + bool: { + should: [ + { + match_phrase: { + 'kibana.alert.rule.parameters.immutable': 'true', + }, + }, + ], + }, + }, + { + range: { + '@timestamp': { + gte: `now-${interval}h`, + lte: 'now', + }, + }, + }, + ], + }, + }, + }, + }; + + return this.esClient.search(query); + } + + public async buildProcessTree( + entityId: string, + resolverSchema: ResolverSchema, + startOfDay: string, + endOfDay: string + ): Promise { + if (this.processTreeFetcher === undefined || this.processTreeFetcher === null) { + throw Error( + 'resolver tree builder is unavailable: cannot build encoded endpoint event graph' + ); + } + + const request: TreeOptions = { + ancestors: 200, + descendants: 500, + timeRange: { + from: startOfDay, + to: endOfDay, + }, + schema: resolverSchema, + nodes: [entityId], + indexPatterns: [`${this.alertsIndex}*`, 'logs-*'], + descendantLevels: 20, + }; + + return this.processTreeFetcher.tree(request, true); + } + + public async fetchTimelineEvents(nodeIds: string[]) { + if (this.esClient === undefined || this.esClient === null) { + throw Error('elasticsearch client is unavailable: cannot retrieve timeline endpoint events'); + } + + const query: SearchRequest = { + expand_wildcards: ['open' as const, 'hidden' as const], + index: [`${this.alertsIndex}*`, 'logs-*'], + ignore_unavailable: true, + size: 100, + body: { + _source: { + include: [ + '@timestamp', + 'process', + 'event', + 'file', + 'network', + 'dns', + 'kibana.rule.alert.uuid', + ], + }, + query: { + bool: { + filter: [ + { + terms: { + 'process.entity_id': nodeIds, + }, + }, + { + term: { + 'event.category': 'process', + }, + }, + ], + }, + }, + }, + }; + + return this.esClient.search(query); + } + public async fetchClusterInfo(): Promise { if (this.esClient === undefined || this.esClient === null) { throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation'); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts index 9cfb6883e9533..b0141ca7a5fb1 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts @@ -11,6 +11,7 @@ import { createTelemetryEndpointTaskConfig } from './endpoint'; import { createTelemetrySecurityListTaskConfig } from './security_lists'; import { createTelemetryDetectionRuleListsTaskConfig } from './detection_rule'; import { createTelemetryPrebuiltRuleAlertsTaskConfig } from './prebuilt_rule_alerts'; +import { createTelemetryTimelineTaskConfig } from './timelines'; import { MAX_SECURITY_LIST_TELEMETRY_BATCH, MAX_ENDPOINT_TELEMETRY_BATCH, @@ -25,5 +26,6 @@ export function createTelemetryTaskConfigs(): SecurityTelemetryTaskConfig[] { createTelemetrySecurityListTaskConfig(MAX_ENDPOINT_TELEMETRY_BATCH), createTelemetryDetectionRuleListsTaskConfig(MAX_DETECTION_RULE_TELEMETRY_BATCH), createTelemetryPrebuiltRuleAlertsTaskConfig(MAX_DETECTION_ALERTS_BATCH), + createTelemetryTimelineTaskConfig(), ]; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts new file mode 100644 index 0000000000000..36f6d3caa6d46 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { createTelemetryTimelineTaskConfig } from './timelines'; +import { createMockTelemetryEventsSender, createMockTelemetryReceiver } from '../__mocks__'; + +describe('timeline telemetry task test', () => { + let logger: ReturnType; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + }); + + test('timeline telemetry task should be correctly set up', async () => { + const testTaskExecutionPeriod = { + last: undefined, + current: new Date().toISOString(), + }; + const mockTelemetryEventsSender = createMockTelemetryEventsSender(); + const mockTelemetryReceiver = createMockTelemetryReceiver(); + const telemetryTelemetryTaskConfig = createTelemetryTimelineTaskConfig(); + + await telemetryTelemetryTaskConfig.runTask( + 'test-timeline-task-id', + logger, + mockTelemetryReceiver, + mockTelemetryEventsSender, + testTaskExecutionPeriod + ); + + expect(mockTelemetryReceiver.buildProcessTree).toHaveBeenCalled(); + expect(mockTelemetryReceiver.fetchTimelineEvents).toHaveBeenCalled(); + expect(mockTelemetryReceiver.fetchTimelineEndpointAlerts).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts new file mode 100644 index 0000000000000..c85d933172786 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { Logger } from '@kbn/core/server'; +import { SafeEndpointEvent } from '../../../../common/endpoint/types'; +import { ITelemetryEventsSender } from '../sender'; +import { ITelemetryReceiver } from '../receiver'; +import type { TaskExecutionPeriod } from '../task'; +import type { + ESClusterInfo, + ESLicense, + TimelineTelemetryTemplate, + TimelineTelemetryEvent, +} from '../types'; +import { TELEMETRY_CHANNEL_TIMELINE } from '../constants'; +import { resolverEntity } from '../../../endpoint/routes/resolver/entity/utils/build_resolver_entity'; + +export function createTelemetryTimelineTaskConfig() { + return { + type: 'security:telemetry-timelines', + title: 'Security Solution Timeline telemetry', + interval: '3h', + timeout: '10m', + version: '1.0.0', + runTask: async ( + taskId: string, + logger: Logger, + receiver: ITelemetryReceiver, + sender: ITelemetryEventsSender, + taskExecutionPeriod: TaskExecutionPeriod + ) => { + let counter = 0; + + logger.debug(`Running task: ${taskId}`); + + const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ + receiver.fetchClusterInfo(), + receiver.fetchLicenseInfo(), + ]); + + const clusterInfo = + clusterInfoPromise.status === 'fulfilled' + ? clusterInfoPromise.value + : ({} as ESClusterInfo); + + const licenseInfo = + licenseInfoPromise.status === 'fulfilled' + ? licenseInfoPromise.value + : ({} as ESLicense | undefined); + + const now = moment(); + const startOfDay = now.startOf('day').toISOString(); + const endOfDay = now.endOf('day').toISOString(); + + const baseDocument = { + version: clusterInfo.version?.number, + cluster_name: clusterInfo.cluster_name, + cluster_uuid: clusterInfo.cluster_uuid, + license_uuid: licenseInfo?.uid, + }; + + // Fetch EP Alerts + + const endpointAlerts = await receiver.fetchTimelineEndpointAlerts(3); + + // No EP Alerts -> Nothing to do + + if ( + endpointAlerts.hits.hits?.length === 0 || + endpointAlerts.hits.hits?.length === undefined + ) { + logger.debug('no endpoint alerts received. exiting telemetry task.'); + return counter; + } + + // Build process tree for each EP Alert recieved + + for (const alert of endpointAlerts.hits.hits) { + const eventId = alert._source ? alert._source['event.id'] : 'unknown'; + const alertUUID = alert._source ? alert._source['kibana.alert.uuid'] : 'unknown'; + + const entities = resolverEntity([alert]); + + // Build Tree + + const tree = await receiver.buildProcessTree( + entities[0].id, + entities[0].schema, + startOfDay, + endOfDay + ); + + const nodeIds = [] as string[]; + for (const node of tree) { + const nodeId = node?.id.toString(); + nodeIds.push(nodeId); + } + + // Fetch event lineage + + const timelineEvents = await receiver.fetchTimelineEvents(nodeIds); + + const eventsStore = new Map(); + for (const event of timelineEvents.hits.hits) { + const doc = event._source; + + if (doc !== null && doc !== undefined) { + const entityId = doc?.process?.entity_id?.toString(); + if (entityId !== null && entityId !== undefined) eventsStore.set(entityId, doc); + } + } + + // Create telemetry record + + const telemetryTimeline: TimelineTelemetryEvent[] = []; + for (const node of tree) { + const id = node.id.toString(); + const event = eventsStore.get(id); + + const timelineTelemetryEvent: TimelineTelemetryEvent = { + ...node, + event, + }; + + telemetryTimeline.push(timelineTelemetryEvent); + } + + const record: TimelineTelemetryTemplate = { + '@timestamp': moment().toISOString(), + ...baseDocument, + alert_id: alertUUID, + event_id: eventId, + timeline: telemetryTimeline, + }; + + sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [record]); + counter += 1; + } + + logger.debug(`sent ${counter} timelines. exiting telemetry task.`); + return counter; + }, + }; +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index d70a011ea85aa..7c22bed299fc3 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -6,6 +6,7 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { AlertEvent, ResolverNode, SafeResolverEvent } from '../../../common/endpoint/types'; type BaseSearchTypes = string | number | boolean | object; export type SearchTypes = BaseSearchTypes | BaseSearchTypes[] | undefined; @@ -357,3 +358,20 @@ export interface RuleSearchResult { params: DetectionRuleParms; }; } + +// EP Timeline telemetry + +export type EnhancedAlertEvent = AlertEvent & { 'event.id': string; 'kibana.alert.uuid': string }; + +export type TimelineTelemetryEvent = ResolverNode & { event: SafeResolverEvent | undefined }; + +export interface TimelineTelemetryTemplate { + '@timestamp': string; + cluster_uuid: string; + cluster_name: string; + version: string | undefined; + license_uuid: string | undefined; + alert_id: string | undefined; + event_id: string; + timeline: TimelineTelemetryEvent[]; +} From e2190233d8c13321bdf57bd6818be2f9adedc0f5 Mon Sep 17 00:00:00 2001 From: DeDe Morton Date: Mon, 23 May 2022 10:22:21 -0700 Subject: [PATCH 22/71] Discourage use of elastic user in add data tutorials (#132084) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../instructions/auditbeat_instructions.ts | 20 +++++++++++++++---- .../instructions/cloud_instructions.ts | 5 ++++- .../instructions/filebeat_instructions.ts | 20 +++++++++++++++---- .../instructions/functionbeat_instructions.ts | 10 ++++++++-- .../instructions/heartbeat_instructions.ts | 20 +++++++++++++++---- .../instructions/metricbeat_instructions.ts | 20 +++++++++++++++---- .../instructions/winlogbeat_instructions.ts | 5 ++++- .../translations/translations/fr-FR.json | 19 ------------------ .../translations/translations/ja-JP.json | 19 ------------------ .../translations/translations/zh-CN.json | 19 ------------------ 10 files changed, 80 insertions(+), 77 deletions(-) diff --git a/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts index 3968aff312380..2e1d0c57dc680 100644 --- a/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts @@ -193,13 +193,16 @@ export const createAuditbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.auditbeat}/securing-auditbeat.html', }, } ), @@ -231,13 +234,16 @@ export const createAuditbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.auditbeat}/securing-auditbeat.html', }, } ), @@ -269,13 +275,16 @@ export const createAuditbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.auditbeat}/securing-auditbeat.html', }, } ), @@ -310,13 +319,16 @@ export const createAuditbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.auditbeat}/securing-auditbeat.html', }, } ), diff --git a/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts b/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts index 6d547b2a1d40d..3562e4fb8b8ce 100644 --- a/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts @@ -15,7 +15,10 @@ export const cloudPasswordAndResetLink = i18n.translate( 'Where {passwordTemplate} is the password of the `elastic` user.' + `\\{#config.cloud.profileUrl\\} Forgot the password? [Reset in Elastic Cloud](\\{config.cloud.baseUrl\\}\\{config.cloud.deploymentUrl\\}/security). - \\{/config.cloud.profileUrl\\}`, + \\{/config.cloud.profileUrl\\}` + + '\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files.', values: { passwordTemplate: '``' }, } ); diff --git a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts index 89445510f2b3d..247343f57b070 100644 --- a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts @@ -183,13 +183,16 @@ export const createFilebeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.filebeat}/securing-filebeat.html', }, } ), @@ -221,13 +224,16 @@ export const createFilebeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.filebeat}/securing-filebeat.html', }, } ), @@ -259,13 +265,16 @@ export const createFilebeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.filebeat}/securing-filebeat.html', }, } ), @@ -300,13 +309,16 @@ export const createFilebeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.filebeat}/securing-filebeat.html', }, } ), diff --git a/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts index 60d6fa5cb813b..c6bb2694b2f2a 100644 --- a/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts @@ -152,13 +152,16 @@ Kibana index pattern. It is normally safe to omit this command.', defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.functionbeat}/securing-functionbeat.html', }, } ), @@ -196,13 +199,16 @@ Kibana index pattern. It is normally safe to omit this command.', defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.functionbeat}/securing-functionbeat.html', }, } ), diff --git a/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts index 5cbd1641bf09a..1fbea1ddf58a1 100644 --- a/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts @@ -174,13 +174,16 @@ export const createHeartbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.heartbeat}/securing-heartbeat.html', }, } ), @@ -212,13 +215,16 @@ export const createHeartbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.heartbeat}/securing-heartbeat.html', }, } ), @@ -250,13 +256,16 @@ export const createHeartbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ +> **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ +authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.heartbeat}/securing-heartbeat.html', }, } ), @@ -291,13 +300,16 @@ export const createHeartbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.heartbeat}/securing-heartbeat.html', }, } ), diff --git a/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts index 02cd53dddbc1f..04e487b00baad 100644 --- a/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts @@ -186,13 +186,16 @@ export const createMetricbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.metricbeat}/securing-metricbeat.html', }, } ), @@ -224,13 +227,16 @@ export const createMetricbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.metricbeat}/securing-metricbeat.html', }, } ), @@ -262,13 +268,16 @@ export const createMetricbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.metricbeat}/securing-metricbeat.html', }, } ), @@ -303,13 +312,16 @@ export const createMetricbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.metricbeat}/securing-metricbeat.html', }, } ), diff --git a/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts index 2c33285899f65..c06994e060d17 100644 --- a/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts @@ -99,13 +99,16 @@ export const createWinlogbeatInstructions = (context: TutorialContext) => { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of \ Elasticsearch, and {kibanaUrlTemplate} is the URL of Kibana. To [configure SSL]({configureSslUrl}) with the \ - default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.', + default certificate generated by Elasticsearch, add its fingerprint in {esCertFingerprintTemplate}.\n\n\ + > **_Important:_** Do not use the built-in `elastic` user to secure clients in a production environment. Instead set up \ + authorized users or API keys, and do not expose passwords in configuration files. [Learn more]({linkUrl}).', values: { passwordTemplate: '``', esUrlTemplate: '``', kibanaUrlTemplate: '``', configureSslUrl: SSL_DOC_URL, esCertFingerprintTemplate: '``', + linkUrl: '{config.docs.beats.winlogbeat}/securing-winlogbeat.html', }, } ), diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index b62a957cfa927..56d2f64de0a65 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -3821,16 +3821,12 @@ "home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle": "Modifier la configuration", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion pour Elastic Cloud :", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle": "Modifier la configuration", - "home.tutorials.common.auditbeatInstructions.config.debTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.auditbeatInstructions.config.debTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.auditbeatInstructions.config.debTitle": "Modifier la configuration", - "home.tutorials.common.auditbeatInstructions.config.osxTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.auditbeatInstructions.config.osxTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.auditbeatInstructions.config.osxTitle": "Modifier la configuration", - "home.tutorials.common.auditbeatInstructions.config.rpmTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.auditbeatInstructions.config.rpmTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.auditbeatInstructions.config.rpmTitle": "Modifier la configuration", - "home.tutorials.common.auditbeatInstructions.config.windowsTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.auditbeatInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.auditbeatInstructions.config.windowsTitle": "Modifier la configuration", "home.tutorials.common.auditbeatInstructions.install.debTextPost": "Vous cherchez les packages 32 bits ? Consultez la [page de téléchargement]({linkUrl}).", @@ -3879,16 +3875,12 @@ "home.tutorials.common.filebeatEnableInstructions.windowsTextPost": "Modifiez les paramètres dans le fichier ''modules.d/{moduleName}.yml''.", "home.tutorials.common.filebeatEnableInstructions.windowsTextPre": "Dans le dossier {path}, exécutez la commande suivante :", "home.tutorials.common.filebeatEnableInstructions.windowsTitle": "Activer et configurer le module {moduleName}", - "home.tutorials.common.filebeatInstructions.config.debTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.filebeatInstructions.config.debTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.filebeatInstructions.config.debTitle": "Modifier la configuration", - "home.tutorials.common.filebeatInstructions.config.osxTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.filebeatInstructions.config.osxTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.filebeatInstructions.config.osxTitle": "Modifier la configuration", - "home.tutorials.common.filebeatInstructions.config.rpmTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.filebeatInstructions.config.rpmTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.filebeatInstructions.config.rpmTitle": "Modifier la configuration", - "home.tutorials.common.filebeatInstructions.config.windowsTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.filebeatInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.filebeatInstructions.config.windowsTitle": "Modifier la configuration", "home.tutorials.common.filebeatInstructions.install.debTextPost": "Vous cherchez les packages 32 bits ? Consultez la [page de téléchargement]({linkUrl}).", @@ -3929,10 +3921,8 @@ "home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTitle": "Configurer le groupe de logs Cloudwatch", "home.tutorials.common.functionbeatEnableOnPremInstructionsOSXLinux.textPre": "Modifiez les paramètres dans le fichier ''functionbeat.yml''.", "home.tutorials.common.functionbeatEnableOnPremInstructionsWindows.textPre": "Modifiez les paramètres dans le fichier {path}.", - "home.tutorials.common.functionbeatInstructions.config.osxTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.functionbeatInstructions.config.osxTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.functionbeatInstructions.config.osxTitle": "Configurer le cluster Elastic", - "home.tutorials.common.functionbeatInstructions.config.windowsTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.functionbeatInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.functionbeatInstructions.config.windowsTitle": "Modifier la configuration", "home.tutorials.common.functionbeatInstructions.deploy.osxTextPre": "Ceci permet d'installer Functionbeat en tant que fonction Lambda. La commande ''setup'' vérifie la configuration d'Elasticsearch et charge le modèle d'indexation Kibana. L'omission de cette commande est normalement sans risque.", @@ -3973,16 +3963,12 @@ "home.tutorials.common.heartbeatEnableOnPremInstructions.osxTextPre": "Modifiez le paramètre ''heartbeat.monitors'' dans le fichier ''heartbeat.yml''.", "home.tutorials.common.heartbeatEnableOnPremInstructions.rpmTextPre": "Modifiez le paramètre ''heartbeat.monitors'' dans le fichier ''heartbeat.yml''.", "home.tutorials.common.heartbeatEnableOnPremInstructions.windowsTextPre": "Modifiez le paramètre ''heartbeat.monitors'' dans le fichier ''heartbeat.yml''.", - "home.tutorials.common.heartbeatInstructions.config.debTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.heartbeatInstructions.config.debTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.heartbeatInstructions.config.debTitle": "Modifier la configuration", - "home.tutorials.common.heartbeatInstructions.config.osxTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.heartbeatInstructions.config.osxTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.heartbeatInstructions.config.osxTitle": "Modifier la configuration", - "home.tutorials.common.heartbeatInstructions.config.rpmTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.heartbeatInstructions.config.rpmTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.heartbeatInstructions.config.rpmTitle": "Modifier la configuration", - "home.tutorials.common.heartbeatInstructions.config.windowsTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.heartbeatInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.heartbeatInstructions.config.windowsTitle": "Modifier la configuration", "home.tutorials.common.heartbeatInstructions.install.debTextPost": "Vous cherchez les packages 32 bits ? Consultez la [page de téléchargement]({link}).", @@ -4036,16 +4022,12 @@ "home.tutorials.common.metricbeatEnableInstructions.windowsTextPost": "Modifiez les paramètres dans le fichier ''modules.d/{moduleName}.yml''.", "home.tutorials.common.metricbeatEnableInstructions.windowsTextPre": "Dans le dossier {path}, exécutez la commande suivante :", "home.tutorials.common.metricbeatEnableInstructions.windowsTitle": "Activer et configurer le module {moduleName}", - "home.tutorials.common.metricbeatInstructions.config.debTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.metricbeatInstructions.config.debTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.metricbeatInstructions.config.debTitle": "Modifier la configuration", - "home.tutorials.common.metricbeatInstructions.config.osxTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.metricbeatInstructions.config.osxTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.metricbeatInstructions.config.osxTitle": "Modifier la configuration", - "home.tutorials.common.metricbeatInstructions.config.rpmTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.metricbeatInstructions.config.rpmTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.metricbeatInstructions.config.rpmTitle": "Modifier la configuration", - "home.tutorials.common.metricbeatInstructions.config.windowsTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.metricbeatInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.metricbeatInstructions.config.windowsTitle": "Modifier la configuration", "home.tutorials.common.metricbeatInstructions.install.debTextPost": "Vous cherchez les packages 32 bits ? Consultez la [page de téléchargement]({link}).", @@ -4080,7 +4062,6 @@ "home.tutorials.common.winlogbeat.premInstructions.gettingStarted.title": "Commencer", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion pour Elastic Cloud :", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle": "Modifier la configuration", - "home.tutorials.common.winlogbeatInstructions.config.windowsTextPostMarkdown": "Où {passwordTemplate} est le mot de passe de l'utilisateur \"elastic\", {esUrlTemplate} est l'URL d'Elasticsearch et {kibanaUrlTemplate} est l'URL de Kibana. Pour [configurer le SSL]({configureSslUrl}) avec le certificat par défaut généré par Elasticsearch, ajoutez son empreinte digitale dans {esCertFingerprintTemplate}.", "home.tutorials.common.winlogbeatInstructions.config.windowsTextPre": "Modifiez {path} afin de définir les informations de connexion :", "home.tutorials.common.winlogbeatInstructions.config.windowsTitle": "Modifier la configuration", "home.tutorials.common.winlogbeatInstructions.install.windowsTextPost": "Modifiez les paramètres sous \"output.elasticsearch\" dans le fichier {path} afin de pointer vers votre installation Elasticsearch.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fe7056a5e3ec1..91996cc8f39dd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3915,16 +3915,12 @@ "home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle": "構成を編集する", - "home.tutorials.common.auditbeatInstructions.config.debTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.auditbeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.auditbeatInstructions.config.osxTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.auditbeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.auditbeatInstructions.config.rpmTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.auditbeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.auditbeatInstructions.config.windowsTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.auditbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.auditbeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({linkUrl})をご覧ください。", @@ -3973,16 +3969,12 @@ "home.tutorials.common.filebeatEnableInstructions.windowsTextPost": "「modules.d/{moduleName}.yml」」ファイルで設定を変更します。", "home.tutorials.common.filebeatEnableInstructions.windowsTextPre": "「{path}」フォルダから次のファイルを実行します:", "home.tutorials.common.filebeatEnableInstructions.windowsTitle": "{moduleName} モジュールを有効にし構成します", - "home.tutorials.common.filebeatInstructions.config.debTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.filebeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.filebeatInstructions.config.osxTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.filebeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.filebeatInstructions.config.rpmTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.filebeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.filebeatInstructions.config.windowsTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.filebeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.filebeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({linkUrl})をご覧ください。", @@ -4023,10 +4015,8 @@ "home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTitle": "Cloudwatch ロググループの構成", "home.tutorials.common.functionbeatEnableOnPremInstructionsOSXLinux.textPre": "「functionbeat.yml」ファイルで設定を変更します。", "home.tutorials.common.functionbeatEnableOnPremInstructionsWindows.textPre": "{path} ファイルで設定を変更します。", - "home.tutorials.common.functionbeatInstructions.config.osxTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.functionbeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.functionbeatInstructions.config.osxTitle": "Elastic クラスターの構成", - "home.tutorials.common.functionbeatInstructions.config.windowsTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.functionbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.functionbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.functionbeatInstructions.deploy.osxTextPre": "これにより Functionbeat が Lambda 関数としてインストールされます「setup」コマンドで Elasticsearch の構成を確認し、Kibana インデックスパターンを読み込みます。通常このコマンドを省いても大丈夫です。", @@ -4067,16 +4057,12 @@ "home.tutorials.common.heartbeatEnableOnPremInstructions.osxTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatEnableOnPremInstructions.rpmTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatEnableOnPremInstructions.windowsTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", - "home.tutorials.common.heartbeatInstructions.config.debTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.heartbeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.heartbeatInstructions.config.osxTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.heartbeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.heartbeatInstructions.config.rpmTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.heartbeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.heartbeatInstructions.config.windowsTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.heartbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.heartbeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({link})をご覧ください。", @@ -4130,16 +4116,12 @@ "home.tutorials.common.metricbeatEnableInstructions.windowsTextPost": "「modules.d/{moduleName}.yml」」ファイルで設定を変更します。", "home.tutorials.common.metricbeatEnableInstructions.windowsTextPre": "「{path}」フォルダから次のファイルを実行します:", "home.tutorials.common.metricbeatEnableInstructions.windowsTitle": "{moduleName} モジュールを有効にし構成します", - "home.tutorials.common.metricbeatInstructions.config.debTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.metricbeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.metricbeatInstructions.config.osxTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.metricbeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.metricbeatInstructions.config.rpmTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.metricbeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.metricbeatInstructions.config.windowsTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.metricbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.metricbeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({link})をご覧ください。", @@ -4174,7 +4156,6 @@ "home.tutorials.common.winlogbeat.premInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle": "構成を編集する", - "home.tutorials.common.winlogbeatInstructions.config.windowsTextPostMarkdown": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。Elasticsearchで生成されたデフォルトの証明書を使用して[SSLを構成]({configureSslUrl})するには、{esCertFingerprintTemplate}でフィンガープリントを追加します。", "home.tutorials.common.winlogbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.winlogbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.winlogbeatInstructions.install.windowsTextPost": "{path} ファイルの「output.elasticsearch」を Elasticsearch のインストールに設定します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 990a113fcd9d6..3bc2a40181935 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3925,16 +3925,12 @@ "home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle": "编辑配置", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle": "编辑配置", - "home.tutorials.common.auditbeatInstructions.config.debTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.auditbeatInstructions.config.debTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.auditbeatInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.auditbeatInstructions.config.osxTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.auditbeatInstructions.config.osxTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.auditbeatInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.auditbeatInstructions.config.rpmTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.auditbeatInstructions.config.rpmTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.auditbeatInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.auditbeatInstructions.config.windowsTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.auditbeatInstructions.config.windowsTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.auditbeatInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.auditbeatInstructions.install.debTextPost": "寻找 32 位软件包?请参阅[下载页面]({linkUrl})。", @@ -3983,16 +3979,12 @@ "home.tutorials.common.filebeatEnableInstructions.windowsTextPost": "在 `modules.d/{moduleName}.yml` 文件中修改设置。", "home.tutorials.common.filebeatEnableInstructions.windowsTextPre": "从 {path} 文件夹中,运行:", "home.tutorials.common.filebeatEnableInstructions.windowsTitle": "启用和配置 {moduleName} 模块", - "home.tutorials.common.filebeatInstructions.config.debTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.filebeatInstructions.config.debTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.filebeatInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.filebeatInstructions.config.osxTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.filebeatInstructions.config.osxTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.filebeatInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.filebeatInstructions.config.rpmTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.filebeatInstructions.config.rpmTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.filebeatInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.filebeatInstructions.config.windowsTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.filebeatInstructions.config.windowsTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.filebeatInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.filebeatInstructions.install.debTextPost": "寻找 32 位软件包?请参阅[下载页面]({linkUrl})。", @@ -4033,10 +4025,8 @@ "home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTitle": "配置 Cloudwatch 日志组", "home.tutorials.common.functionbeatEnableOnPremInstructionsOSXLinux.textPre": "在 `functionbeat.yml` 文件中修改设置。", "home.tutorials.common.functionbeatEnableOnPremInstructionsWindows.textPre": "在 {path} 文件中修改设置。", - "home.tutorials.common.functionbeatInstructions.config.osxTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.functionbeatInstructions.config.osxTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.functionbeatInstructions.config.osxTitle": "配置 Elastic 集群", - "home.tutorials.common.functionbeatInstructions.config.windowsTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.functionbeatInstructions.config.windowsTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.functionbeatInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.functionbeatInstructions.deploy.osxTextPre": "这会将 Functionbeat 安装为 Lambda 函数。`setup` 命令检查 Elasticsearch 配置并加载 Kibana 索引模式。通常可省略此命令。", @@ -4077,16 +4067,12 @@ "home.tutorials.common.heartbeatEnableOnPremInstructions.osxTextPre": "在 `heartbeat.yml` 文件中编辑 `heartbeat.monitors` 设置。", "home.tutorials.common.heartbeatEnableOnPremInstructions.rpmTextPre": "在 `heartbeat.yml` 文件中编辑 `heartbeat.monitors` 设置。", "home.tutorials.common.heartbeatEnableOnPremInstructions.windowsTextPre": "在 `heartbeat.yml` 文件中编辑 `heartbeat.monitors` 设置。", - "home.tutorials.common.heartbeatInstructions.config.debTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.heartbeatInstructions.config.debTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.heartbeatInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.heartbeatInstructions.config.osxTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.heartbeatInstructions.config.osxTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.heartbeatInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.heartbeatInstructions.config.rpmTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.heartbeatInstructions.config.rpmTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.heartbeatInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.heartbeatInstructions.config.windowsTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.heartbeatInstructions.config.windowsTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.heartbeatInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.heartbeatInstructions.install.debTextPost": "寻找 32 位软件包?请参阅[下载页面]({link})。", @@ -4140,16 +4126,12 @@ "home.tutorials.common.metricbeatEnableInstructions.windowsTextPost": "在 `modules.d/{moduleName}.yml` 文件中修改设置。", "home.tutorials.common.metricbeatEnableInstructions.windowsTextPre": "从 {path} 文件夹中,运行:", "home.tutorials.common.metricbeatEnableInstructions.windowsTitle": "启用和配置 {moduleName} 模块", - "home.tutorials.common.metricbeatInstructions.config.debTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.metricbeatInstructions.config.debTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.metricbeatInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.metricbeatInstructions.config.osxTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.metricbeatInstructions.config.osxTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.metricbeatInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.metricbeatInstructions.config.rpmTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.metricbeatInstructions.config.rpmTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.metricbeatInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.metricbeatInstructions.config.windowsTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.metricbeatInstructions.config.windowsTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.metricbeatInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.metricbeatInstructions.install.debTextPost": "寻找 32 位软件包?请参阅[下载页面]({link})。", @@ -4184,7 +4166,6 @@ "home.tutorials.common.winlogbeat.premInstructions.gettingStarted.title": "入门", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle": "编辑配置", - "home.tutorials.common.winlogbeatInstructions.config.windowsTextPostMarkdown": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。要使用 Elasticsearch 生成的默认证书 [配置 SSL]({configureSslUrl}),请在 {esCertFingerprintTemplate} 中添加其指纹。", "home.tutorials.common.winlogbeatInstructions.config.windowsTextPre": "修改 {path} 以设置连接信息:", "home.tutorials.common.winlogbeatInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.winlogbeatInstructions.install.windowsTextPost": "在 {path} 文件中修改 `output.elasticsearch` 下的设置以指向您的 Elasticsearch 安装。", From 2d1ac53300da885c945fae563b7607a5bcb0e667 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Mon, 23 May 2022 12:43:56 -0500 Subject: [PATCH 23/71] [easy][shared-ux] Fix typos in RedirectAppLinks (#132563) --- packages/shared-ux/link/redirect_app/README.mdx | 8 ++++---- packages/shared-ux/link/redirect_app/src/index.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/shared-ux/link/redirect_app/README.mdx b/packages/shared-ux/link/redirect_app/README.mdx index 8e2eada760ea2..07d4f75ab764d 100644 --- a/packages/shared-ux/link/redirect_app/README.mdx +++ b/packages/shared-ux/link/redirect_app/README.mdx @@ -70,17 +70,17 @@ This is the component is likely the most useful to solutions in Kibana. It assu ```tsx import { RedirectAppLinks } from '@kbn/shared-ux-links-redirect-app'; - { ... }}> + { ... }}> . Go to another-app . - + {/* OR */} - + . Go to another-app . - + ``` \ No newline at end of file diff --git a/packages/shared-ux/link/redirect_app/src/index.tsx b/packages/shared-ux/link/redirect_app/src/index.tsx index 5efb99cc48664..09317ebab59f7 100644 --- a/packages/shared-ux/link/redirect_app/src/index.tsx +++ b/packages/shared-ux/link/redirect_app/src/index.tsx @@ -7,7 +7,7 @@ */ export { RedirectAppLinks as RedirectAppLinksContainer } from './redirect_app_links'; -export { RedirectAppLinks as RedirectAppLinksComponent } from './redirect_app_links'; +export { RedirectAppLinks as RedirectAppLinksComponent } from './redirect_app_links.component'; export { RedirectAppLinksKibanaProvider, RedirectAppLinksProvider } from './services'; import React, { FC } from 'react'; From 5ee756b7ace06bcd1da37eb81d8b55b598da4d99 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 23 May 2022 11:51:06 -0600 Subject: [PATCH 24/71] [Maps] use EmsSpriteSheet type from @elastic/ems-client (#132715) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ems_vector_tile_layer.tsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx b/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx index 6f8bc3470d792..679629b838835 100644 --- a/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx @@ -6,7 +6,7 @@ */ import type { Map as MbMap, LayerSpecification, StyleSpecification } from '@kbn/mapbox-gl'; -import { TMSService } from '@elastic/ems-client'; +import { type EmsSpriteSheet, TMSService } from '@elastic/ems-client'; import { i18n } from '@kbn/i18n'; import _ from 'lodash'; // @ts-expect-error @@ -30,20 +30,6 @@ interface SourceRequestMeta { tileLayerId: string; } -// TODO remove once ems_client exports EmsSpriteSheet and EmsSprite type -interface EmsSprite { - height: number; - pixelRatio: number; - sdf?: boolean; - width: number; - x: number; - y: number; -} - -export interface EmsSpriteSheet { - [spriteName: string]: EmsSprite; -} - interface SourceRequestData { spriteSheetImageData?: ImageData; vectorStyleSheet?: StyleSpecification; From f540c5e39232c69408c0af8557e264329804e9bb Mon Sep 17 00:00:00 2001 From: Kristof C Date: Mon, 23 May 2022 12:56:15 -0500 Subject: [PATCH 25/71] [Security Solution] [Detection & Response] 131827 Update Detections Response view with pagination and opening numbers in timeline (#131828) * Fix alert colour pallete & alerts chart header size * Add pagination and navigation to timeline capability * fix translation name conflict * Rename hook file to snake case to match elastic formatting * Change name scheme oof navigateToTimeline to OpenInTimeline & remove styled components Co-authored-by: Kristof-Pierre Cummings Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../table/use_action_cell_data_provider.ts | 2 +- .../alerts_by_status/alerts_by_status.tsx | 13 +- .../cases_by_status/cases_by_status.tsx | 10 +- .../cases_table/cases_table.test.tsx | 2 +- .../cases_table/cases_table.tsx | 2 +- .../hooks/use_navigate_to_timeline.tsx | 76 +++++++ .../host_alerts_table.test.tsx | 59 ++++-- .../host_alerts_table/host_alerts_table.tsx | 97 +++++---- .../host_alerts_table/mock_data.ts | 4 + .../use_host_alerts_items.test.ts | 16 ++ .../use_host_alerts_items.ts | 200 +++++++++++------- .../rule_alerts_table.test.tsx | 2 +- .../rule_alerts_table/rule_alerts_table.tsx | 18 +- .../detection_response/translations.ts | 18 +- .../user_alerts_table/mock_data.ts | 4 + .../use_user_alerts_items.test.ts | 16 ++ .../use_user_alerts_items.ts | 199 ++++++++++------- .../user_alerts_table.test.tsx | 59 ++++-- .../user_alerts_table/user_alerts_table.tsx | 95 +++++---- .../components/detection_response/util.tsx | 39 ---- .../components/detection_response/utils.tsx | 23 +- x-pack/plugins/timelines/common/index.ts | 1 + 22 files changed, 617 insertions(+), 338 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx delete mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/util.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts index 15a117817b627..1dded682f54ba 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts @@ -42,7 +42,7 @@ export interface UseActionCellDataProvider { values: string[] | null | undefined; } -const getDataProvider = (field: string, id: string, value: string): DataProvider => ({ +export const getDataProvider = (field: string, id: string, value: string): DataProvider => ({ and: [], enabled: true, id: escapeDataProviderId(id), diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index f7f3cc9a81f7f..a3f49732267aa 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -49,11 +49,6 @@ const StyledLegendFlexItem = styled(EuiFlexItem)` padding-top: 45px; `; -// To Do remove this styled component once togglequery is updated: #131405 -const StyledEuiPanel = styled(EuiPanel)` - height: fit-content; -`; - interface AlertsByStatusProps { signalIndexName: string | null; } @@ -124,10 +119,7 @@ export const AlertsByStatus = ({ signalIndexName }: AlertsByStatusProps) => { return ( <> - + {loading && ( { } inspectMultiple toggleStatus={toggleStatus} @@ -212,7 +205,7 @@ export const AlertsByStatus = ({ signalIndexName }: AlertsByStatusProps) => { )} - + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx index 67ed0d43c830c..cc4bfa5e26703 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_by_status/cases_by_status.tsx @@ -12,7 +12,7 @@ import styled from 'styled-components'; import { FormattedNumber } from '@kbn/i18n-react'; import numeral from '@elastic/numeral'; import { BarChart } from '../../../../common/components/charts/barchart'; -import { LastUpdatedAt } from '../util'; +import { LastUpdatedAt } from '../utils'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { HeaderSection } from '../../../../common/components/header_section'; import { @@ -112,10 +112,6 @@ const Wrapper = styled.div` width: 100%; `; -const StyledEuiPanel = styled(EuiPanel)` - height: 258px; -`; - const CasesByStatusComponent: React.FC = () => { const { toggleStatus, setToggleStatus } = useQueryToggle(CASES_BY_STATUS_ID); const { getAppUrl, navigateTo } = useNavigation(); @@ -155,7 +151,7 @@ const CasesByStatusComponent: React.FC = () => { ); return ( - + { )} - + ); }; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.test.tsx index 4250c20059094..8f9872374e01c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.test.tsx @@ -76,7 +76,7 @@ describe('CasesTable', () => { mockUseCaseItemsReturn({ isLoading: false }); const { getByText } = renderComponent(); - expect(getByText('Updated now')).toBeInTheDocument(); + expect(getByText(/Updated/)).toBeInTheDocument(); }); it('should render the table columns', () => { diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx index 80d2495f57ab5..838dc8a1fa2de 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx @@ -27,7 +27,7 @@ import { CaseDetailsLink } from '../../../../common/components/links'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { useNavigation, NavigateTo, GetAppUrl } from '../../../../common/lib/kibana'; import * as i18n from '../translations'; -import { LastUpdatedAt } from '../util'; +import { LastUpdatedAt } from '../utils'; import { StatusBadge } from './status_badge'; import { CaseItem, useCaseItems } from './use_case_items'; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx new file mode 100644 index 0000000000000..417ec82be002a --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useDispatch } from 'react-redux'; + +import { getDataProvider } from '../../../../common/components/event_details/table/use_action_cell_data_provider'; +import { sourcererActions } from '../../../../common/store/sourcerer'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { DataProvider, TimelineId, TimelineType } from '../../../../../common/types/timeline'; +import { useCreateTimeline } from '../../../../timelines/components/timeline/properties/use_create_timeline'; +import { updateProviders } from '../../../../timelines/store/timeline/actions'; + +export const useNavigateToTimeline = () => { + const dispatch = useDispatch(); + + const clearTimeline = useCreateTimeline({ + timelineId: TimelineId.active, + timelineType: TimelineType.default, + }); + + const navigateToTimeline = (dataProvider: DataProvider) => { + // Reset the current timeline + clearTimeline(); + // Update the timeline's providers to match the current prevalence field query + dispatch( + updateProviders({ + id: TimelineId.active, + providers: [dataProvider], + }) + ); + // Only show detection alerts + // (This is required so the timeline event count matches the prevalence count) + dispatch( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.timeline, + selectedDataViewId: 'security-solution-default', + selectedPatterns: ['.alerts-security.alerts-default'], + }) + ); + }; + + const openHostInTimeline = ({ hostName, severity }: { hostName: string; severity?: string }) => { + const dataProvider = getDataProvider('host.name', '', hostName); + + if (severity) { + dataProvider.and.push(getDataProvider('kibana.alert.severity', '', severity)); + } + + navigateToTimeline(dataProvider); + }; + + const openUserInTimeline = ({ userName, severity }: { userName: string; severity?: string }) => { + const dataProvider = getDataProvider('user.name', '', userName); + + if (severity) { + dataProvider.and.push(getDataProvider('kibana.alert.severity', '', severity)); + } + navigateToTimeline(dataProvider); + }; + + const openRuleInTimeline = (ruleName: string) => { + const dataProvider = getDataProvider('kibana.alert.rule.name', '', ruleName); + + navigateToTimeline(dataProvider); + }; + + return { + openHostInTimeline, + openRuleInTimeline, + openUserInTimeline, + }; +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx index a89055a72df6f..5172db743404c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import { parsedVulnerableHostsAlertsResult } from './mock_data'; @@ -30,6 +30,11 @@ const defaultUseHostAlertsItemsReturn: UseHostAlertsItemsReturn = { items: [], isLoading: false, updatedAt: Date.now(), + pagination: { + currentPage: 0, + pageCount: 0, + setPage: () => null, + }, }; const mockUseHostAlertsItems = jest.fn(() => defaultUseHostAlertsItemsReturn); const mockUseHostAlertsItemsReturn = (overrides: Partial) => { @@ -47,34 +52,33 @@ const renderComponent = () => ); -// FLAKY: https://github.com/elastic/kibana/issues/131611 -describe.skip('HostAlertsTable', () => { +describe('HostAlertsTable', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should render empty table', () => { - const { getByText, getByTestId } = renderComponent(); + const { getByText, queryByTestId } = renderComponent(); - expect(getByTestId('severityHostAlertsPanel')).toBeInTheDocument(); + expect(queryByTestId('severityHostAlertsPanel')).toBeInTheDocument(); + expect(queryByTestId('hostTablePaginator')).not.toBeInTheDocument(); expect(getByText('No alerts to display')).toBeInTheDocument(); - expect(getByTestId('severityHostAlertsButton')).toBeInTheDocument(); }); it('should render a loading table', () => { mockUseHostAlertsItemsReturn({ isLoading: true }); - const { getByText, getByTestId } = renderComponent(); + const { getByText, queryByTestId } = renderComponent(); expect(getByText('Updating...')).toBeInTheDocument(); - expect(getByTestId('severityHostAlertsButton')).toBeInTheDocument(); - expect(getByTestId('severityHostAlertsTable')).toHaveClass('euiBasicTable-loading'); + expect(queryByTestId('severityHostAlertsTable')).toHaveClass('euiBasicTable-loading'); + expect(queryByTestId('hostTablePaginator')).not.toBeInTheDocument(); }); it('should render the updated at subtitle', () => { mockUseHostAlertsItemsReturn({ isLoading: false }); const { getByText } = renderComponent(); - expect(getByText('Updated now')).toBeInTheDocument(); + expect(getByText(/Updated/)).toBeInTheDocument(); }); it('should render the table columns', () => { @@ -92,13 +96,32 @@ describe.skip('HostAlertsTable', () => { it('should render the table items', () => { mockUseHostAlertsItemsReturn({ items: [parsedVulnerableHostsAlertsResult[0]] }); - const { getByTestId } = renderComponent(); - - expect(getByTestId('hostSeverityAlertsTable-hostName')).toHaveTextContent('Host-342m5gl1g2'); - expect(getByTestId('hostSeverityAlertsTable-totalAlerts')).toHaveTextContent('100'); - expect(getByTestId('hostSeverityAlertsTable-critical')).toHaveTextContent('5'); - expect(getByTestId('hostSeverityAlertsTable-high')).toHaveTextContent('50'); - expect(getByTestId('hostSeverityAlertsTable-medium')).toHaveTextContent('5'); - expect(getByTestId('hostSeverityAlertsTable-low')).toHaveTextContent('40'); + const { queryByTestId } = renderComponent(); + + expect(queryByTestId('hostSeverityAlertsTable-hostName')).toHaveTextContent('Host-342m5gl1g2'); + expect(queryByTestId('hostSeverityAlertsTable-totalAlerts')).toHaveTextContent('100'); + expect(queryByTestId('hostSeverityAlertsTable-critical')).toHaveTextContent('5'); + expect(queryByTestId('hostSeverityAlertsTable-high')).toHaveTextContent('50'); + expect(queryByTestId('hostSeverityAlertsTable-medium')).toHaveTextContent('5'); + expect(queryByTestId('hostSeverityAlertsTable-low')).toHaveTextContent('40'); + expect(queryByTestId('hostTablePaginator')).not.toBeInTheDocument(); + }); + + it('should render the paginator if more than 4 results', () => { + const mockSetPage = jest.fn(); + + mockUseHostAlertsItemsReturn({ + pagination: { + currentPage: 1, + pageCount: 3, + setPage: mockSetPage, + }, + }); + const { queryByTestId, getByText } = renderComponent(); + const page3 = getByText('3'); + expect(queryByTestId('hostTablePaginator')).toBeInTheDocument(); + + fireEvent.click(page3); + expect(mockSetPage).toHaveBeenCalledWith(2); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx index 49ad4352cb586..f3151db3927db 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx @@ -5,72 +5,56 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; -import styled from 'styled-components'; +import React, { useMemo } from 'react'; import { EuiBasicTable, EuiBasicTableColumn, - EuiButton, EuiEmptyPrompt, EuiHealth, + EuiLink, EuiPanel, EuiSpacer, + EuiTablePagination, } from '@elastic/eui'; -import { SecurityPageName } from '../../../../app/types'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { HeaderSection } from '../../../../common/components/header_section'; import { HoverVisibilityContainer } from '../../../../common/components/hover_visibility_container'; import { BUTTON_CLASS as INPECT_BUTTON_CLASS } from '../../../../common/components/inspect'; import { HostDetailsLink } from '../../../../common/components/links'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; -import { useNavigation, NavigateTo, GetAppUrl } from '../../../../common/lib/kibana'; +import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; import * as i18n from '../translations'; -import { LastUpdatedAt, SEVERITY_COLOR } from '../util'; +import { ITEMS_PER_PAGE, LastUpdatedAt, SEVERITY_COLOR } from '../utils'; import { HostAlertsItem, useHostAlertsItems } from './use_host_alerts_items'; -type GetTableColumns = (params: { - getAppUrl: GetAppUrl; - navigateTo: NavigateTo; -}) => Array>; - interface HostAlertsTableProps { signalIndexName: string | null; } -const DETECTION_RESPONSE_HOST_SEVERITY_QUERY_ID = 'vulnerableHostsBySeverityQuery'; +type GetTableColumns = ( + handleClick: (params: { hostName: string; severity?: string }) => void +) => Array>; -// To Do remove this styled component once togglequery is updated: #131405 -const StyledEuiPanel = styled(EuiPanel)` - height: fit-content; -`; +const DETECTION_RESPONSE_HOST_SEVERITY_QUERY_ID = 'vulnerableHostsBySeverityQuery'; export const HostAlertsTable = React.memo(({ signalIndexName }: HostAlertsTableProps) => { - const { getAppUrl, navigateTo } = useNavigation(); + const { openHostInTimeline } = useNavigateToTimeline(); const { toggleStatus, setToggleStatus } = useQueryToggle( DETECTION_RESPONSE_HOST_SEVERITY_QUERY_ID ); - - const { items, isLoading, updatedAt } = useHostAlertsItems({ + const { items, isLoading, updatedAt, pagination } = useHostAlertsItems({ skip: !toggleStatus, queryId: DETECTION_RESPONSE_HOST_SEVERITY_QUERY_ID, signalIndexName, }); - const navigateToHosts = useCallback(() => { - navigateTo({ deepLinkId: SecurityPageName.hosts }); - }, [navigateTo]); - - const columns = useMemo( - () => getTableColumns({ getAppUrl, navigateTo }), - [getAppUrl, navigateTo] - ); + const columns = useMemo(() => getTableColumns(openHostInTimeline), [openHostInTimeline]); return ( - //
- + {toggleStatus && ( <> @@ -91,20 +76,26 @@ export const HostAlertsTable = React.memo(({ signalIndexName }: HostAlertsTableP } /> - - {i18n.VIEW_ALL_HOST_ALERTS} - + {pagination.pageCount > 1 && ( + + )} )} - + - //
); }); HostAlertsTable.displayName = 'HostAlertsTable'; -const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo }) => [ +const getTableColumns: GetTableColumns = (handleClick) => [ { field: 'hostName', name: i18n.HOST_ALERTS_HOSTNAME_COLUMN, @@ -117,41 +108,59 @@ const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo }) => [ field: 'totalAlerts', name: i18n.ALERTS_TEXT, 'data-test-subj': 'hostSeverityAlertsTable-totalAlerts', - render: (totalAlerts: number) => , + render: (totalAlerts: number, { hostName }) => ( + handleClick({ hostName })}> + + + ), }, { field: 'critical', name: i18n.STATUS_CRITICAL_LABEL, - render: (count: number) => ( + render: (count: number, { hostName }) => ( - + handleClick({ hostName, severity: 'critical' })} + > + + ), }, { field: 'high', name: i18n.STATUS_HIGH_LABEL, - render: (count: number) => ( + render: (count: number, { hostName }) => ( - + handleClick({ hostName, severity: 'high' })}> + + ), }, { field: 'medium', name: i18n.STATUS_MEDIUM_LABEL, - render: (count: number) => ( + render: (count: number, { hostName }) => ( - + handleClick({ hostName, severity: 'medium' })} + > + + ), }, { field: 'low', name: i18n.STATUS_LOW_LABEL, - render: (count: number) => ( + render: (count: number, { hostName }) => ( - + handleClick({ hostName, severity: 'low' })}> + + ), }, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/mock_data.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/mock_data.ts index d46d1b5401a9e..3c53951dd657c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/mock_data.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/mock_data.ts @@ -9,6 +9,9 @@ import { buildVulnerableHostAggregationQuery } from './use_host_alerts_items'; export const mockVulnerableHostsBySeverityResult = { aggregations: { + host_count: { + value: 4, + }, hostsBySeverity: { buckets: [ { @@ -119,6 +122,7 @@ export const mockQuery = () => ({ query: buildVulnerableHostAggregationQuery({ from: '2020-07-07T08:20:18.966Z', to: '2020-07-08T08:20:18.966Z', + currentPage: 0, }), indexName: 'signal-alerts', skip: false, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.test.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.test.ts index 42568a390f686..d38f0a4bfaa77 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.test.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.test.ts @@ -74,6 +74,10 @@ describe('useVulnerableHostsCounters', () => { items: [], isLoading: false, updatedAt: dateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 0, + }), }); expect(mockUseQueryAlerts).toBeCalledWith(mockQuery()); @@ -91,6 +95,10 @@ describe('useVulnerableHostsCounters', () => { items: parsedVulnerableHostsAlertsResult, isLoading: false, updatedAt: dateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 1, + }), }); }); @@ -110,6 +118,10 @@ describe('useVulnerableHostsCounters', () => { items: parsedVulnerableHostsAlertsResult, isLoading: false, updatedAt: newDateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 1, + }), }); }); @@ -122,6 +134,10 @@ describe('useVulnerableHostsCounters', () => { items: [], isLoading: false, updatedAt: dateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 0, + }), }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts index a01cd709b44f6..62fcc4580b253 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts @@ -11,8 +11,13 @@ import { useQueryInspector } from '../../../../common/components/page/manage_que import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { GenericBuckets } from '../../../../../common/search_strategy'; import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; +import { getPageCount, ITEMS_PER_PAGE } from '../utils'; const HOSTS_BY_SEVERITY_AGG = 'hostsBySeverity'; +const defaultPagination = { + pageCount: 0, + currentPage: 0, +}; interface TimeRange { from: string; @@ -24,6 +29,7 @@ export interface UseHostAlertsItemsProps { queryId: string; signalIndexName: string | null; } + export interface HostAlertsItem { hostName: string; totalAlerts: number; @@ -37,11 +43,18 @@ export type UseHostAlertsItems = (props: UseHostAlertsItemsProps) => { items: HostAlertsItem[]; isLoading: boolean; updatedAt: number; + pagination: Pagination & { setPage: (pageNumber: number) => void }; }; +interface Pagination { + pageCount: number; + currentPage: number; +} + export const useHostAlertsItems: UseHostAlertsItems = ({ skip, queryId, signalIndexName }) => { const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState([]); + const [paginationData, setPaginationData] = useState(defaultPagination); const { to, from, setQuery: setGlobalQuery, deleteQuery } = useGlobalTime(); @@ -53,20 +66,31 @@ export const useHostAlertsItems: UseHostAlertsItems = ({ skip, queryId, signalIn loading, refetch: refetchQuery, } = useQueryAlerts<{}, AlertCountersBySeverityAndHostAggregation>({ - query: buildVulnerableHostAggregationQuery({ from, to }), + query: buildVulnerableHostAggregationQuery({ + from, + to, + currentPage: paginationData.currentPage, + }), indexName: signalIndexName, skip, }); useEffect(() => { - setQuery(buildVulnerableHostAggregationQuery({ from, to })); - }, [setQuery, from, to]); + setQuery( + buildVulnerableHostAggregationQuery({ from, to, currentPage: paginationData.currentPage }) + ); + }, [setQuery, from, to, paginationData.currentPage]); useEffect(() => { if (data == null || !data.aggregations) { setItems([]); } else { setItems(parseHostsData(data.aggregations)); + + setPaginationData((p) => ({ + ...p, + pageCount: getPageCount(data.aggregations?.host_count.value), + })); } setUpdatedAt(Date.now()); }, [data]); @@ -77,6 +101,13 @@ export const useHostAlertsItems: UseHostAlertsItems = ({ skip, queryId, signalIn } }, [skip, refetchQuery]); + const setPage = (pageNumber: number) => { + setPaginationData((p) => ({ + ...p, + currentPage: pageNumber, + })); + }; + useQueryInspector({ deleteQuery, inspect: { @@ -88,87 +119,112 @@ export const useHostAlertsItems: UseHostAlertsItems = ({ skip, queryId, signalIn queryId, loading, }); - return { items, isLoading: loading, updatedAt }; -}; -export const buildVulnerableHostAggregationQuery = ({ from, to }: TimeRange) => ({ - query: { - bool: { - filter: [ - { - term: { - 'kibana.alert.workflow_status': 'open', - }, - }, - { - range: { - '@timestamp': { - gte: from, - lte: to, - }, - }, - }, - ], + return { + items, + isLoading: loading, + updatedAt, + pagination: { + ...paginationData, + setPage, }, - }, - size: 0, - aggs: { - [HOSTS_BY_SEVERITY_AGG]: { - terms: { - field: 'host.name', - order: [ - { - 'critical.doc_count': 'desc', - }, - { - 'high.doc_count': 'desc', - }, + }; +}; + +export const buildVulnerableHostAggregationQuery = ({ + from, + to, + currentPage, +}: TimeRange & { currentPage: number }) => { + const fromValue = ITEMS_PER_PAGE * currentPage; + + return { + query: { + bool: { + filter: [ { - 'medium.doc_count': 'desc', + term: { + 'kibana.alert.workflow_status': 'open', + }, }, { - 'low.doc_count': 'desc', + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, }, ], - size: 4, }, - aggs: { - critical: { - filter: { - term: { - 'kibana.alert.severity': 'critical', + }, + size: 0, + aggs: { + host_count: { cardinality: { field: 'host.name' } }, + [HOSTS_BY_SEVERITY_AGG]: { + terms: { + size: 100, + field: 'host.name', + order: [ + { + 'critical.doc_count': 'desc', }, - }, + { + 'high.doc_count': 'desc', + }, + { + 'medium.doc_count': 'desc', + }, + { + 'low.doc_count': 'desc', + }, + ], }, - high: { - filter: { - term: { - 'kibana.alert.severity': 'high', + aggs: { + critical: { + filter: { + term: { + 'kibana.alert.severity': 'critical', + }, }, }, - }, - medium: { - filter: { - term: { - 'kibana.alert.severity': 'medium', + high: { + filter: { + term: { + 'kibana.alert.severity': 'high', + }, }, }, - }, - low: { - filter: { - term: { - 'kibana.alert.severity': 'low', + medium: { + filter: { + term: { + 'kibana.alert.severity': 'medium', + }, + }, + }, + low: { + filter: { + term: { + 'kibana.alert.severity': 'low', + }, + }, + }, + bucketOfPagination: { + bucket_sort: { + from: fromValue, + size: 4, }, }, }, }, }, - }, -}); + }; +}; interface SeverityContainer { doc_count: number; } + interface AlertBySeverityBucketData extends GenericBuckets { low: SeverityContainer; medium: SeverityContainer; @@ -180,6 +236,7 @@ interface AlertCountersBySeverityAndHostAggregation { [HOSTS_BY_SEVERITY_AGG]: { buckets: AlertBySeverityBucketData[]; }; + host_count: { value: number }; } function parseHostsData( @@ -188,16 +245,15 @@ function parseHostsData( const buckets = rawAggregation?.[HOSTS_BY_SEVERITY_AGG].buckets ?? []; return buckets.reduce((accumalatedAlertsByHost, currentHost) => { - return [ - ...accumalatedAlertsByHost, - { - hostName: currentHost.key || '—', - totalAlerts: currentHost.doc_count, - low: currentHost.low.doc_count, - medium: currentHost.medium.doc_count, - high: currentHost.high.doc_count, - critical: currentHost.critical.doc_count, - }, - ]; + accumalatedAlertsByHost.push({ + hostName: currentHost.key || '—', + totalAlerts: currentHost.doc_count, + low: currentHost.low.doc_count, + medium: currentHost.medium.doc_count, + high: currentHost.high.doc_count, + critical: currentHost.critical.doc_count, + }); + + return accumalatedAlertsByHost; }, []); } diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx index dafd1ca965a3f..16a13c426b550 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx @@ -91,7 +91,7 @@ describe('RuleAlertsTable', () => { ); - expect(result.getByText('Updated now')).toBeInTheDocument(); + expect(result.getByText(/Updated/)).toBeInTheDocument(); }); it('should render the table columns', () => { diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx index 550e04753e886..470f9901a05c9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx @@ -22,7 +22,7 @@ import { FormattedRelative } from '@kbn/i18n-react'; import { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { HeaderSection } from '../../../../common/components/header_section'; -import { LastUpdatedAt, SEVERITY_COLOR } from '../util'; +import { LastUpdatedAt, SEVERITY_COLOR } from '../utils'; import * as i18n from '../translations'; import { useRuleAlertsItems, RuleAlertsItem } from './use_rule_alerts_items'; import { useNavigation, NavigateTo, GetAppUrl } from '../../../../common/lib/kibana'; @@ -31,6 +31,7 @@ import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { HoverVisibilityContainer } from '../../../../common/components/hover_visibility_container'; import { BUTTON_CLASS as INPECT_BUTTON_CLASS } from '../../../../common/components/inspect'; import { FormattedCount } from '../../../../common/components/formatted_number'; +import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; export interface RuleAlertsTableProps { signalIndexName: string | null; @@ -39,12 +40,13 @@ export interface RuleAlertsTableProps { export type GetTableColumns = (params: { getAppUrl: GetAppUrl; navigateTo: NavigateTo; + openRuleInTimeline: (ruleName: string) => void; }) => Array>; const DETECTION_RESPONSE_RULE_ALERTS_QUERY_ID = 'detection-response-rule-alerts-severity-table' as const; -export const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo }) => [ +export const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo, openRuleInTimeline }) => [ { field: 'name', name: i18n.RULE_ALERTS_COLUMN_RULE_NAME, @@ -79,7 +81,11 @@ export const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo }) => [ field: 'alert_count', name: i18n.RULE_ALERTS_COLUMN_ALERT_COUNT, 'data-test-subj': 'severityRuleAlertsTable-alertCount', - render: (alertCount: number) => , + render: (alertCount: number, { name }) => ( + openRuleInTimeline(name)}> + + + ), }, { field: 'severity', @@ -100,13 +106,15 @@ export const RuleAlertsTable = React.memo(({ signalIndexNa skip: !toggleStatus, }); + const { openRuleInTimeline } = useNavigateToTimeline(); + const navigateToAlerts = useCallback(() => { navigateTo({ deepLinkId: SecurityPageName.alerts }); }, [navigateTo]); const columns = useMemo( - () => getTableColumns({ getAppUrl, navigateTo }), - [getAppUrl, navigateTo] + () => getTableColumns({ getAppUrl, navigateTo, openRuleInTimeline }), + [getAppUrl, navigateTo, openRuleInTimeline] ); return ( diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts index d013ab258631a..a9773d09ba461 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts @@ -92,14 +92,14 @@ export const RULE_ALERTS_SECTION_TITLE = i18n.translate( export const HOST_ALERTS_SECTION_TITLE = i18n.translate( 'xpack.securitySolution.detectionResponse.hostAlertsSectionTitle', { - defaultMessage: 'Vulnerable hosts by severity', + defaultMessage: 'Hosts by alert severity', } ); export const USER_ALERTS_SECTION_TITLE = i18n.translate( 'xpack.securitySolution.detectionResponse.userAlertsSectionTitle', { - defaultMessage: 'Vulnerable users by severity', + defaultMessage: 'Users by alert severity', } ); @@ -243,3 +243,17 @@ export const ERROR_MESSAGE_CASES = i18n.translate( defaultMessage: 'Error fetching case data', } ); + +export const HOST_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionResponse.hostSectionTooltip', + { + defaultMessage: 'Maximum of 100 hosts. Please consult Alerts page for further information.', + } +); + +export const USER_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionResponse.userSectionTooltip', + { + defaultMessage: 'Maximum of 100 users. Please consult Alerts page for further information.', + } +); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/mock_data.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/mock_data.ts index e10bf1b05f032..a5339de96e16c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/mock_data.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/mock_data.ts @@ -9,6 +9,9 @@ import { buildVulnerableUserAggregationQuery } from './use_user_alerts_items'; export const mockVulnerableUsersBySeverityResult = { aggregations: { + user_count: { + value: 4, + }, usersBySeverity: { buckets: [ { @@ -119,6 +122,7 @@ export const mockQuery = () => ({ query: buildVulnerableUserAggregationQuery({ from: '2020-07-07T08:20:18.966Z', to: '2020-07-08T08:20:18.966Z', + currentPage: 0, }), indexName: 'signal-alerts', skip: false, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.test.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.test.ts index 22ade5d3341c7..3aef486805322 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.test.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.test.ts @@ -73,6 +73,10 @@ describe('useUserAlertsItems', () => { items: [], isLoading: false, updatedAt: dateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 0, + }), }); expect(mockUseQueryAlerts).toBeCalledWith(mockQuery()); @@ -90,6 +94,10 @@ describe('useUserAlertsItems', () => { items: parsedVulnerableUserAlertsResult, isLoading: false, updatedAt: dateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 1, + }), }); }); @@ -109,6 +117,10 @@ describe('useUserAlertsItems', () => { items: parsedVulnerableUserAlertsResult, isLoading: false, updatedAt: newDateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 1, + }), }); }); @@ -121,6 +133,10 @@ describe('useUserAlertsItems', () => { items: [], isLoading: false, updatedAt: dateNow, + pagination: expect.objectContaining({ + currentPage: 0, + pageCount: 0, + }), }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts index 3e2778f82cde4..5c0280f093cbe 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts @@ -11,8 +11,13 @@ import { useQueryInspector } from '../../../../common/components/page/manage_que import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { GenericBuckets } from '../../../../../common/search_strategy'; import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; +import { getPageCount, ITEMS_PER_PAGE } from '../utils'; const USERS_BY_SEVERITY_AGG = 'usersBySeverity'; +const defaultPagination = { + pageCount: 0, + currentPage: 0, +}; interface TimeRange { from: string; @@ -38,11 +43,18 @@ export type UseUserAlertsItems = (props: UseUserAlertsItemsProps) => { items: UserAlertsItem[]; isLoading: boolean; updatedAt: number; + pagination: Pagination & { setPage: (pageNumber: number) => void }; }; +interface Pagination { + pageCount: number; + currentPage: number; +} + export const useUserAlertsItems: UseUserAlertsItems = ({ skip, queryId, signalIndexName }) => { const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState([]); + const [paginationData, setPaginationData] = useState(defaultPagination); const { to, from, setQuery: setGlobalQuery, deleteQuery } = useGlobalTime(); @@ -54,20 +66,31 @@ export const useUserAlertsItems: UseUserAlertsItems = ({ skip, queryId, signalIn response, refetch: refetchQuery, } = useQueryAlerts<{}, AlertCountersBySeverityAggregation>({ - query: buildVulnerableUserAggregationQuery({ from, to }), + query: buildVulnerableUserAggregationQuery({ + from, + to, + currentPage: paginationData.currentPage, + }), indexName: signalIndexName, skip, }); useEffect(() => { - setQuery(buildVulnerableUserAggregationQuery({ from, to })); - }, [setQuery, from, to]); + setQuery( + buildVulnerableUserAggregationQuery({ from, to, currentPage: paginationData.currentPage }) + ); + }, [setQuery, from, to, paginationData.currentPage]); useEffect(() => { if (data == null || !data.aggregations) { setItems([]); } else { setItems(parseUsersData(data.aggregations)); + + setPaginationData((p) => ({ + ...p, + pageCount: getPageCount(data.aggregations?.user_count.value), + })); } setUpdatedAt(Date.now()); }, [data]); @@ -78,6 +101,13 @@ export const useUserAlertsItems: UseUserAlertsItems = ({ skip, queryId, signalIn } }, [skip, refetchQuery]); + const setPage = (pageNumber: number) => { + setPaginationData((p) => ({ + ...p, + currentPage: pageNumber, + })); + }; + useQueryInspector({ deleteQuery, inspect: { @@ -89,87 +119,112 @@ export const useUserAlertsItems: UseUserAlertsItems = ({ skip, queryId, signalIn queryId, loading, }); - return { items, isLoading: loading, updatedAt }; -}; -export const buildVulnerableUserAggregationQuery = ({ from, to }: TimeRange) => ({ - query: { - bool: { - filter: [ - { - term: { - 'kibana.alert.workflow_status': 'open', - }, - }, - { - range: { - '@timestamp': { - gte: from, - lte: to, - }, - }, - }, - ], + return { + items, + isLoading: loading, + updatedAt, + pagination: { + ...paginationData, + setPage, }, - }, - size: 0, - aggs: { - [USERS_BY_SEVERITY_AGG]: { - terms: { - field: 'user.name', - order: [ - { - 'critical.doc_count': 'desc', - }, - { - 'high.doc_count': 'desc', - }, + }; +}; + +export const buildVulnerableUserAggregationQuery = ({ + from, + to, + currentPage, +}: TimeRange & { currentPage: number }) => { + const fromValue = ITEMS_PER_PAGE * currentPage; + + return { + query: { + bool: { + filter: [ { - 'medium.doc_count': 'desc', + term: { + 'kibana.alert.workflow_status': 'open', + }, }, { - 'low.doc_count': 'desc', + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, }, ], - size: 4, }, - aggs: { - critical: { - filter: { - term: { - 'kibana.alert.severity': 'critical', + }, + size: 0, + aggs: { + user_count: { cardinality: { field: 'user.name' } }, + [USERS_BY_SEVERITY_AGG]: { + terms: { + size: 100, + field: 'user.name', + order: [ + { + 'critical.doc_count': 'desc', }, - }, + { + 'high.doc_count': 'desc', + }, + { + 'medium.doc_count': 'desc', + }, + { + 'low.doc_count': 'desc', + }, + ], }, - high: { - filter: { - term: { - 'kibana.alert.severity': 'high', + aggs: { + critical: { + filter: { + term: { + 'kibana.alert.severity': 'critical', + }, }, }, - }, - medium: { - filter: { - term: { - 'kibana.alert.severity': 'medium', + high: { + filter: { + term: { + 'kibana.alert.severity': 'high', + }, }, }, - }, - low: { - filter: { - term: { - 'kibana.alert.severity': 'low', + medium: { + filter: { + term: { + 'kibana.alert.severity': 'medium', + }, + }, + }, + low: { + filter: { + term: { + 'kibana.alert.severity': 'low', + }, + }, + }, + bucketOfPagination: { + bucket_sort: { + from: fromValue, + size: 4, }, }, }, }, }, - }, -}); + }; +}; interface SeverityContainer { doc_count: number; } + interface AlertBySeverityBucketData extends GenericBuckets { low: SeverityContainer; medium: SeverityContainer; @@ -181,22 +236,22 @@ interface AlertCountersBySeverityAggregation { [USERS_BY_SEVERITY_AGG]: { buckets: AlertBySeverityBucketData[]; }; + user_count: { value: number }; } function parseUsersData(rawAggregation: AlertCountersBySeverityAggregation): UserAlertsItem[] { const buckets = rawAggregation?.[USERS_BY_SEVERITY_AGG].buckets ?? []; return buckets.reduce((accumalatedAlertsByUser, currentUser) => { - return [ - ...accumalatedAlertsByUser, - { - userName: currentUser.key || '—', - totalAlerts: currentUser.doc_count, - low: currentUser.low.doc_count, - medium: currentUser.medium.doc_count, - high: currentUser.high.doc_count, - critical: currentUser.critical.doc_count, - }, - ]; + accumalatedAlertsByUser.push({ + userName: currentUser.key || '—', + totalAlerts: currentUser.doc_count, + low: currentUser.low.doc_count, + medium: currentUser.medium.doc_count, + high: currentUser.high.doc_count, + critical: currentUser.critical.doc_count, + }); + + return accumalatedAlertsByUser; }, []); } diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx index 8e0b7656fda8e..a7c48f5092a39 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import { parsedVulnerableUserAlertsResult } from './mock_data'; @@ -30,6 +30,11 @@ const defaultUseUserAlertsItemsReturn: UseUserAlertsItemsReturn = { items: [], isLoading: false, updatedAt: Date.now(), + pagination: { + currentPage: 0, + pageCount: 0, + setPage: () => null, + }, }; const mockUseUserAlertsItems = jest.fn(() => defaultUseUserAlertsItemsReturn); const mockUseUserAlertsItemsReturn = (overrides: Partial) => { @@ -47,34 +52,33 @@ const renderComponent = () => ); -// FLAKY: https://github.com/elastic/kibana/issues/132360 -describe.skip('UserAlertsTable', () => { +describe('UserAlertsTable', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should render empty table', () => { - const { getByText, getByTestId } = renderComponent(); + const { getByText, queryByTestId } = renderComponent(); - expect(getByTestId('severityUserAlertsPanel')).toBeInTheDocument(); + expect(queryByTestId('severityUserAlertsPanel')).toBeInTheDocument(); + expect(queryByTestId('userTablePaginator')).not.toBeInTheDocument(); expect(getByText('No alerts to display')).toBeInTheDocument(); - expect(getByTestId('severityUserAlertsButton')).toBeInTheDocument(); }); it('should render a loading table', () => { mockUseUserAlertsItemsReturn({ isLoading: true }); - const { getByText, getByTestId } = renderComponent(); + const { getByText, queryByTestId } = renderComponent(); expect(getByText('Updating...')).toBeInTheDocument(); - expect(getByTestId('severityUserAlertsButton')).toBeInTheDocument(); - expect(getByTestId('severityUserAlertsTable')).toHaveClass('euiBasicTable-loading'); + expect(queryByTestId('severityUserAlertsTable')).toHaveClass('euiBasicTable-loading'); + expect(queryByTestId('userTablePaginator')).not.toBeInTheDocument(); }); it('should render the updated at subtitle', () => { mockUseUserAlertsItemsReturn({ isLoading: false }); const { getByText } = renderComponent(); - expect(getByText('Updated now')).toBeInTheDocument(); + expect(getByText(/Updated/)).toBeInTheDocument(); }); it('should render the table columns', () => { @@ -92,13 +96,32 @@ describe.skip('UserAlertsTable', () => { it('should render the table items', () => { mockUseUserAlertsItemsReturn({ items: [parsedVulnerableUserAlertsResult[0]] }); - const { getByTestId } = renderComponent(); - - expect(getByTestId('userSeverityAlertsTable-userName')).toHaveTextContent('crffn20qcs'); - expect(getByTestId('userSeverityAlertsTable-totalAlerts')).toHaveTextContent('4'); - expect(getByTestId('userSeverityAlertsTable-critical')).toHaveTextContent('4'); - expect(getByTestId('userSeverityAlertsTable-high')).toHaveTextContent('1'); - expect(getByTestId('userSeverityAlertsTable-medium')).toHaveTextContent('1'); - expect(getByTestId('userSeverityAlertsTable-low')).toHaveTextContent('1'); + const { queryByTestId } = renderComponent(); + + expect(queryByTestId('userSeverityAlertsTable-userName')).toHaveTextContent('crffn20qcs'); + expect(queryByTestId('userSeverityAlertsTable-totalAlerts')).toHaveTextContent('4'); + expect(queryByTestId('userSeverityAlertsTable-critical')).toHaveTextContent('4'); + expect(queryByTestId('userSeverityAlertsTable-high')).toHaveTextContent('1'); + expect(queryByTestId('userSeverityAlertsTable-medium')).toHaveTextContent('1'); + expect(queryByTestId('userSeverityAlertsTable-low')).toHaveTextContent('1'); + expect(queryByTestId('userTablePaginator')).not.toBeInTheDocument(); + }); + + it('should render the paginator if more than 4 results', () => { + const mockSetPage = jest.fn(); + + mockUseUserAlertsItemsReturn({ + pagination: { + currentPage: 1, + pageCount: 3, + setPage: mockSetPage, + }, + }); + const { queryByTestId, getByText } = renderComponent(); + const page3 = getByText('3'); + expect(queryByTestId('userTablePaginator')).toBeInTheDocument(); + + fireEvent.click(page3); + expect(mockSetPage).toHaveBeenCalledWith(2); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx index 0416bb48e13e1..80104244fedf0 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx @@ -5,70 +5,56 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; -import styled from 'styled-components'; +import React, { useMemo } from 'react'; import { EuiBasicTable, EuiBasicTableColumn, - EuiButton, EuiEmptyPrompt, EuiHealth, + EuiLink, EuiPanel, EuiSpacer, + EuiTablePagination, } from '@elastic/eui'; -import { SecurityPageName } from '../../../../app/types'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { HeaderSection } from '../../../../common/components/header_section'; import { HoverVisibilityContainer } from '../../../../common/components/hover_visibility_container'; import { BUTTON_CLASS as INPECT_BUTTON_CLASS } from '../../../../common/components/inspect'; import { UserDetailsLink } from '../../../../common/components/links'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; -import { useNavigation, NavigateTo, GetAppUrl } from '../../../../common/lib/kibana'; +import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; import * as i18n from '../translations'; -import { LastUpdatedAt, SEVERITY_COLOR } from '../util'; +import { ITEMS_PER_PAGE, LastUpdatedAt, SEVERITY_COLOR } from '../utils'; import { UserAlertsItem, useUserAlertsItems } from './use_user_alerts_items'; -export interface UserAlertsTableProps { +interface UserAlertsTableProps { signalIndexName: string | null; } -type GetTableColumns = (params: { - getAppUrl: GetAppUrl; - navigateTo: NavigateTo; -}) => Array>; +type GetTableColumns = ( + handleClick: (params: { userName: string; severity?: string }) => void +) => Array>; const DETECTION_RESPONSE_USER_SEVERITY_QUERY_ID = 'vulnerableUsersBySeverityQuery'; -// To Do remove this styled component once togglequery is updated: #131405 -const StyledEuiPanel = styled(EuiPanel)` - height: fit-content; -`; - export const UserAlertsTable = React.memo(({ signalIndexName }: UserAlertsTableProps) => { - const { getAppUrl, navigateTo } = useNavigation(); + const { openUserInTimeline } = useNavigateToTimeline(); const { toggleStatus, setToggleStatus } = useQueryToggle( DETECTION_RESPONSE_USER_SEVERITY_QUERY_ID ); - const { items, isLoading, updatedAt } = useUserAlertsItems({ + const { items, isLoading, updatedAt, pagination } = useUserAlertsItems({ skip: !toggleStatus, queryId: DETECTION_RESPONSE_USER_SEVERITY_QUERY_ID, signalIndexName, }); - const navigateToAlerts = useCallback(() => { - navigateTo({ deepLinkId: SecurityPageName.users }); - }, [navigateTo]); - - const columns = useMemo( - () => getTableColumns({ getAppUrl, navigateTo }), - [getAppUrl, navigateTo] - ); + const columns = useMemo(() => getTableColumns(openUserInTimeline), [openUserInTimeline]); return ( - + } + tooltip={i18n.USER_TOOLTIP} /> - {toggleStatus && ( <> - - {i18n.VIEW_ALL_USER_ALERTS} - + {pagination.pageCount > 1 && ( + + )} )} - + ); }); UserAlertsTable.displayName = 'UserAlertsTable'; -const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo }) => [ +const getTableColumns: GetTableColumns = (handleClick) => [ { field: 'userName', name: i18n.USER_ALERTS_USERNAME_COLUMN, @@ -115,41 +108,59 @@ const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo }) => [ field: 'totalAlerts', name: i18n.ALERTS_TEXT, 'data-test-subj': 'userSeverityAlertsTable-totalAlerts', - render: (totalAlerts: number) => , + render: (totalAlerts: number, { userName }) => ( + handleClick({ userName })}> + + + ), }, { field: 'critical', name: i18n.STATUS_CRITICAL_LABEL, - render: (count: number) => ( + render: (count: number, { userName }) => ( - + handleClick({ userName, severity: 'critical' })} + > + + ), }, { field: 'high', name: i18n.STATUS_HIGH_LABEL, - render: (count: number) => ( + render: (count: number, { userName }) => ( - + handleClick({ userName, severity: 'high' })}> + + ), }, { field: 'medium', name: i18n.STATUS_MEDIUM_LABEL, - render: (count: number) => ( + render: (count: number, { userName }) => ( - {count} + handleClick({ userName, severity: 'medium' })} + > + + ), }, { field: 'low', name: i18n.STATUS_LOW_LABEL, - render: (count: number) => ( + render: (count: number, { userName }) => ( - + handleClick({ userName, severity: 'low' })}> + + ), }, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/util.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/util.tsx deleted file mode 100644 index 4ceba66773397..0000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/util.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { FormattedRelative } from '@kbn/i18n-react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as i18n from './translations'; - -export const SEVERITY_COLOR = { - critical: '#EF6550', - high: '#EE9266', - medium: '#F3B689', - low: '#F8D9B2', -} as const; - -export interface LastUpdatedAtProps { - updatedAt: number; - isUpdating: boolean; -} -export const LastUpdatedAt: React.FC = ({ isUpdating, updatedAt }) => ( - - {isUpdating ? ( - {i18n.UPDATING} - ) : ( - - <>{i18n.UPDATED} - - - )} - -); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx index 4ceba66773397..0a602b21f676f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx @@ -6,21 +6,27 @@ */ import React from 'react'; -import { FormattedRelative } from '@kbn/i18n-react'; + import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n-react'; + import * as i18n from './translations'; export const SEVERITY_COLOR = { - critical: '#EF6550', - high: '#EE9266', - medium: '#F3B689', - low: '#F8D9B2', + critical: '#E7664C', + high: '#DA8B45', + medium: '#D6BF57', + low: '#54B399', } as const; +export const ITEMS_PER_PAGE = 4; +const MAX_ALLOWED_RESULTS = 100; + export interface LastUpdatedAtProps { updatedAt: number; isUpdating: boolean; } + export const LastUpdatedAt: React.FC = ({ isUpdating, updatedAt }) => ( {isUpdating ? ( @@ -37,3 +43,10 @@ export const LastUpdatedAt: React.FC = ({ isUpdating, update )} ); + +/** + * While there could be more than 100 hosts or users we only want to show 25 pages of results, + * and the host count cardinality result will always be the total count + * */ +export const getPageCount = (count: number = 0) => + Math.ceil(Math.min(count || 0, MAX_ALLOWED_RESULTS) / ITEMS_PER_PAGE); diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 96728a07432fd..1af23099dca01 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -37,6 +37,7 @@ export type { RowRenderer, SetEventsDeleted, SetEventsLoading, + TimelineType, } from './types'; export { IS_OPERATOR, EXISTS_OPERATOR, DataProviderType, TimelineId } from './types'; From dc9f2732a106c837140a5b74f5698076f3230529 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Mon, 23 May 2022 20:01:56 +0200 Subject: [PATCH 26/71] Add csp.disableUnsafeEval config option to remove the unsafe-eval CSP (#124484) Adds a new experimental Kibana setting called `csp.disableUnsafeEval` which will default to `false`. When set to `true`, it will remove `unsafe-eval` from our CSP. Also introduces a new module called `@kbn/handlebars` which is a replacement for the official `handlebars` module used in the frontend. This new module is necessary in order to avoid calling `eval`/`new Function` from within `handlebars` which is not allowed once `unsafe-eval` is removed from our CSP. The `@kbn/handlebars` module is simply an extension of the main `handlebars` module which adds a new compile function called `compileAST` (as an alternative to the regular `compile` function). This new function will not use code-generation from strings to compile the template but will instead generate an AST and return a render function with the same API as the function returned by the regular `compile` function. This is a little bit slower method, but since this is only meant to be used client-side, the slowdown should not be an issue. The following limitations exists when using `@kbn/handlebars`: The Inline partials handlebars template feature is not supported. Only the following compile options will be supported: - `knownHelpers` - `knownHelpersOnly` - `strict` - `assumeObjects` - `noEscape` - `data` Only the following runtime options will be supported: - `helpers` - `blockParams` - `data` Closes #36311 --- .eslintrc.js | 86 ++ .github/CODEOWNERS | 1 + api_docs/deprecations_by_api.mdx | 55 +- api_docs/deprecations_by_plugin.mdx | 88 +- api_docs/deprecations_by_team.mdx | 48 +- api_docs/kbn_handlebars.devdocs.json | 153 +++ api_docs/kbn_handlebars.mdx | 33 + api_docs/plugin_directory.mdx | 126 +- docs/setup/settings.asciidoc | 9 +- package.json | 2 + packages/BUILD.bazel | 2 + packages/kbn-handlebars/.gitignore | 1 + packages/kbn-handlebars/.patches/basic.patch | 612 +++++++++ packages/kbn-handlebars/.patches/blocks.patch | 461 +++++++ .../kbn-handlebars/.patches/builtins.patch | 872 +++++++++++++ .../kbn-handlebars/.patches/compiler.patch | 272 ++++ packages/kbn-handlebars/.patches/data.patch | 273 ++++ .../kbn-handlebars/.patches/helpers.patch | 1096 +++++++++++++++++ .../kbn-handlebars/.patches/regressions.patch | 518 ++++++++ .../kbn-handlebars/.patches/security.patch | 443 +++++++ packages/kbn-handlebars/.patches/strict.patch | 180 +++ .../.patches/subexpressions.patch | 318 +++++ packages/kbn-handlebars/.patches/utils.patch | 109 ++ .../.patches/whitespace-control.patch | 187 +++ packages/kbn-handlebars/BUILD.bazel | 115 ++ packages/kbn-handlebars/LICENSE | 29 + packages/kbn-handlebars/README.md | 192 +++ packages/kbn-handlebars/jest.config.js | 10 + packages/kbn-handlebars/package.json | 9 + .../scripts/check_for_test_changes.sh | 33 + packages/kbn-handlebars/scripts/print_ast.js | 45 + .../scripts/update_test_patches.sh | 24 + .../kbn-handlebars/src/__jest__/test_bench.ts | 170 +++ .../src/__snapshots__/index.test.ts.snap | 105 ++ packages/kbn-handlebars/src/index.test.ts | 93 ++ packages/kbn-handlebars/src/index.ts | 710 +++++++++++ .../src/upstream/index.basic.test.ts | 481 ++++++++ .../src/upstream/index.blocks.test.ts | 198 +++ .../src/upstream/index.builtins.test.ts | 649 ++++++++++ .../src/upstream/index.compiler.test.ts | 89 ++ .../src/upstream/index.data.test.ts | 255 ++++ .../src/upstream/index.helpers.test.ts | 954 ++++++++++++++ .../src/upstream/index.regressions.test.ts | 279 +++++ .../src/upstream/index.security.test.ts | 132 ++ .../src/upstream/index.strict.test.ts | 164 +++ .../src/upstream/index.subexpressions.test.ts | 214 ++++ .../src/upstream/index.utils.test.ts | 24 + .../upstream/index.whitespace-control.test.ts | 80 ++ packages/kbn-handlebars/tsconfig.json | 19 + src/core/server/csp/config.ts | 7 + src/core/server/csp/csp_config.test.mocks.ts | 25 + src/core/server/csp/csp_config.test.ts | 75 +- src/core/server/csp/csp_directives.test.ts | 8 +- src/core/server/csp/csp_directives.ts | 5 +- .../http_resources_service.test.ts | 12 +- .../http_resources_service.test.ts | 15 +- .../resources/base/bin/kibana-docker | 1 + src/dev/precommit_hook/casing_check_config.js | 4 + .../collectors/csp/csp_collector.test.ts | 1 + .../components/lib/replace_vars.ts | 19 +- .../components/lib/tick_formatter.js | 4 +- test/api_integration/apis/general/csp.js | 36 - test/api_integration/apis/general/index.js | 1 - test/common/config.js | 1 + .../functions/browser/markdown.ts | 3 +- .../plugins/canvas/common/lib/handlebars.js | 2 +- .../authentication_service.test.ts | 19 +- .../check_steps/use_expanded_row.test.tsx | 4 +- .../drilldowns/url_drilldown/handlebars.ts | 8 +- .../drilldowns/url_drilldown/url_template.ts | 13 +- .../tests/anonymous/login.ts | 4 +- .../tests/kerberos/kerberos_login.ts | 4 +- .../login_selector/basic_functionality.ts | 16 +- .../oidc/authorization_code_flow/oidc_auth.ts | 8 +- .../tests/oidc/implicit_flow/oidc_auth.ts | 12 +- .../tests/pki/pki_auth.ts | 4 +- .../tests/saml/saml_login.ts | 8 +- yarn.lock | 8 + 78 files changed, 11036 insertions(+), 309 deletions(-) create mode 100644 api_docs/kbn_handlebars.devdocs.json create mode 100644 api_docs/kbn_handlebars.mdx create mode 100644 packages/kbn-handlebars/.gitignore create mode 100644 packages/kbn-handlebars/.patches/basic.patch create mode 100644 packages/kbn-handlebars/.patches/blocks.patch create mode 100644 packages/kbn-handlebars/.patches/builtins.patch create mode 100644 packages/kbn-handlebars/.patches/compiler.patch create mode 100644 packages/kbn-handlebars/.patches/data.patch create mode 100644 packages/kbn-handlebars/.patches/helpers.patch create mode 100644 packages/kbn-handlebars/.patches/regressions.patch create mode 100644 packages/kbn-handlebars/.patches/security.patch create mode 100644 packages/kbn-handlebars/.patches/strict.patch create mode 100644 packages/kbn-handlebars/.patches/subexpressions.patch create mode 100644 packages/kbn-handlebars/.patches/utils.patch create mode 100644 packages/kbn-handlebars/.patches/whitespace-control.patch create mode 100644 packages/kbn-handlebars/BUILD.bazel create mode 100644 packages/kbn-handlebars/LICENSE create mode 100644 packages/kbn-handlebars/README.md create mode 100644 packages/kbn-handlebars/jest.config.js create mode 100644 packages/kbn-handlebars/package.json create mode 100755 packages/kbn-handlebars/scripts/check_for_test_changes.sh create mode 100755 packages/kbn-handlebars/scripts/print_ast.js create mode 100755 packages/kbn-handlebars/scripts/update_test_patches.sh create mode 100644 packages/kbn-handlebars/src/__jest__/test_bench.ts create mode 100644 packages/kbn-handlebars/src/__snapshots__/index.test.ts.snap create mode 100644 packages/kbn-handlebars/src/index.test.ts create mode 100644 packages/kbn-handlebars/src/index.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.basic.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.blocks.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.builtins.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.compiler.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.data.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.helpers.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.regressions.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.security.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.strict.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.subexpressions.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.utils.test.ts create mode 100644 packages/kbn-handlebars/src/upstream/index.whitespace-control.test.ts create mode 100644 packages/kbn-handlebars/tsconfig.json create mode 100644 src/core/server/csp/csp_config.test.mocks.ts delete mode 100644 test/api_integration/apis/general/csp.js diff --git a/.eslintrc.js b/.eslintrc.js index 3ec2fe38b4d6f..b4dbfbaf8600b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -94,6 +94,22 @@ const SAFER_LODASH_SET_DEFINITELYTYPED_HEADER = ` */ `; +const KBN_HANDLEBARS_HEADER = ` +/* + * Elasticsearch B.V licenses this file to you under the MIT License. + * See \`packages/kbn-handlebars/LICENSE\` for more information. + */ +`; + +const KBN_HANDLEBARS_HANDLEBARS_HEADER = ` +/* + * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), + * and may include modifications made by Elasticsearch B.V. + * Elasticsearch B.V. licenses this file to you under the MIT License. + * See \`packages/kbn-handlebars/LICENSE\` for more information. + */ +`; + const packagePkgJsons = globby.sync('*/package.json', { cwd: Path.resolve(__dirname, 'packages'), absolute: true, @@ -293,6 +309,8 @@ module.exports = { SAFER_LODASH_SET_HEADER, SAFER_LODASH_SET_LODASH_HEADER, SAFER_LODASH_SET_DEFINITELYTYPED_HEADER, + KBN_HANDLEBARS_HEADER, + KBN_HANDLEBARS_HANDLEBARS_HEADER, ], }, ], @@ -325,6 +343,8 @@ module.exports = { SAFER_LODASH_SET_HEADER, SAFER_LODASH_SET_LODASH_HEADER, SAFER_LODASH_SET_DEFINITELYTYPED_HEADER, + KBN_HANDLEBARS_HEADER, + KBN_HANDLEBARS_HANDLEBARS_HEADER, ], }, ], @@ -364,6 +384,8 @@ module.exports = { SAFER_LODASH_SET_HEADER, SAFER_LODASH_SET_LODASH_HEADER, SAFER_LODASH_SET_DEFINITELYTYPED_HEADER, + KBN_HANDLEBARS_HEADER, + KBN_HANDLEBARS_HANDLEBARS_HEADER, ], }, ], @@ -393,6 +415,8 @@ module.exports = { OLD_ELASTIC_LICENSE_HEADER, SAFER_LODASH_SET_HEADER, SAFER_LODASH_SET_DEFINITELYTYPED_HEADER, + KBN_HANDLEBARS_HEADER, + KBN_HANDLEBARS_HANDLEBARS_HEADER, ], }, ], @@ -418,6 +442,8 @@ module.exports = { OLD_ELASTIC_LICENSE_HEADER, SAFER_LODASH_SET_LODASH_HEADER, SAFER_LODASH_SET_DEFINITELYTYPED_HEADER, + KBN_HANDLEBARS_HEADER, + KBN_HANDLEBARS_HANDLEBARS_HEADER, ], }, ], @@ -443,6 +469,66 @@ module.exports = { OLD_DUAL_LICENSE_HEADER, SAFER_LODASH_SET_HEADER, SAFER_LODASH_SET_LODASH_HEADER, + KBN_HANDLEBARS_HEADER, + KBN_HANDLEBARS_HANDLEBARS_HEADER, + ], + }, + ], + }, + }, + + /** + * @kbn/handlebars package requires special license headers + */ + { + files: ['packages/kbn-handlebars/**/*.{js,mjs,ts,tsx}'], + rules: { + '@kbn/eslint/require-license-header': [ + 'error', + { + license: KBN_HANDLEBARS_HEADER, + }, + ], + '@kbn/eslint/disallow-license-headers': [ + 'error', + { + licenses: [ + APACHE_2_0_LICENSE_HEADER, + DUAL_LICENSE_HEADER, + ELASTIC_LICENSE_HEADER, + OLD_DUAL_LICENSE_HEADER, + OLD_ELASTIC_LICENSE_HEADER, + SAFER_LODASH_SET_HEADER, + SAFER_LODASH_SET_LODASH_HEADER, + SAFER_LODASH_SET_DEFINITELYTYPED_HEADER, + KBN_HANDLEBARS_HANDLEBARS_HEADER, + ], + }, + ], + }, + }, + { + files: ['packages/kbn-handlebars/src/upstream/**/*.{js,mjs,ts,tsx}'], + rules: { + '@kbn/eslint/require-license-header': [ + 'error', + { + license: KBN_HANDLEBARS_HANDLEBARS_HEADER, + }, + ], + '@kbn/eslint/disallow-license-headers': [ + 'error', + { + licenses: [ + APACHE_2_0_LICENSE_HEADER, + DUAL_LICENSE_HEADER, + ELASTIC_LICENSE_HEADER, + OLD_DUAL_LICENSE_HEADER, + OLD_ELASTIC_LICENSE_HEADER, + SAFER_LODASH_SET_HEADER, + SAFER_LODASH_SET_LODASH_HEADER, + SAFER_LODASH_SET_DEFINITELYTYPED_HEADER, + KBN_HANDLEBARS_HEADER, ], }, ], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index abd63289e0480..65156ad05ae8f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -324,6 +324,7 @@ # Kibana Platform Security /packages/kbn-crypto/ @elastic/kibana-security +/packages/kbn-handlebars/ @elastic/kibana-security /src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core /src/plugins/interactive_setup/ @elastic/kibana-security /test/interactive_setup_api_integration/ @elastic/kibana-security diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index a9ce0254cea52..e71454b74d697 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API summary: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-04-26 +date: 2022-05-23 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- @@ -14,14 +14,14 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Deprecated API | Referencing plugin(s) | Remove By | | ---------------|-----------|-----------| | | dataViews, maps, data | - | -| | dataViews, unifiedSearch, maps, data | - | +| | dataViews, maps, data | - | | | dataViews, discover, ux, savedObjects, dataViewEditor, maps, visDefaultEditor, data | - | | | dataViews, dataViewEditor, maps, visDefaultEditor, data | - | | | dataViews, unifiedSearch | - | | | dataViews, canvas | - | | | dataViews, unifiedSearch, data | - | | | dataViews, canvas, data | - | -| | dataViews, unifiedSearch, maps, data | - | +| | dataViews, maps, data | - | | | dataViews, dataViewEditor, maps, visDefaultEditor, data | - | | | dataViews, maps, data | - | | | dataViews | - | @@ -38,16 +38,17 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | dataViews, dataViewManagement | - | +| | discover, visualizations, dashboard, lens, maps, monitoring | - | | | unifiedSearch, discover, maps, infra, graph, securitySolution, stackAlerts, inputControlVis, savedObjects | - | | | maps | - | | | data, infra, maps | - | -| | discover | - | -| | discover | - | +| | alerting, discover, securitySolution | - | +| | alerting, discover, securitySolution | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | | | advancedSettings, discover | - | -| | management, observability, infra, apm, cloudSecurityPosture, enterpriseSearch, securitySolution, synthetics, ux, kibanaOverview | - | -| | esUiShared, home, spaces, fleet, visualizations, lens, observability, dataEnhanced, ml, apm, cloudSecurityPosture, indexLifecycleManagement, synthetics, upgradeAssistant, ux, kibanaOverview, savedObjectsManagement | - | +| | management, observability, infra, apm, cloudSecurityPosture, enterpriseSearch, synthetics, ux, kibanaOverview | - | +| | esUiShared, home, data, spaces, fleet, visualizations, lens, observability, ml, apm, cloudSecurityPosture, indexLifecycleManagement, synthetics, upgradeAssistant, ux, kibanaOverview, savedObjectsManagement | - | | | canvas, visTypeXy | - | | | canvas | - | | | canvas | - | @@ -58,11 +59,12 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | canvas | - | | | canvas | - | | | canvas, visTypeXy | - | -| | management, spaces, observability, ml, canvas, cloudSecurityPosture, enterpriseSearch, osquery, securitySolution, kibanaOverview | - | +| | management, spaces, observability, ml, canvas, cloudSecurityPosture, enterpriseSearch, osquery, kibanaOverview | - | +| | actions, alerting | - | +| | encryptedSavedObjects, actions, data, cloud, ml, logstash, securitySolution | - | | | dashboard, lens, stackAlerts, visTypeTable, visTypeTimeseries, visTypeXy, visTypeVislib, expressionPartitionVis | - | -| | visTypeTimeseries, graph, dataViewManagement | - | -| | encryptedSavedObjects, actions, cloud, ml, dataEnhanced, logstash, securitySolution | - | | | dashboard | - | +| | visTypeTimeseries, graph, dataViewManagement | - | | | visTypeTimeseries | - | | | dataViewManagement | - | | | dataViewManagement | - | @@ -72,44 +74,36 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | visTypeGauge | - | | | visTypePie | - | | | visTypePie | - | -| | actions, alerting | - | | | console | - | +| | discover, dashboard, lens, urlDrilldown, stackAlerts | 8.1 | +| | discover, dashboard, lens, urlDrilldown, stackAlerts | 8.1 | +| | discover, dashboard, lens, urlDrilldown, stackAlerts | 8.1 | | | unifiedSearch | 8.1 | -| | unifiedSearch, dataEnhanced | 8.1 | -| | unifiedSearch, discover, dashboard, urlDrilldown, stackAlerts | 8.1 | -| | unifiedSearch | 8.1 | -| | unifiedSearch | 8.1 | -| | unifiedSearch, discover, dashboard, urlDrilldown, stackAlerts | 8.1 | -| | unifiedSearch, dataEnhanced | 8.1 | -| | unifiedSearch, discover, dashboard, urlDrilldown, stackAlerts | 8.1 | -| | unifiedSearch, dataEnhanced | 8.1 | -| | discover, stackAlerts, inputControlVis | 8.1 | -| | discover, stackAlerts, inputControlVis | 8.1 | -| | dataEnhanced | 8.1 | +| | stackAlerts, alerting, securitySolution, inputControlVis | 8.1 | +| | stackAlerts, alerting, securitySolution, inputControlVis | 8.1 | | | apm | 8.1 | | | dataViews, unifiedSearch | 8.2 | | | dataViews, unifiedSearch, data | 8.2 | | | visualizations, dashboard, lens, maps, ml, securitySolution, security | 8.8.0 | -| | lens, dashboard, maps | 8.8.0 | +| | dashboard, maps | 8.8.0 | | | embeddable, discover, presentationUtil, dashboard, graph | 8.8.0 | +| | spaces, security, alerting | 8.8.0 | | | spaces, security, actions, alerting, ml, remoteClusters, graph, indexLifecycleManagement, mapsEms, painlessLab, rollup, searchprofiler, snapshotRestore, transform, upgradeAssistant | 8.8.0 | | | apm, security, securitySolution | 8.8.0 | | | apm, security, securitySolution | 8.8.0 | | | securitySolution | 8.8.0 | | | savedObjectsTaggingOss, visualizations, dashboard, lens | 8.8.0 | | | dashboard | 8.8.0 | +| | monitoring | 8.8.0 | +| | monitoring, kibanaUsageCollection | 8.8.0 | | | cloud, apm | 8.8.0 | | | security, licenseManagement, ml, apm, crossClusterReplication, logstash, painlessLab, searchprofiler, watcher | 8.8.0 | | | management, fleet, security, kibanaOverview | 8.8.0 | -| | spaces, security, alerting | 8.8.0 | | | security, fleet | 8.8.0 | | | security, fleet | 8.8.0 | | | security, fleet | 8.8.0 | | | security | 8.8.0 | | | mapsEms | 8.8.0 | -| | visTypeVega | 8.8.0 | -| | monitoring, visTypeVega | 8.8.0 | -| | monitoring, kibanaUsageCollection | 8.8.0 | | | ml | 8.8.0 Note to maintainers: when looking at usages, mind that typical use could be inside a `catch` block, @@ -132,6 +126,7 @@ Safe to remove. | ---------------|------------| | | data | | | data | +| | data | | | data | | | data | | | data | @@ -168,12 +163,15 @@ Safe to remove. | | data | | | data | | | data | +| | data | | | data | +| | data | | | data | | | data | | | data | | | data | | | data | +| | data | | | data | | | data | | | data | @@ -181,11 +179,13 @@ Safe to remove. | | data | | | data | | | data | +| | data | | | data | | | data | | | data | | | data | | | data | +| | data | | | dataViews | | | dataViews | | | expressions | @@ -211,5 +211,6 @@ Safe to remove. | | reporting | | | taskManager | | | core | +| | core | | | core | | | core | \ No newline at end of file diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 4904da587db13..f94de1760dd44 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin summary: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-04-26 +date: 2022-05-23 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- @@ -33,6 +33,10 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.ts#:~:text=create) | - | +| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch) | 8.1 | +| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.ts#:~:text=create) | - | +| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch) | 8.1 | | | [plugin.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/plugin.test.ts#:~:text=getKibanaFeatures) | 8.8.0 | | | [plugin.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/plugin.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24) | 8.8.0 | | | [task.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/usage/task.ts#:~:text=index) | - | @@ -113,6 +117,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [sync_dashboard_filter_state.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts#:~:text=syncQueryStateWithUrl), [sync_dashboard_filter_state.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/sync_dashboard_filter_state.ts#:~:text=syncQueryStateWithUrl), [dashboard_listing.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx#:~:text=syncQueryStateWithUrl), [dashboard_listing.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx#:~:text=syncQueryStateWithUrl) | - | | | [export_csv_action.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/actions/export_csv_action.tsx#:~:text=fieldFormats) | - | | | [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=Filter), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=Filter), [dashboard_state_slice.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts#:~:text=Filter), [dashboard_state_slice.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts#:~:text=Filter), [dashboard_state_slice.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts#:~:text=Filter)+ 5 more | 8.1 | | | [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [filter_utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/lib/filter_utils.ts#:~:text=Filter), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=Filter), [saved_dashboard.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts#:~:text=Filter), [dashboard_state_slice.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts#:~:text=Filter), [dashboard_state_slice.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts#:~:text=Filter), [dashboard_state_slice.ts](https://github.com/elastic/kibana/tree/master/src/plugins/dashboard/public/application/state/dashboard_state_slice.ts#:~:text=Filter)+ 5 more | 8.1 | @@ -130,32 +135,21 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField)+ 16 more | - | +| | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternField), [agg_config.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=IndexPatternField), [agg_config.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=IndexPatternField)+ 16 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [create_search_source.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.test.ts#:~:text=IndexPatternsContract)+ 19 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IndexPatternsService) | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/types.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [tabify_docs.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/tabify/tabify_docs.ts#:~:text=IndexPattern), [tabify_docs.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/tabify/tabify_docs.ts#:~:text=IndexPattern)+ 89 more | - | -| | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IFieldType), [date_histogram.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/buckets/date_histogram.ts#:~:text=IFieldType), [date_histogram.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/buckets/date_histogram.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IFieldType), [create_filters_from_range_select.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts#:~:text=IFieldType), [create_filters_from_range_select.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts#:~:text=IFieldType)+ 6 more | 8.2 | -| | [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [mapping.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/param_types/mapping.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField)+ 16 more | - | -| | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [normalize_sort_request.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/normalize_sort_request.ts#:~:text=IIndexPattern), [normalize_sort_request.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/normalize_sort_request.ts#:~:text=IIndexPattern), [normalize_sort_request.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/normalize_sort_request.ts#:~:text=IIndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IIndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IIndexPattern)+ 23 more | - | +| | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IFieldType), [date_histogram.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/buckets/date_histogram.ts#:~:text=IFieldType), [date_histogram.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/buckets/date_histogram.ts#:~:text=IFieldType), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IFieldType), [create_filters_from_range_select.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts#:~:text=IFieldType), [create_filters_from_range_select.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts#:~:text=IFieldType)+ 6 more | 8.2 | +| | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [kibana_context_type.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/kibana_context_type.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternField), [agg_config.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=IndexPatternField), [agg_config.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=IndexPatternField)+ 16 more | - | +| | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [get_time.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/query/timefilter/get_time.ts#:~:text=IIndexPattern), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IIndexPattern), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IIndexPattern), [generate_filters.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#:~:text=IIndexPattern), [timefilter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/timefilter/timefilter.ts#:~:text=IIndexPattern), [timefilter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/query/timefilter/timefilter.ts#:~:text=IIndexPattern)+ 23 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternAttributes), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IndexPatternAttributes), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternAttributes) | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [create_search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [search_source_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source_service.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [esaggs_fn.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/types.ts#:~:text=IndexPatternsContract), [create_search_source.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/create_search_source.test.ts#:~:text=IndexPatternsContract)+ 19 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/index.ts#:~:text=IndexPatternsService), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/index.ts#:~:text=IndexPatternsService) | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/index.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/types.ts#:~:text=IndexPattern), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/types.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [search_source.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/search_source/search_source.ts#:~:text=IndexPattern), [tabify_docs.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/tabify/tabify_docs.ts#:~:text=IndexPattern), [tabify_docs.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/common/search/tabify/tabify_docs.ts#:~:text=IndexPattern)+ 89 more | - | | | [aggs_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/search/aggs/aggs_service.ts#:~:text=indexPatternsServiceFactory), [esaggs.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/search/expressions/esaggs.ts#:~:text=indexPatternsServiceFactory), [search_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/search/search_service.ts#:~:text=indexPatternsServiceFactory) | - | +| | [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks), [get_columns.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/session/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [get_columns.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/session/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [get_columns.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/search/session/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks) | - | | | [data_table.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx#:~:text=executeTriggerActions), [data_table.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx#:~:text=executeTriggerActions) | - | - - - -## dataEnhanced - -| Deprecated API | Reference location(s) | Remove By | -| ---------------|-----------|-----------| -| | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode) | 8.1 | -| | [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=nodeBuilder), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder)+ 2 more | 8.1 | -| | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode) | 8.1 | -| | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode) | 8.1 | -| | [get_columns.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [get_columns.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [get_columns.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks), [connected_search_session_indicator.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx#:~:text=RedirectAppLinks) | - | -| | [session_service.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/session_service.ts#:~:text=authc) | - | +| | [session_service.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data/server/search/session/session_service.ts#:~:text=authc) | - | @@ -197,9 +191,9 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IndexPattern), [data_view_field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.test.ts#:~:text=IndexPattern), [data_view_field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.test.ts#:~:text=IndexPattern), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/public/index.ts#:~:text=IndexPattern), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPattern), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPattern), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPattern), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPattern) | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IndexPatternField), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/public/index.ts#:~:text=IndexPatternField), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPatternField), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPatternField), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPatternField), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPatternField), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPatternField), [data_view.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.test.ts#:~:text=IndexPatternField), [data_view_field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.test.ts#:~:text=IndexPatternField), [data_view_field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.test.ts#:~:text=IndexPatternField)+ 2 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IIndexPattern), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IIndexPattern), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IIndexPattern) | - | -| | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType)+ 7 more | 8.2 | +| | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IFieldType), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IFieldType)+ 7 more | 8.2 | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IndexPatternAttributes) | - | -| | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType)+ 7 more | 8.2 | +| | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IFieldType), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IFieldType)+ 7 more | 8.2 | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IIndexPattern), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IIndexPattern), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IIndexPattern) | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IndexPatternAttributes) | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IndexPatternsContract), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/public/index.ts#:~:text=IndexPatternsContract) | - | @@ -228,13 +222,12 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | ---------------|-----------|-----------| | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern) | - | | | [saved_search_embeddable.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx#:~:text=create), [saved_search_embeddable.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx#:~:text=create) | - | -| | [anchor.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/services/anchor.ts#:~:text=fetch), [fetch_hits_in_interval.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts#:~:text=fetch) | 8.1 | +| | [discover_state.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/main/services/discover_state.ts#:~:text=syncQueryStateWithUrl), [discover_state.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/main/services/discover_state.ts#:~:text=syncQueryStateWithUrl) | - | | | [plugin.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/plugin.tsx#:~:text=indexPatterns) | - | | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter) | 8.1 | | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter) | 8.1 | | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern) | - | | | [saved_search_embeddable.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx#:~:text=create), [saved_search_embeddable.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx#:~:text=create) | - | -| | [anchor.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/services/anchor.ts#:~:text=fetch), [fetch_hits_in_interval.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts#:~:text=fetch) | 8.1 | | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=IndexPattern) | - | | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter) | 8.1 | | | [on_save_search.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx#:~:text=SavedObjectSaveModal), [on_save_search.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | @@ -372,11 +365,14 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/app.tsx#:~:text=syncQueryStateWithUrl), [app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/app.tsx#:~:text=syncQueryStateWithUrl) | - | | | [ranges.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx#:~:text=fieldFormats), [droppable.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts#:~:text=fieldFormats) | - | +| | [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter), [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter) | 8.1 | +| | [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter), [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter) | 8.1 | +| | [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter), [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter) | 8.1 | | | [workspace_panel.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx#:~:text=RedirectAppLinks), [workspace_panel.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx#:~:text=RedirectAppLinks), [workspace_panel.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx#:~:text=RedirectAppLinks) | - | | | [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject) | 8.8.0 | | | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave) | 8.8.0 | -| | [saved_object_migrations.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts#:~:text=warning), [saved_object_migrations.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts#:~:text=warning) | 8.8.0 | @@ -415,6 +411,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract), [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract), [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract), [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract) | - | | | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=IndexPattern), [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=IndexPattern), [percentile_agg_field.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.test.ts#:~:text=IndexPattern), [percentile_agg_field.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.test.ts#:~:text=IndexPattern), [get_docvalue_source_fields.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts#:~:text=IndexPattern), [get_docvalue_source_fields.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts#:~:text=IndexPattern), [get_docvalue_source_fields.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts#:~:text=IndexPattern), [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=IndexPattern), [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=IndexPattern), [percentile_agg_field.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.test.ts#:~:text=IndexPattern)+ 4 more | - | | | [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField)+ 84 more | - | +| | [global_sync.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts#:~:text=syncQueryStateWithUrl), [global_sync.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts#:~:text=syncQueryStateWithUrl) | - | | | [kibana_services.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/kibana_services.ts#:~:text=indexPatterns) | - | | | [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract), [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract), [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract), [index.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/lazy_load_bundle/index.ts#:~:text=IndexPatternsContract) | - | | | [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField), [single_field_select.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/components/single_field_select.tsx#:~:text=IndexPatternField)+ 84 more | - | @@ -463,6 +460,7 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [url_state.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/monitoring/public/url_state.ts#:~:text=syncQueryStateWithUrl), [url_state.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/monitoring/public/url_state.ts#:~:text=syncQueryStateWithUrl) | - | | | [legacy_shims.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/monitoring/public/legacy_shims.ts#:~:text=injectedMetadata) | 8.8.0 | | | [bulk_uploader.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts#:~:text=process) | 8.8.0 | @@ -595,9 +593,11 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts#:~:text=create) | - | +| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch) | 8.1 | | | [middleware.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=indexPatterns), [dependencies_start_mock.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts#:~:text=indexPatterns) | - | -| | [use_primary_navigation.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx#:~:text=KibanaPageTemplateProps), [use_primary_navigation.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx#:~:text=KibanaPageTemplateProps), [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx#:~:text=KibanaPageTemplateProps), [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx#:~:text=KibanaPageTemplateProps) | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx#:~:text=KibanaPageTemplate), [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx#:~:text=KibanaPageTemplate) | - | +| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts#:~:text=create) | - | +| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch) | 8.1 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode)+ 2 more | 8.8.0 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode)+ 2 more | 8.8.0 | | | [request_context_factory.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [request_context_factory.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [create_signals_migration_route.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts#:~:text=authc), [delete_signals_migration_route.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts#:~:text=authc), [finalize_signals_migration_route.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts#:~:text=authc), [open_close_signals_route.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts#:~:text=authc), [preview_rules_route.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts#:~:text=authc), [common.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts#:~:text=authc) | - | @@ -634,10 +634,10 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch) | 8.1 | | | [entity_index_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx#:~:text=indexPatterns), [boundary_index_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns), [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx#:~:text=indexPatterns) | - | | | [expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx#:~:text=fieldFormats) | - | -| | [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter) | 8.1 | -| | [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter) | 8.1 | +| | [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter) | 8.1 | +| | [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter) | 8.1 | | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch) | 8.1 | -| | [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter) | 8.1 | +| | [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter) | 8.1 | @@ -645,8 +645,8 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [use_no_data_config.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/use_no_data_config.ts#:~:text=KibanaPageTemplateProps), [use_no_data_config.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/use_no_data_config.ts#:~:text=KibanaPageTemplateProps) | - | -| | [alert_messages.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/uptime_app.tsx#:~:text=RedirectAppLinks) | - | +| | [use_no_data_config.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/app/use_no_data_config.ts#:~:text=KibanaPageTemplateProps), [use_no_data_config.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/app/use_no_data_config.ts#:~:text=KibanaPageTemplateProps), [use_no_data_config.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_no_data_config.ts#:~:text=KibanaPageTemplateProps), [use_no_data_config.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_no_data_config.ts#:~:text=KibanaPageTemplateProps) | - | +| | [alert_messages.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [alert_messages.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/lib/alert_types/alert_messages.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [uptime_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/legacy_uptime/app/uptime_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks), [synthetics_app.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx#:~:text=RedirectAppLinks) | - | @@ -662,22 +662,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract), [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract) | - | -| | [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern) | - | -| | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType)+ 25 more | 8.2 | +| | [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern) | - | +| | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType) | 8.2 | | | [query_string_input.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/query_string_input/query_string_input.tsx#:~:text=indexPatterns) | - | | | [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=esFilters), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=esFilters), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=esFilters) | 8.1 | -| | [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [value.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts#:~:text=KueryNode)+ 2 more | 8.1 | -| | [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=Filter)+ 10 more | 8.1 | -| | [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS) | 8.1 | -| | [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated) | 8.1 | -| | [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=Filter)+ 10 more | 8.1 | -| | [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [value.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts#:~:text=KueryNode)+ 2 more | 8.1 | -| | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType)+ 60 more | 8.2 | -| | [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern)+ 4 more | - | -| | [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract), [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract), [create_index_pattern_select.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/index_pattern_select/create_index_pattern_select.tsx#:~:text=IndexPatternsContract) | - | -| | [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=Filter)+ 10 more | 8.1 | -| | [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [value.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts#:~:text=KueryNode)+ 2 more | 8.1 | +| | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType)+ 8 more | 8.2 | +| | [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IIndexPattern), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IIndexPattern)+ 4 more | - | @@ -762,15 +752,6 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ -## visTypeVega - -| Deprecated API | Reference location(s) | Remove By | -| ---------------|-----------|-----------| -| | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/vega/public/plugin.ts#:~:text=injectedMetadata) | 8.8.0 | -| | [search_api.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/vega/public/data_model/search_api.ts#:~:text=injectedMetadata), [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/vega/public/plugin.ts#:~:text=injectedMetadata) | 8.8.0 | - - - ## visTypeVislib | Deprecated API | Reference location(s) | Remove By | @@ -795,6 +776,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [app.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/app.tsx#:~:text=syncQueryStateWithUrl), [app.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/app.tsx#:~:text=syncQueryStateWithUrl) | - | | | [get_table_columns.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx#:~:text=RedirectAppLinks), [get_table_columns.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx#:~:text=RedirectAppLinks), [get_table_columns.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx#:~:text=RedirectAppLinks) | - | | | [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject) | 8.8.0 | | | [visualize_top_nav.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx#:~:text=onAppLeave), [visualize_editor_common.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx#:~:text=onAppLeave), [app.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/app.tsx#:~:text=onAppLeave), [index.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/index.tsx#:~:text=onAppLeave) | 8.8.0 | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index aab61180e9dc8..816b8a9282d9f 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team summary: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-04-26 +date: 2022-05-23 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- @@ -25,12 +25,8 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| dataViews | | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType)+ 7 more | 8.2 | -| dataViews | | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType), [types.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/types.ts#:~:text=IFieldType)+ 23 more | 8.2 | -| dataEnhanced | | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode) | 8.1 | -| dataEnhanced | | [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=nodeBuilder), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=nodeBuilder), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=nodeBuilder)+ 2 more | 8.1 | -| dataEnhanced | | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode) | 8.1 | -| dataEnhanced | | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/types.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [get_search_session_page.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/get_search_session_page.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [check_non_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/check_non_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode), [expire_persisted_sessions.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts#:~:text=KueryNode) | 8.1 | +| dataViews | | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IFieldType), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IFieldType)+ 7 more | 8.2 | +| dataViews | | [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [utils.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/utils.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [data_view_field.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/data_view_field.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [field_list.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/fields/field_list.ts#:~:text=IFieldType), [index.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/index.ts#:~:text=IFieldType), [data_view.ts](https://github.com/elastic/kibana/tree/master/src/plugins/data_views/common/data_views/data_view.ts#:~:text=IFieldType)+ 23 more | 8.2 | | urlDrilldown | | [context_variables.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts#:~:text=Filter), [context_variables.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts#:~:text=Filter), [url_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx#:~:text=Filter), [url_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx#:~:text=Filter), [data.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts#:~:text=Filter), [data.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts#:~:text=Filter) | 8.1 | | urlDrilldown | | [context_variables.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts#:~:text=Filter), [context_variables.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts#:~:text=Filter), [url_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx#:~:text=Filter), [url_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx#:~:text=Filter), [data.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts#:~:text=Filter), [data.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts#:~:text=Filter) | 8.1 | | urlDrilldown | | [context_variables.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts#:~:text=Filter), [context_variables.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/variables/context_variables.ts#:~:text=Filter), [url_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx#:~:text=Filter), [url_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx#:~:text=Filter), [data.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts#:~:text=Filter), [data.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts#:~:text=Filter) | 8.1 | @@ -42,10 +38,8 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| discover | | [anchor.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/services/anchor.ts#:~:text=fetch), [fetch_hits_in_interval.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts#:~:text=fetch) | 8.1 | | discover | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter) | 8.1 | | discover | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter) | 8.1 | -| discover | | [anchor.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/services/anchor.ts#:~:text=fetch), [fetch_hits_in_interval.ts](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts#:~:text=fetch) | 8.1 | | discover | | [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter), [view_alert_route.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/view_alert/view_alert_route.tsx#:~:text=Filter) | 8.1 | | discover | | [on_save_search.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx#:~:text=SavedObjectSaveModal), [on_save_search.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx#:~:text=SavedObjectSaveModal), [save_modal.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/graph/public/components/save_modal.tsx#:~:text=SavedObjectSaveModal), [save_modal.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/graph/public/components/save_modal.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | | graph | | [plugin.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/graph/server/plugin.ts#:~:text=license%24) | 8.8.0 | @@ -161,13 +155,13 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| stackAlerts | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch) | 8.1 | -| stackAlerts | | [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter) | 8.1 | -| stackAlerts | | [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter) | 8.1 | -| stackAlerts | | [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch) | 8.1 | -| stackAlerts | | [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [read_only_filter_items.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter), [search_source_expression.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx#:~:text=Filter) | 8.1 | +| alerting | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch) | 8.1 | +| alerting | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [fetch_search_source_query.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/lib/fetch_search_source_query.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch), [alert_type.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts#:~:text=fetch) | 8.1 | | alerting | | [plugin.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/plugin.test.ts#:~:text=getKibanaFeatures) | 8.8.0 | | alerting | | [plugin.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/plugin.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions/server/plugin.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions/server/lib/license_state.test.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions/server/lib/license_state.test.ts#:~:text=license%24) | 8.8.0 | +| stackAlerts | | [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter) | 8.1 | +| stackAlerts | | [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter) | 8.1 | +| stackAlerts | | [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter), [search_source_expression_form.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx#:~:text=Filter) | 8.1 | @@ -175,6 +169,8 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| +| securitySolution | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch) | 8.1 | +| securitySolution | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch) | 8.1 | | securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode)+ 2 more | 8.8.0 | | securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode), [isolation.test.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts#:~:text=mode)+ 2 more | 8.8.0 | | securitySolution | | [index.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/app/index.tsx#:~:text=onAppLeave) | 8.8.0 | @@ -204,17 +200,9 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| unifiedSearch | | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType)+ 25 more | 8.2 | +| unifiedSearch | | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType) | 8.2 | | unifiedSearch | | [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=esFilters), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=esFilters), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=esFilters) | 8.1 | -| unifiedSearch | | [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [value.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts#:~:text=KueryNode)+ 2 more | 8.1 | -| unifiedSearch | | [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=Filter)+ 10 more | 8.1 | -| unifiedSearch | | [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=FILTERS) | 8.1 | -| unifiedSearch | | [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated), [filter_editor_utils.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts#:~:text=toggleFilterNegated) | 8.1 | -| unifiedSearch | | [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=Filter)+ 10 more | 8.1 | -| unifiedSearch | | [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [value.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts#:~:text=KueryNode)+ 2 more | 8.1 | -| unifiedSearch | | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [query_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/query_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType)+ 60 more | 8.2 | -| unifiedSearch | | [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [use_filter_manager.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/search_bar/lib/use_filter_manager.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [apply_filter_action.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/actions/apply_filter_action.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [get_stub_filter.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/test_helpers/get_stub_filter.ts#:~:text=Filter), [filter_label.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/filter_bar/filter_editor/lib/filter_label.tsx#:~:text=Filter)+ 10 more | 8.1 | -| unifiedSearch | | [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [conjunction.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [field.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [operator.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts#:~:text=KueryNode), [value.test.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts#:~:text=KueryNode)+ 2 more | 8.1 | +| unifiedSearch | | [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [value.ts](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts#:~:text=IFieldType), [field.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx#:~:text=IFieldType)+ 8 more | 8.2 | @@ -222,9 +210,9 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| lens | | [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject) | 8.8.0 | -| lens | | [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave), [visualize_top_nav.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx#:~:text=onAppLeave), [visualize_editor_common.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx#:~:text=onAppLeave), [app.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/app.tsx#:~:text=onAppLeave), [index.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/index.tsx#:~:text=onAppLeave) | 8.8.0 | -| lens | | [saved_object_migrations.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts#:~:text=warning), [saved_object_migrations.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts#:~:text=warning) | 8.8.0 | -| management | | [application.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/management/public/application.tsx#:~:text=appBasePath) | 8.8.0 | -| visTypeVega | | [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/vega/public/plugin.ts#:~:text=injectedMetadata) | 8.8.0 | -| visTypeVega | | [search_api.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/vega/public/data_model/search_api.ts#:~:text=injectedMetadata), [plugin.ts](https://github.com/elastic/kibana/tree/master/src/plugins/vis_types/vega/public/plugin.ts#:~:text=injectedMetadata) | 8.8.0 | \ No newline at end of file +| visualizations | | [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/utils/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [display_duplicate_title_confirm_modal.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/display_duplicate_title_confirm_modal.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject), [check_for_duplicate_title.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/persistence/saved_objects_utils/check_for_duplicate_title.ts#:~:text=SavedObject) | 8.8.0 | +| visualizations | | [visualize_top_nav.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx#:~:text=onAppLeave), [visualize_editor_common.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx#:~:text=onAppLeave), [app.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/app.tsx#:~:text=onAppLeave), [index.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/visualizations/public/visualize_app/index.tsx#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave) | 8.8.0 | +| lens | | [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter), [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter) | 8.1 | +| lens | | [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter), [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter) | 8.1 | +| lens | | [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter), [open_in_discover_drilldown.tsx](https://github.com/elastic/kibana/tree/master/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx#:~:text=Filter) | 8.1 | +| management | | [application.tsx](https://github.com/elastic/kibana/tree/master/src/plugins/management/public/application.tsx#:~:text=appBasePath) | 8.8.0 | \ No newline at end of file diff --git a/api_docs/kbn_handlebars.devdocs.json b/api_docs/kbn_handlebars.devdocs.json new file mode 100644 index 0000000000000..a7eaa1cd0c13c --- /dev/null +++ b/api_docs/kbn_handlebars.devdocs.json @@ -0,0 +1,153 @@ +{ + "id": "@kbn/handlebars", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/handlebars", + "id": "def-common.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [ + "\nCreates an isolated Handlebars environment.\n\nEach environment has its own helpers.\nThis is only necessary for use cases that demand distinct helpers.\nMost use cases can use the root Handlebars environment directly.\n" + ], + "signature": [ + "() => typeof ", + { + "pluginId": "@kbn/handlebars", + "scope": "common", + "docId": "kibKbnHandlebarsPluginApi", + "section": "def-common.ExtendedHandlebars", + "text": "ExtendedHandlebars" + }, + " & typeof Handlebars" + ], + "path": "packages/kbn-handlebars/src/index.ts", + "deprecated": false, + "children": [], + "returnComment": [ + "A sandboxed/scoped version of the" + ], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/handlebars", + "id": "def-common.compileFnName", + "type": "CompoundType", + "tags": [], + "label": "compileFnName", + "description": [ + "\nIf the `unsafe-eval` CSP is set, this string constant will be `compile`,\notherwise `compileAST`.\n\nThis can be used to call the more optimized `compile` function in\nenvironments that support it, or fall back to `compileAST` on environments\nthat don't." + ], + "signature": [ + "\"compile\" | \"compileAST\"" + ], + "path": "packages/kbn-handlebars/src/index.ts", + "deprecated": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/handlebars", + "id": "def-common.ExtendedCompileOptions", + "type": "Type", + "tags": [], + "label": "ExtendedCompileOptions", + "description": [ + "\nSupported Handlebars compile options.\n\nThis is a subset of all the compile options supported by the upstream\nHandlebars module." + ], + "signature": [ + "{ data?: boolean | undefined; strict?: boolean | undefined; knownHelpers?: KnownHelpers | undefined; knownHelpersOnly?: boolean | undefined; assumeObjects?: boolean | undefined; noEscape?: boolean | undefined; }" + ], + "path": "packages/kbn-handlebars/src/index.ts", + "deprecated": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/handlebars", + "id": "def-common.ExtendedRuntimeOptions", + "type": "Type", + "tags": [], + "label": "ExtendedRuntimeOptions", + "description": [ + "\nSupported Handlebars runtime options\n\nThis is a subset of all the runtime options supported by the upstream\nHandlebars module." + ], + "signature": [ + "{ data?: any; helpers?: { [name: string]: Function; } | undefined; blockParams?: any[] | undefined; }" + ], + "path": "packages/kbn-handlebars/src/index.ts", + "deprecated": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/handlebars", + "id": "def-common.Handlebars", + "type": "CompoundType", + "tags": [], + "label": "Handlebars", + "description": [ + "\nA custom version of the Handlesbars module with an extra `compileAST` function." + ], + "signature": [ + "typeof ", + { + "pluginId": "@kbn/handlebars", + "scope": "common", + "docId": "kibKbnHandlebarsPluginApi", + "section": "def-common.ExtendedHandlebars", + "text": "ExtendedHandlebars" + }, + " & typeof Handlebars" + ], + "path": "packages/kbn-handlebars/src/index.ts", + "deprecated": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/handlebars", + "id": "def-common.ExtendedHandlebars", + "type": "Object", + "tags": [], + "label": "ExtendedHandlebars", + "description": [ + "\nNormally this namespace isn't used directly. It's required to be present by\nTypeScript when calling the `Handlebars.create()` function." + ], + "signature": [ + "typeof ", + { + "pluginId": "@kbn/handlebars", + "scope": "common", + "docId": "kibKbnHandlebarsPluginApi", + "section": "def-common.ExtendedHandlebars", + "text": "ExtendedHandlebars" + } + ], + "path": "packages/kbn-handlebars/src/index.ts", + "deprecated": false, + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx new file mode 100644 index 0000000000000..79d09d800a950 --- /dev/null +++ b/api_docs/kbn_handlebars.mdx @@ -0,0 +1,33 @@ +--- +id: kibKbnHandlebarsPluginApi +slug: /kibana-dev-docs/api/kbn-handlebars +title: "@kbn/handlebars" +image: https://source.unsplash.com/400x175/?github +summary: API docs for the @kbn/handlebars plugin +date: 2022-05-23 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] +warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. +--- +import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 6 | 0 | 0 | 0 | + +## Common + +### Objects + + +### Functions + + +### Consts, variables and types + + diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index f43ac68f5f298..df36a234ba0ce 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -3,7 +3,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory summary: Directory of public APIs available through plugins or packages. -date: 2022-04-26 +date: 2022-05-23 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -12,123 +12,123 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 247 | 201 | 35 | +| 258 | 209 | 35 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 25958 | 170 | 19687 | 1153 | +| 26222 | 171 | 19770 | 870 | ## Plugin Directory | Plugin name           | Maintaining team | Description | API Cnt | Any Cnt | Missing
comments | Missing
exports | |--------------|----------------|-----------|--------------|----------|---------------|--------| -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 195 | 0 | 191 | 11 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 240 | 0 | 235 | 19 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 23 | 0 | 19 | 1 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 325 | 0 | 316 | 19 | -| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 40 | 0 | 40 | 50 | +| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 12 | 0 | 0 | 0 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 359 | 0 | 350 | 19 | +| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 40 | 0 | 40 | 51 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 78 | 1 | 69 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | -| | [ResponseOps](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 71 | 0 | 57 | 19 | +| | [ResponseOps](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 66 | 0 | 52 | 22 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 272 | 2 | 253 | 9 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 28 | 0 | 23 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 29 | 0 | 24 | 0 | | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 14 | 0 | 14 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | -| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 203 | 0 | 197 | 6 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2524 | 15 | 977 | 33 | +| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 205 | 0 | 199 | 7 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2541 | 15 | 977 | 33 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 98 | 0 | 79 | 1 | -| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 142 | 0 | 140 | 12 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 101 | 0 | 82 | 1 | +| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 143 | 0 | 141 | 12 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3414 | 38 | 2802 | 18 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Enhanced data plugin. (See src/plugins/data.) Enhances the main data plugin with a search session management UI. Includes a reusable search session indicator component to use in other applications. Exposes routes for managing search sessions. Includes a service that monitors, updates, and cleans up search session saved objects. | 16 | 0 | 16 | 2 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout from any kibana app | 13 | 0 | 7 | 0 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3482 | 38 | 2869 | 19 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout from any kibana app | 14 | 0 | 7 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Reusable data view field editor across Kibana | 42 | 0 | 37 | 3 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data view management app | 2 | 0 | 2 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 862 | 3 | 710 | 15 | -| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 23 | 2 | 19 | 1 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 866 | 3 | 714 | 15 | +| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 28 | 3 | 24 | 1 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 10 | 0 | 8 | 2 | -| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 76 | 0 | 60 | 7 | +| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 78 | 0 | 62 | 7 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 476 | 0 | 386 | 4 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 486 | 0 | 396 | 4 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | -| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 48 | 0 | 44 | 0 | +| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 44 | 0 | | | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 2 | 0 | 2 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 112 | 3 | 108 | 3 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | The Event Annotation service contains expressions for event annotations | 49 | 0 | 49 | 3 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 91 | 0 | 91 | 9 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | The Event Annotation service contains expressions for event annotations | 90 | 0 | 90 | 5 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 100 | 0 | 100 | 9 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Gauge plugin adds a `gauge` renderer and function to the expression plugin. The renderer will display the `gauge` chart. | 61 | 0 | 61 | 2 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 104 | 0 | 100 | 3 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 107 | 0 | 103 | 3 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'image' function and renderer to expressions | 26 | 0 | 26 | 0 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'metric' function and renderer to expressions | 32 | 0 | 27 | 0 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression MetricVis plugin adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart. | 46 | 0 | 46 | 1 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression MetricVis plugin adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart. | 48 | 0 | 48 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts. | 70 | 0 | 70 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'repeatImage' function and renderer to expressions | 32 | 0 | 32 | 0 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'revealImage' function and renderer to expressions | 14 | 0 | 14 | 3 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'shape' function and renderer to expressions | 148 | 0 | 146 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart. | 7 | 0 | 7 | 0 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart. | 473 | 0 | 463 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds expression runtime to Kibana | 2158 | 17 | 1713 | 5 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart. | 143 | 0 | 133 | 14 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds expression runtime to Kibana | 2176 | 17 | 1722 | 5 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 222 | 0 | 95 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 286 | 6 | 247 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1385 | 8 | 1268 | 10 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1403 | 8 | 1281 | 10 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | graph | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 0 | 0 | 0 | 0 | | grokdebugger | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 140 | 0 | 102 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 141 | 0 | 102 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 175 | 0 | 170 | 3 | -| | [Logs and Metrics UI](https://github.com/orgs/elastic/teams/logs-metrics-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 31 | 0 | 28 | 5 | +| | [Logs and Metrics UI](https://github.com/orgs/elastic/teams/logs-metrics-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 34 | 0 | 31 | 5 | | ingestPipelines | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | inputControlVis | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Input Control visualization to Kibana | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 123 | 2 | 96 | 4 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides UI and APIs for the interactive setup mode. | 28 | 0 | 18 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 240 | 0 | 204 | 5 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 248 | 0 | 212 | 5 | | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 615 | 3 | 420 | 9 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 527 | 0 | 452 | 30 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 574 | 0 | 497 | 35 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 8 | 0 | 8 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 3 | 0 | 3 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | | [Security detections response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 198 | 0 | 90 | 49 | | logstash | [Logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 41 | 0 | 41 | 6 | -| | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 224 | 0 | 223 | 25 | +| | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 226 | 0 | 225 | 25 | | | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 67 | 0 | 67 | 0 | -| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 196 | 8 | 79 | 30 | +| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 254 | 10 | 81 | 31 | | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 11 | 0 | 9 | 1 | | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | -| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 373 | 2 | 370 | 30 | +| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 376 | 2 | 373 | 30 | | | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 13 | 0 | 13 | 0 | | painlessLab | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 228 | 2 | 177 | 11 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Reporting Services enables applications to feature reports that the user can automate with Watcher and download later. | 36 | 0 | 16 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 21 | 0 | 21 | 0 | -| | [RAC](https://github.com/orgs/elastic/teams/rac) | - | 194 | 0 | 167 | 8 | +| | [RAC](https://github.com/orgs/elastic/teams/rac) | - | 202 | 0 | 174 | 9 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 24 | 0 | 19 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 192 | 2 | 151 | 5 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 110 | 0 | 97 | 0 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 54 | 0 | 50 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 76 | 0 | 70 | 3 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 90 | 0 | 45 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 32 | 0 | 13 | 0 | -| | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 7 | 4 | +| | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 28 | 0 | 8 | 4 | | searchprofiler | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 183 | 0 | 103 | 0 | | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 46 | 0 | 46 | 19 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds URL Service and sharing capabilities to Kibana | 113 | 0 | 54 | 10 | | | [Shared UX](https://github.com/orgs/elastic/teams/shared-ux) | A plugin providing components and services for shared user experiences in Kibana. | 4 | 0 | 0 | 0 | -| | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 21 | 1 | 21 | 1 | +| | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 22 | 1 | 22 | 1 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories. | 260 | 0 | 64 | 0 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 4 | 0 | 4 | 0 | | synthetics | [Uptime](https://github.com/orgs/elastic/teams/uptime) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 0 | @@ -137,13 +137,13 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 32 | 0 | 32 | 6 | | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 1 | 0 | 1 | 0 | | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 11 | 0 | 10 | 0 | -| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 434 | 1 | 330 | 35 | +| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 435 | 1 | 331 | 35 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 321 | 0 | 307 | 25 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 374 | 0 | 360 | 34 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 130 | 0 | 91 | 11 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 203 | 0 | 141 | 9 | -| | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 79 | 2 | 75 | 11 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 205 | 0 | 142 | 9 | +| | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 82 | 2 | 78 | 13 | | upgradeAssistant | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | urlDrilldown | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 12 | 0 | 12 | 0 | @@ -162,35 +162,40 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 57 | 0 | 51 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 365 | 12 | 344 | 14 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 372 | 12 | 351 | 14 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory | Package name           | Maintaining team | Description | API Cnt | Any Cnt | Missing
comments | Missing
exports | |--------------|----------------|-----------|--------------|----------|---------------|--------| -| | [Owner missing] | Elastic APM trace data generator | 70 | 0 | 70 | 10 | +| | [Owner missing] | Elastic APM trace data generator | 74 | 0 | 74 | 11 | | | [Owner missing] | - | 11 | 5 | 11 | 0 | | | [Owner missing] | Alerts components and hooks | 9 | 1 | 9 | 0 | | | Ahmad Bamieh ahmadbamieh@gmail.com | Kibana Analytics tool | 69 | 0 | 69 | 2 | -| | [Owner missing] | - | 88 | 1 | 10 | 0 | -| | [Owner missing] | - | 18 | 0 | 13 | 0 | +| | [Owner missing] | - | 95 | 0 | 7 | 0 | +| | [Owner missing] | - | 18 | 0 | 13 | 0 | +| | [Owner missing] | - | 22 | 0 | 13 | 0 | +| | [Owner missing] | - | 18 | 0 | 13 | 0 | +| | [Owner missing] | - | 20 | 0 | 4 | 0 | | | [Owner missing] | - | 16 | 0 | 16 | 0 | | | [Owner missing] | - | 11 | 0 | 11 | 0 | | | [Owner missing] | - | 10 | 0 | 10 | 0 | | | [Owner missing] | - | 18 | 0 | 9 | 1 | | | [Owner missing] | - | 4 | 0 | 4 | 0 | -| | [Owner missing] | - | 8 | 0 | 7 | 0 | | | [Owner missing] | - | 7 | 0 | 2 | 0 | -| | [Owner missing] | - | 59 | 0 | 15 | 0 | +| | [Owner missing] | - | 60 | 0 | 15 | 0 | | | [Owner missing] | - | 2 | 0 | 2 | 0 | | | [Owner missing] | - | 106 | 0 | 80 | 1 | -| | [Owner missing] | - | 64 | 0 | 44 | 2 | +| | [Owner missing] | - | 65 | 0 | 44 | 2 | | | [Owner missing] | - | 129 | 3 | 127 | 17 | | | [Owner missing] | - | 13 | 0 | 7 | 0 | | | [Owner missing] | elasticsearch datemath parser, used in kibana | 44 | 0 | 43 | 0 | -| | [Owner missing] | - | 120 | 3 | 109 | 0 | -| | [Owner missing] | - | 64 | 0 | 64 | 2 | +| | [Owner missing] | - | 9 | 1 | 9 | 0 | +| | [Owner missing] | - | 65 | 0 | 64 | 0 | +| | [Owner missing] | - | 15 | 0 | 9 | 0 | +| | [Owner missing] | - | 31 | 2 | 27 | 0 | +| | [Owner missing] | - | 65 | 0 | 65 | 2 | | | [Owner missing] | - | 1 | 0 | 1 | 0 | | | [Owner missing] | - | 27 | 0 | 14 | 1 | | | [Owner missing] | - | 213 | 1 | 159 | 11 | @@ -198,6 +203,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Owner missing] | - | 20 | 0 | 16 | 0 | | | [Owner missing] | - | 2 | 0 | 0 | 0 | | | [Owner missing] | - | 1 | 0 | 0 | 0 | +| | [Owner missing] | - | 6 | 0 | 0 | 0 | | | [Owner missing] | - | 51 | 0 | 48 | 0 | | | [Owner missing] | - | 43 | 0 | 36 | 0 | | | App Services | - | 35 | 4 | 35 | 0 | @@ -208,11 +214,12 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Owner missing] | - | 8 | 0 | 8 | 0 | | | [Owner missing] | - | 494 | 1 | 1 | 0 | | | [Owner missing] | - | 55 | 0 | 55 | 2 | -| | [Owner missing] | - | 45 | 0 | 45 | 10 | +| | [Owner missing] | - | 47 | 0 | 46 | 10 | +| | [Owner missing] | A library to convert APM traces into JSON format for performance testing. | 3 | 0 | 3 | 0 | | | [Owner missing] | - | 30 | 0 | 29 | 0 | | | [Owner missing] | - | 1 | 0 | 1 | 0 | | | [Owner missing] | Just some helpers for kibana plugin devs. | 1 | 0 | 1 | 0 | -| | [Owner missing] | - | 47 | 0 | 35 | 5 | +| | [Owner missing] | - | 45 | 0 | 33 | 5 | | | [Owner missing] | - | 21 | 0 | 10 | 0 | | | [Owner missing] | - | 74 | 0 | 71 | 0 | | | [Owner missing] | Security Solution auto complete | 50 | 1 | 35 | 0 | @@ -232,17 +239,18 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Owner missing] | - | 53 | 0 | 50 | 1 | | | [Owner missing] | - | 25 | 0 | 24 | 1 | | | [Owner missing] | - | 12 | 0 | 2 | 3 | -| | [Owner missing] | - | 34 | 0 | 6 | 6 | -| | [Owner missing] | - | 67 | 0 | 43 | 0 | -| | [Owner missing] | - | 10 | 0 | 2 | 0 | +| | [Owner missing] | - | 23 | 0 | 6 | 3 | +| | [Owner missing] | - | 8 | 0 | 2 | 3 | +| | [Owner missing] | - | 78 | 0 | 49 | 2 | +| | [Owner missing] | - | 16 | 0 | 7 | 0 | | | [Owner missing] | - | 9 | 0 | 3 | 0 | | | [Owner missing] | - | 2 | 0 | 2 | 0 | | | [Owner missing] | - | 92 | 1 | 59 | 1 | | | [Owner missing] | - | 4 | 0 | 2 | 0 | -| | Operations | - | 22 | 2 | 21 | 0 | +| | Operations | - | 38 | 2 | 21 | 0 | | | [Owner missing] | - | 2 | 0 | 2 | 0 | -| | Operations | - | 252 | 6 | 214 | 9 | -| | [Owner missing] | - | 132 | 8 | 103 | 2 | +| | Operations | - | 240 | 5 | 201 | 9 | +| | [Owner missing] | - | 135 | 8 | 103 | 2 | | | [Owner missing] | - | 72 | 0 | 55 | 0 | | | [Owner missing] | - | 29 | 0 | 2 | 0 | | | [Owner missing] | - | 83 | 0 | 83 | 1 | diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 79421e1ac90b5..24d62505e99e8 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -26,12 +26,19 @@ which may cause a delay before pages start being served. Set to `false` to disable Console. *Default: `true`* | `csp.rules:` - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] +| deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] A https://w3c.github.io/webappsec-csp/[Content Security Policy] template that disables certain unnecessary and potentially insecure capabilities in the browser. It is strongly recommended that you keep the default CSP rules that ship with {kib}. +| `csp.disableUnsafeEval` +| experimental[] Set this to `true` to remove the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions[`unsafe-eval`] source expression from the `script-src` directive. *Default: `false`* + +By enabling `csp.disableUnsafeEval`, Kibana will use a custom version of the Handlebars template library which doesn't support https://handlebarsjs.com/guide/partials.html#inline-partials[inline partials]. +Handlebars is used in various locations in the Kibana frontend where custom templates can be supplied by the user when for instance setting up a visualisation. +If you experience any issues rendering Handlebars templates after turning on `csp.disableUnsafeEval`, or if you rely on inline partials, please revert this setting to `false` and https://github.com/elastic/kibana/issues/new/choose[open an issue] in the Kibana GitHub repository. + | `csp.script_src:` | Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src[Content Security Policy `script-src` directive]. diff --git a/package.json b/package.json index 7cc2500e8dd6e..f5c8a68efbf30 100644 --- a/package.json +++ b/package.json @@ -152,6 +152,7 @@ "@kbn/eslint-plugin-imports": "link:bazel-bin/packages/kbn-eslint-plugin-imports", "@kbn/field-types": "link:bazel-bin/packages/kbn-field-types", "@kbn/flot-charts": "link:bazel-bin/packages/kbn-flot-charts", + "@kbn/handlebars": "link:bazel-bin/packages/kbn-handlebars", "@kbn/i18n": "link:bazel-bin/packages/kbn-i18n", "@kbn/i18n-react": "link:bazel-bin/packages/kbn-i18n-react", "@kbn/interpreter": "link:bazel-bin/packages/kbn-interpreter", @@ -646,6 +647,7 @@ "@types/kbn__field-types": "link:bazel-bin/packages/kbn-field-types/npm_module_types", "@types/kbn__find-used-node-modules": "link:bazel-bin/packages/kbn-find-used-node-modules/npm_module_types", "@types/kbn__generate": "link:bazel-bin/packages/kbn-generate/npm_module_types", + "@types/kbn__handlebars": "link:bazel-bin/packages/kbn-handlebars/npm_module_types", "@types/kbn__i18n": "link:bazel-bin/packages/kbn-i18n/npm_module_types", "@types/kbn__i18n-react": "link:bazel-bin/packages/kbn-i18n-react/npm_module_types", "@types/kbn__import-resolver": "link:bazel-bin/packages/kbn-import-resolver/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 51db32d5d89f7..cc8925bc777c2 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -53,6 +53,7 @@ filegroup( "//packages/kbn-find-used-node-modules:build", "//packages/kbn-flot-charts:build", "//packages/kbn-generate:build", + "//packages/kbn-handlebars:build", "//packages/kbn-i18n-react:build", "//packages/kbn-i18n:build", "//packages/kbn-import-resolver:build", @@ -159,6 +160,7 @@ filegroup( "//packages/kbn-field-types:build_types", "//packages/kbn-find-used-node-modules:build_types", "//packages/kbn-generate:build_types", + "//packages/kbn-handlebars:build_types", "//packages/kbn-i18n-react:build_types", "//packages/kbn-i18n:build_types", "//packages/kbn-import-resolver:build_types", diff --git a/packages/kbn-handlebars/.gitignore b/packages/kbn-handlebars/.gitignore new file mode 100644 index 0000000000000..d36977dc47615 --- /dev/null +++ b/packages/kbn-handlebars/.gitignore @@ -0,0 +1 @@ +.tmp diff --git a/packages/kbn-handlebars/.patches/basic.patch b/packages/kbn-handlebars/.patches/basic.patch new file mode 100644 index 0000000000000..90027eedf0f40 --- /dev/null +++ b/packages/kbn-handlebars/.patches/basic.patch @@ -0,0 +1,612 @@ +1,11c1,21 +< global.handlebarsEnv = null; +< +< beforeEach(function() { +< global.handlebarsEnv = Handlebars.create(); +< }); +< +< describe('basic context', function() { +< it('most basic', function() { +< expectTemplate('{{foo}}') +< .withInput({ foo: 'foo' }) +< .toCompileTo('foo'); +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import Handlebars from '..'; +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('basic context', () => { +> it('most basic', () => { +> expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo'); +> }); +> +> it('escaping', () => { +> expectTemplate('\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('{{foo}}'); +> expectTemplate('content \\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content {{foo}}'); +> expectTemplate('\\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('\\food'); +> expectTemplate('content \\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content \\food'); +> expectTemplate('\\\\ {{foo}}').withInput({ foo: 'food' }).toCompileTo('\\\\ food'); +14,36c24 +< it('escaping', function() { +< expectTemplate('\\{{foo}}') +< .withInput({ foo: 'food' }) +< .toCompileTo('{{foo}}'); +< +< expectTemplate('content \\{{foo}}') +< .withInput({ foo: 'food' }) +< .toCompileTo('content {{foo}}'); +< +< expectTemplate('\\\\{{foo}}') +< .withInput({ foo: 'food' }) +< .toCompileTo('\\food'); +< +< expectTemplate('content \\\\{{foo}}') +< .withInput({ foo: 'food' }) +< .toCompileTo('content \\food'); +< +< expectTemplate('\\\\ {{foo}}') +< .withInput({ foo: 'food' }) +< .toCompileTo('\\\\ food'); +< }); +< +< it('compiling with a basic context', function() { +--- +> it('compiling with a basic context', () => { +40c28 +< world: 'world' +--- +> world: 'world', +42d29 +< .withMessage('It works if all the required keys are provided') +46,49c33,34 +< it('compiling with a string context', function() { +< expectTemplate('{{.}}{{length}}') +< .withInput('bye') +< .toCompileTo('bye3'); +--- +> it('compiling with a string context', () => { +> expectTemplate('{{.}}{{length}}').withInput('bye').toCompileTo('bye3'); +52c37 +< it('compiling with an undefined context', function() { +--- +> it('compiling with an undefined context', () => { +62c47 +< it('comments', function() { +--- +> it('comments', () => { +66c51 +< world: 'world' +--- +> world: 'world', +68d52 +< .withMessage('comments are ignored') +72,76c56 +< +< expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo( +< 'blah' +< ); +< +--- +> expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo('blah'); +78,82c58 +< +< expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo( +< ' blah' +< ); +< +--- +> expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(' blah'); +84,87c60 +< +< expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo( +< ' blah' +< ); +--- +> expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(' blah'); +90,91c63,64 +< it('boolean', function() { +< var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; +--- +> it('boolean', () => { +> const string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; +95c68 +< world: 'world' +--- +> world: 'world', +97d69 +< .withMessage('booleans show the contents when true') +103c75 +< world: 'world' +--- +> world: 'world', +105d76 +< .withMessage('booleans do not show the contents when false') +109c80 +< it('zeros', function() { +--- +> it('zeros', () => { +113c84 +< num2: 0 +--- +> num2: 0, +117,119c88 +< expectTemplate('num: {{.}}') +< .withInput(0) +< .toCompileTo('num: 0'); +--- +> expectTemplate('num: {{.}}').withInput(0).toCompileTo('num: 0'); +126c95 +< it('false', function() { +--- +> it('false', () => { +131c100 +< val2: new Boolean(false) +--- +> val2: new Boolean(false), +135,137c104 +< expectTemplate('val: {{.}}') +< .withInput(false) +< .toCompileTo('val: false'); +--- +> expectTemplate('val: {{.}}').withInput(false).toCompileTo('val: false'); +146c113 +< val2: new Boolean(false) +--- +> val2: new Boolean(false), +156c123 +< it('should handle undefined and null', function() { +--- +> it('should handle undefined and null', () => { +159,167c126,128 +< awesome: function(_undefined, _null, options) { +< return ( +< (_undefined === undefined) + +< ' ' + +< (_null === null) + +< ' ' + +< typeof options +< ); +< } +--- +> awesome(_undefined: any, _null: any, options: any) { +> return (_undefined === undefined) + ' ' + (_null === null) + ' ' + typeof options; +> }, +173c134 +< undefined: function() { +--- +> undefined() { +175c136 +< } +--- +> }, +181c142 +< null: function() { +--- +> null() { +183c144 +< } +--- +> }, +188c149 +< it('newlines', function() { +--- +> it('newlines', () => { +190d150 +< +194,223c154,160 +< it('escaping text', function() { +< expectTemplate("Awesome's") +< .withMessage( +< "text is escaped so that it doesn't get caught on single quotes" +< ) +< .toCompileTo("Awesome's"); +< +< expectTemplate('Awesome\\') +< .withMessage("text is escaped so that the closing quote can't be ignored") +< .toCompileTo('Awesome\\'); +< +< expectTemplate('Awesome\\\\ foo') +< .withMessage("text is escaped so that it doesn't mess up backslashes") +< .toCompileTo('Awesome\\\\ foo'); +< +< expectTemplate('Awesome {{foo}}') +< .withInput({ foo: '\\' }) +< .withMessage("text is escaped so that it doesn't mess up backslashes") +< .toCompileTo('Awesome \\'); +< +< expectTemplate(" ' ' ") +< .withMessage('double quotes never produce invalid javascript') +< .toCompileTo(" ' ' "); +< }); +< +< it('escaping expressions', function() { +< expectTemplate('{{{awesome}}}') +< .withInput({ awesome: "&'\\<>" }) +< .withMessage("expressions with 3 handlebars aren't escaped") +< .toCompileTo("&'\\<>"); +--- +> it('escaping text', () => { +> expectTemplate("Awesome's").toCompileTo("Awesome's"); +> expectTemplate('Awesome\\').toCompileTo('Awesome\\'); +> expectTemplate('Awesome\\\\ foo').toCompileTo('Awesome\\\\ foo'); +> expectTemplate('Awesome {{foo}}').withInput({ foo: '\\' }).toCompileTo('Awesome \\'); +> expectTemplate(" ' ' ").toCompileTo(" ' ' "); +> }); +225,228c162,165 +< expectTemplate('{{&awesome}}') +< .withInput({ awesome: "&'\\<>" }) +< .withMessage("expressions with {{& handlebars aren't escaped") +< .toCompileTo("&'\\<>"); +--- +> it('escaping expressions', () => { +> expectTemplate('{{{awesome}}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); +> +> expectTemplate('{{&awesome}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); +232d168 +< .withMessage('by default expressions should be escaped') +237d172 +< .withMessage('escaping should properly handle amperstands') +241c176 +< it("functions returning safestrings shouldn't be escaped", function() { +--- +> it("functions returning safestrings shouldn't be escaped", () => { +244c179 +< awesome: function() { +--- +> awesome() { +246c181 +< } +--- +> }, +248d182 +< .withMessage("functions returning safestrings aren't escaped") +252c186 +< it('functions', function() { +--- +> it('functions', () => { +255c189 +< awesome: function() { +--- +> awesome() { +257c191 +< } +--- +> }, +259d192 +< .withMessage('functions are called and render their output') +264c197 +< awesome: function() { +--- +> awesome() { +267c200 +< more: 'More awesome' +--- +> more: 'More awesome', +269d201 +< .withMessage('functions are bound to the context') +273c205 +< it('functions with context argument', function() { +--- +> it('functions with context argument', () => { +276c208 +< awesome: function(context) { +--- +> awesome(context: any) { +279c211 +< frank: 'Frank' +--- +> frank: 'Frank', +281d212 +< .withMessage('functions are called with context arguments') +285c216 +< it('pathed functions with context argument', function() { +--- +> it('pathed functions with context argument', () => { +289c220 +< awesome: function(context) { +--- +> awesome(context: any) { +291c222 +< } +--- +> }, +293c224 +< frank: 'Frank' +--- +> frank: 'Frank', +295d225 +< .withMessage('functions are called with context arguments') +299c229 +< it('depthed functions with context argument', function() { +--- +> it('depthed functions with context argument', () => { +302c232 +< awesome: function(context) { +--- +> awesome(context: any) { +305c235 +< frank: 'Frank' +--- +> frank: 'Frank', +307d236 +< .withMessage('functions are called with context arguments') +311c240 +< it('block functions with context argument', function() { +--- +> it('block functions with context argument', () => { +314c243 +< awesome: function(context, options) { +--- +> awesome(context: any, options: any) { +316c245 +< } +--- +> }, +318d246 +< .withMessage('block functions are called with context and options') +322,325c250,251 +< it('depthed block functions with context argument', function() { +< expectTemplate( +< '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}' +< ) +--- +> it('depthed block functions with context argument', () => { +> expectTemplate('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}') +328c254 +< awesome: function(context, options) { +--- +> awesome(context: any, options: any) { +330c256 +< } +--- +> }, +332d257 +< .withMessage('block functions are called with context and options') +336c261 +< it('block functions without context argument', function() { +--- +> it('block functions without context argument', () => { +339c264 +< awesome: function(options) { +--- +> awesome(options: any) { +341c266 +< } +--- +> }, +343d267 +< .withMessage('block functions are called with options') +347c271 +< it('pathed block functions without context argument', function() { +--- +> it('pathed block functions without context argument', () => { +351c275 +< awesome: function() { +--- +> awesome() { +353,354c277,278 +< } +< } +--- +> }, +> }, +356d279 +< .withMessage('block functions are called with options') +360,363c283,284 +< it('depthed block functions without context argument', function() { +< expectTemplate( +< '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}' +< ) +--- +> it('depthed block functions without context argument', () => { +> expectTemplate('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}') +366c287 +< awesome: function() { +--- +> awesome() { +368c289 +< } +--- +> }, +370d290 +< .withMessage('block functions are called with options') +374,378c294,295 +< it('paths with hyphens', function() { +< expectTemplate('{{foo-bar}}') +< .withInput({ 'foo-bar': 'baz' }) +< .withMessage('Paths can contain hyphens (-)') +< .toCompileTo('baz'); +--- +> it('paths with hyphens', () => { +> expectTemplate('{{foo-bar}}').withInput({ 'foo-bar': 'baz' }).toCompileTo('baz'); +382d298 +< .withMessage('Paths can contain hyphens (-)') +387d302 +< .withMessage('Paths can contain hyphens (-)') +391c306 +< it('nested paths', function() { +--- +> it('nested paths', () => { +394d308 +< .withMessage('Nested paths access nested objects') +398c312 +< it('nested paths with empty string value', function() { +--- +> it('nested paths with empty string value', () => { +401d314 +< .withMessage('Nested paths access nested objects with empty string') +405c318 +< it('literal paths', function() { +--- +> it('literal paths', () => { +408d320 +< .withMessage('Literal paths can be used') +413d324 +< .withMessage('Literal paths can be used') +417c328 +< it('literal references', function() { +--- +> it('literal references', () => { +443c354 +< it("that current context path ({{.}}) doesn't hit helpers", function() { +--- +> it("that current context path ({{.}}) doesn't hit helpers", () => { +445a357 +> // @ts-expect-error Setting the helper to a string instead of a function doesn't make sense normally, but here it doesn't matter +450c362 +< it('complex but empty paths', function() { +--- +> it('complex but empty paths', () => { +455,457c367 +< expectTemplate('{{person/name}}') +< .withInput({ person: {} }) +< .toCompileTo(''); +--- +> expectTemplate('{{person/name}}').withInput({ person: {} }).toCompileTo(''); +460c370 +< it('this keyword in paths', function() { +--- +> it('this keyword in paths', () => { +463d372 +< .withMessage('This keyword in paths evaluates to current context') +468c377 +< hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] +--- +> hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], +470d378 +< .withMessage('This keyword evaluates in more complex paths') +474c382 +< it('this keyword nested inside path', function() { +--- +> it('this keyword nested inside path', () => { +476d383 +< Error, +480,482c387 +< expectTemplate('{{[this]}}') +< .withInput({ this: 'bar' }) +< .toCompileTo('bar'); +--- +> expectTemplate('{{[this]}}').withInput({ this: 'bar' }).toCompileTo('bar'); +489,491c394,396 +< it('this keyword in helpers', function() { +< var helpers = { +< foo: function(value) { +--- +> it('this keyword in helpers', () => { +> const helpers = { +> foo(value: any) { +493c398 +< } +--- +> }, +499d403 +< .withMessage('This keyword in paths evaluates to current context') +504c408 +< hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] +--- +> hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], +507d410 +< .withMessage('This keyword evaluates in more complex paths') +511c414 +< it('this keyword nested inside helpers param', function() { +--- +> it('this keyword nested inside helpers param', () => { +513d415 +< Error, +519c421 +< foo: function(value) { +--- +> foo(value: any) { +522c424 +< this: 'bar' +--- +> this: 'bar', +528c430 +< foo: function(value) { +--- +> foo(value: any) { +531c433 +< text: { this: 'bar' } +--- +> text: { this: 'bar' }, +536c438 +< it('pass string literals', function() { +--- +> it('pass string literals', () => { +538,541c440 +< +< expectTemplate('{{"foo"}}') +< .withInput({ foo: 'bar' }) +< .toCompileTo('bar'); +--- +> expectTemplate('{{"foo"}}').withInput({ foo: 'bar' }).toCompileTo('bar'); +545c444 +< foo: ['bar', 'baz'] +--- +> foo: ['bar', 'baz'], +550c449 +< it('pass number literals', function() { +--- +> it('pass number literals', () => { +552,556c451 +< +< expectTemplate('{{12}}') +< .withInput({ '12': 'bar' }) +< .toCompileTo('bar'); +< +--- +> expectTemplate('{{12}}').withInput({ '12': 'bar' }).toCompileTo('bar'); +558,562c453 +< +< expectTemplate('{{12.34}}') +< .withInput({ '12.34': 'bar' }) +< .toCompileTo('bar'); +< +--- +> expectTemplate('{{12.34}}').withInput({ '12.34': 'bar' }).toCompileTo('bar'); +565c456 +< '12.34': function(arg) { +--- +> '12.34'(arg: any) { +567c458 +< } +--- +> }, +572c463 +< it('pass boolean literals', function() { +--- +> it('pass boolean literals', () => { +574,581c465,466 +< +< expectTemplate('{{true}}') +< .withInput({ '': 'foo' }) +< .toCompileTo(''); +< +< expectTemplate('{{false}}') +< .withInput({ false: 'foo' }) +< .toCompileTo('foo'); +--- +> expectTemplate('{{true}}').withInput({ '': 'foo' }).toCompileTo(''); +> expectTemplate('{{false}}').withInput({ false: 'foo' }).toCompileTo('foo'); +584c469 +< it('should handle literals in subexpression', function() { +--- +> it('should handle literals in subexpression', () => { +587c472 +< false: function() { +--- +> false() { +589c474 +< } +--- +> }, +591c476 +< .withHelper('foo', function(arg) { +--- +> .withHelper('foo', function (arg) { diff --git a/packages/kbn-handlebars/.patches/blocks.patch b/packages/kbn-handlebars/.patches/blocks.patch new file mode 100644 index 0000000000000..731ee90e6e9b8 --- /dev/null +++ b/packages/kbn-handlebars/.patches/blocks.patch @@ -0,0 +1,461 @@ +1,3c1,12 +< describe('blocks', function() { +< it('array', function() { +< var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('blocks', () => { +> it('array', () => { +> const string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; +7,12c16,17 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +14d18 +< .withMessage('Arrays iterate over the contents when not empty') +20c24 +< world: 'world' +--- +> world: 'world', +22d25 +< .withMessage('Arrays ignore the contents when empty') +26,29c29,30 +< it('array without data', function() { +< expectTemplate( +< '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}' +< ) +--- +> it('array without data', () => { +> expectTemplate('{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}') +31,36c32,33 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +38d34 +< .withCompileOptions({ compat: false }) +42,45c38,39 +< it('array with @index', function() { +< expectTemplate( +< '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!' +< ) +--- +> it('array with @index', () => { +> expectTemplate('{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!') +47,52c41,42 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +54d43 +< .withMessage('The @index variable is used') +58,59c47,48 +< it('empty block', function() { +< var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; +--- +> it('empty block', () => { +> const string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; +63,68c52,53 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +70d54 +< .withMessage('Arrays iterate over the contents when not empty') +76c60 +< world: 'world' +--- +> world: 'world', +78d61 +< .withMessage('Arrays ignore the contents when empty') +82c65 +< it('block with complex lookup', function() { +--- +> it('block with complex lookup', () => { +86,90c69 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ] +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +92,97c71 +< .withMessage( +< 'Templates can access variables in contexts up the stack with relative path syntax' +< ) +< .toCompileTo( +< 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ' +< ); +--- +> .toCompileTo('goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '); +100c74 +< it('multiple blocks with complex lookup', function() { +--- +> it('multiple blocks with complex lookup', () => { +104,108c78 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ] +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +113,116c83,84 +< it('block with complex lookup using nested context', function() { +< expectTemplate( +< '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}' +< ).toThrow(Error); +--- +> it('block with complex lookup using nested context', () => { +> expectTemplate('{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}').toThrow(Error); +119c87 +< it('block with deep nested complex lookup', function() { +--- +> it('block with deep nested complex lookup', () => { +125c93 +< outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] +--- +> outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }], +130,133c98,99 +< it('works with cached blocks', function() { +< expectTemplate( +< '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}' +< ) +--- +> it('works with cached blocks', () => { +> expectTemplate('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}') +138,139c104,105 +< { first: 'Alan', last: 'Johnson' } +< ] +--- +> { first: 'Alan', last: 'Johnson' }, +> ], +144,145c110,111 +< describe('inverted sections', function() { +< it('inverted sections with unset value', function() { +--- +> describe('inverted sections', () => { +> it('inverted sections with unset value', () => { +148,150c114 +< ) +< .withMessage("Inverted section rendered when value isn't set.") +< .toCompileTo('Right On!'); +--- +> ).toCompileTo('Right On!'); +153,156c117,118 +< it('inverted section with false value', function() { +< expectTemplate( +< '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' +< ) +--- +> it('inverted section with false value', () => { +> expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') +158d119 +< .withMessage('Inverted section rendered when value is false.') +162,165c123,124 +< it('inverted section with empty set', function() { +< expectTemplate( +< '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' +< ) +--- +> it('inverted section with empty set', () => { +> expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') +167d125 +< .withMessage('Inverted section rendered when value is empty set.') +171c129 +< it('block inverted sections', function() { +--- +> it('block inverted sections', () => { +177c135 +< it('chained inverted sections', function() { +--- +> it('chained inverted sections', () => { +188,190c146 +< expectTemplate( +< '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}' +< ) +--- +> expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}') +195,198c151,152 +< it('chained inverted sections with mismatch', function() { +< expectTemplate( +< '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}' +< ).toThrow(Error); +--- +> it('chained inverted sections with mismatch', () => { +> expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}').toThrow(Error); +201c155 +< it('block inverted sections with empty arrays', function() { +--- +> it('block inverted sections with empty arrays', () => { +205c159 +< people: [] +--- +> people: [], +211,212c165,166 +< describe('standalone sections', function() { +< it('block standalone else sections', function() { +--- +> describe('standalone sections', () => { +> it('block standalone else sections', () => { +226,241c180,181 +< it('block standalone else sections can be disabled', function() { +< expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') +< .withInput({ none: 'No people' }) +< .withCompileOptions({ ignoreStandalone: true }) +< .toCompileTo('\nNo people\n\n'); +< +< expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n') +< .withInput({ none: 'No people' }) +< .withCompileOptions({ ignoreStandalone: true }) +< .toCompileTo('\nNo people\n\n'); +< }); +< +< it('block standalone chained else sections', function() { +< expectTemplate( +< '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n' +< ) +--- +> it('block standalone chained else sections', () => { +> expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n') +245,247c185 +< expectTemplate( +< '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n' +< ) +--- +> expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n') +252c190 +< it('should handle nesting', function() { +--- +> it('should handle nesting', () => { +255c193 +< data: [1, 3, 5] +--- +> data: [1, 3, 5], +260,455d197 +< +< describe('compat mode', function() { +< it('block with deep recursive lookup lookup', function() { +< expectTemplate( +< '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}' +< ) +< .withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] }) +< .withCompileOptions({ compat: true }) +< .toCompileTo('Goodbye cruel OMG!'); +< }); +< +< it('block with deep recursive pathed lookup', function() { +< expectTemplate( +< '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' +< ) +< .withInput({ +< omg: { yes: 'OMG!' }, +< outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] +< }) +< .withCompileOptions({ compat: true }) +< .toCompileTo('Goodbye cruel OMG!'); +< }); +< +< it('block with missed recursive lookup', function() { +< expectTemplate( +< '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' +< ) +< .withInput({ +< omg: { no: 'OMG!' }, +< outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] +< }) +< .withCompileOptions({ compat: true }) +< .toCompileTo('Goodbye cruel '); +< }); +< }); +< +< describe('decorators', function() { +< it('should apply mustache decorators', function() { +< expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') +< .withHelper('helper', function(options) { +< return options.fn.run; +< }) +< .withDecorator('decorator', function(fn) { +< fn.run = 'success'; +< return fn; +< }) +< .toCompileTo('success'); +< }); +< +< it('should apply allow undefined return', function() { +< expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}') +< .withHelper('helper', function(options) { +< return options.fn() + options.fn.run; +< }) +< .withDecorator('decorator', function(fn) { +< fn.run = 'cess'; +< }) +< .toCompileTo('success'); +< }); +< +< it('should apply block decorators', function() { +< expectTemplate( +< '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}' +< ) +< .withHelper('helper', function(options) { +< return options.fn.run; +< }) +< .withDecorator('decorator', function(fn, props, container, options) { +< fn.run = options.fn(); +< return fn; +< }) +< .toCompileTo('success'); +< }); +< +< it('should support nested decorators', function() { +< expectTemplate( +< '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}' +< ) +< .withHelper('helper', function(options) { +< return options.fn.run; +< }) +< .withDecorators({ +< decorator: function(fn, props, container, options) { +< fn.run = options.fn.nested + options.fn(); +< return fn; +< }, +< nested: function(fn, props, container, options) { +< props.nested = options.fn(); +< } +< }) +< .toCompileTo('success'); +< }); +< +< it('should apply multiple decorators', function() { +< expectTemplate( +< '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}' +< ) +< .withHelper('helper', function(options) { +< return options.fn.run; +< }) +< .withDecorator('decorator', function(fn, props, container, options) { +< fn.run = (fn.run || '') + options.fn(); +< return fn; +< }) +< .toCompileTo('success'); +< }); +< +< it('should access parent variables', function() { +< expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') +< .withHelper('helper', function(options) { +< return options.fn.run; +< }) +< .withDecorator('decorator', function(fn, props, container, options) { +< fn.run = options.args; +< return fn; +< }) +< .withInput({ foo: 'success' }) +< .toCompileTo('success'); +< }); +< +< it('should work with root program', function() { +< var run; +< expectTemplate('{{*decorator "success"}}') +< .withDecorator('decorator', function(fn, props, container, options) { +< equals(options.args[0], 'success'); +< run = true; +< return fn; +< }) +< .withInput({ foo: 'success' }) +< .toCompileTo(''); +< equals(run, true); +< }); +< +< it('should fail when accessing variables from root', function() { +< var run; +< expectTemplate('{{*decorator foo}}') +< .withDecorator('decorator', function(fn, props, container, options) { +< equals(options.args[0], undefined); +< run = true; +< return fn; +< }) +< .withInput({ foo: 'fail' }) +< .toCompileTo(''); +< equals(run, true); +< }); +< +< describe('registration', function() { +< it('unregisters', function() { +< handlebarsEnv.decorators = {}; +< +< handlebarsEnv.registerDecorator('foo', function() { +< return 'fail'; +< }); +< +< equals(!!handlebarsEnv.decorators.foo, true); +< handlebarsEnv.unregisterDecorator('foo'); +< equals(handlebarsEnv.decorators.foo, undefined); +< }); +< +< it('allows multiple globals', function() { +< handlebarsEnv.decorators = {}; +< +< handlebarsEnv.registerDecorator({ +< foo: function() {}, +< bar: function() {} +< }); +< +< equals(!!handlebarsEnv.decorators.foo, true); +< equals(!!handlebarsEnv.decorators.bar, true); +< handlebarsEnv.unregisterDecorator('foo'); +< handlebarsEnv.unregisterDecorator('bar'); +< equals(handlebarsEnv.decorators.foo, undefined); +< equals(handlebarsEnv.decorators.bar, undefined); +< }); +< +< it('fails with multiple and args', function() { +< shouldThrow( +< function() { +< handlebarsEnv.registerDecorator( +< { +< world: function() { +< return 'world!'; +< }, +< testHelper: function() { +< return 'found it!'; +< } +< }, +< {} +< ); +< }, +< Error, +< 'Arg not supported with multiple decorators' +< ); +< }); +< }); +< }); diff --git a/packages/kbn-handlebars/.patches/builtins.patch b/packages/kbn-handlebars/.patches/builtins.patch new file mode 100644 index 0000000000000..64e3294009a85 --- /dev/null +++ b/packages/kbn-handlebars/.patches/builtins.patch @@ -0,0 +1,872 @@ +1,4c1,16 +< describe('builtin helpers', function() { +< describe('#if', function() { +< it('if', function() { +< var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> /* eslint-disable max-classes-per-file */ +> +> import Handlebars from '..'; +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('builtin helpers', () => { +> describe('#if', () => { +> it('if', () => { +> const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; +9c21 +< world: 'world' +--- +> world: 'world', +11d22 +< .withMessage('if with boolean argument shows the contents when true') +17c28 +< world: 'world' +--- +> world: 'world', +19d29 +< .withMessage('if with string argument shows the contents') +25c35 +< world: 'world' +--- +> world: 'world', +27,29d36 +< .withMessage( +< 'if with boolean argument does not show the contents when false' +< ) +32,35c39 +< expectTemplate(string) +< .withInput({ world: 'world' }) +< .withMessage('if with undefined does not show the contents') +< .toCompileTo('cruel world!'); +--- +> expectTemplate(string).withInput({ world: 'world' }).toCompileTo('cruel world!'); +40c44 +< world: 'world' +--- +> world: 'world', +42d45 +< .withMessage('if with non-empty array shows the contents') +48c51 +< world: 'world' +--- +> world: 'world', +50d52 +< .withMessage('if with empty array does not show the contents') +56c58 +< world: 'world' +--- +> world: 'world', +58d59 +< .withMessage('if with zero does not show the contents') +61,63c62 +< expectTemplate( +< '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!' +< ) +--- +> expectTemplate('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!') +66c65 +< world: 'world' +--- +> world: 'world', +68d66 +< .withMessage('if with zero does not show the contents') +72,73c70,71 +< it('if with function argument', function() { +< var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; +--- +> it('if with function argument', () => { +> const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; +77c75 +< goodbye: function() { +--- +> goodbye() { +80c78 +< world: 'world' +--- +> world: 'world', +82,84d79 +< .withMessage( +< 'if with function shows the contents when function returns true' +< ) +89c84 +< goodbye: function() { +--- +> goodbye() { +92c87 +< world: 'world' +--- +> world: 'world', +94,96d88 +< .withMessage( +< 'if with function shows the contents when function returns string' +< ) +101c93 +< goodbye: function() { +--- +> goodbye() { +104c96 +< world: 'world' +--- +> world: 'world', +106,108d97 +< .withMessage( +< 'if with function does not show the contents when returns false' +< ) +113c102 +< goodbye: function() { +--- +> goodbye() { +116c105 +< world: 'world' +--- +> world: 'world', +118,120d106 +< .withMessage( +< 'if with function does not show the contents when returns undefined' +< ) +124,127c110,111 +< it('should not change the depth list', function() { +< expectTemplate( +< '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}' +< ) +--- +> it('should not change the depth list', () => { +> expectTemplate('{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}') +130c114 +< world: 'world' +--- +> world: 'world', +136,137c120,121 +< describe('#with', function() { +< it('with', function() { +--- +> describe('#with', () => { +> it('with', () => { +142,143c126,127 +< last: 'Johnson' +< } +--- +> last: 'Johnson', +> }, +148c132 +< it('with with function argument', function() { +--- +> it('with with function argument', () => { +151c135 +< person: function() { +--- +> person() { +154c138 +< last: 'Johnson' +--- +> last: 'Johnson', +156c140 +< } +--- +> }, +161c145 +< it('with with else', function() { +--- +> it('with with else', () => { +167c151 +< it('with provides block parameter', function() { +--- +> it('with provides block parameter', () => { +172,173c156,157 +< last: 'Johnson' +< } +--- +> last: 'Johnson', +> }, +178c162 +< it('works when data is disabled', function() { +--- +> it('works when data is disabled', () => { +186,194c170,172 +< describe('#each', function() { +< beforeEach(function() { +< handlebarsEnv.registerHelper('detectDataInsideEach', function(options) { +< return options.data && options.data.exclaim; +< }); +< }); +< +< it('each', function() { +< var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; +--- +> describe('#each', () => { +> it('each', () => { +> const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; +198,203c176,177 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +205,207d178 +< .withMessage( +< 'each with array argument iterates over the contents when not empty' +< ) +213c184 +< world: 'world' +--- +> world: 'world', +215d185 +< .withMessage('each with array argument ignores the contents when empty') +219c189 +< it('each without data', function() { +--- +> it('each without data', () => { +222,227c192,193 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +240c206 +< it('each without context', function() { +--- +> it('each without context', () => { +246,248c212,213 +< it('each with an object and @key', function() { +< var string = +< '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!'; +--- +> it('each with an object and @key', () => { +> const string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!'; +250c215 +< function Clazz() { +--- +> function Clazz(this: any) { +255c220 +< var hash = { goodbyes: new Clazz(), world: 'world' }; +--- +> const hash = { goodbyes: new (Clazz as any)(), world: 'world' }; +260,270c225,233 +< var actual = compileWithPartials(string, hash); +< var expected1 = +< '<b>#1</b>. goodbye! 2. GOODBYE! cruel world!'; +< var expected2 = +< '2. GOODBYE! <b>#1</b>. goodbye! cruel world!'; +< +< equals( +< actual === expected1 || actual === expected2, +< true, +< 'each with object argument iterates over the contents when not empty' +< ); +--- +> try { +> expectTemplate(string) +> .withInput(hash) +> .toCompileTo('<b>#1</b>. goodbye! 2. GOODBYE! cruel world!'); +> } catch (e) { +> expectTemplate(string) +> .withInput(hash) +> .toCompileTo('2. GOODBYE! <b>#1</b>. goodbye! cruel world!'); +> } +275c238 +< world: 'world' +--- +> world: 'world', +280,283c243,244 +< it('each with @index', function() { +< expectTemplate( +< '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' +< ) +--- +> it('each with @index', () => { +> expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') +285,290c246,247 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +292d248 +< .withMessage('The @index variable is used') +296c252 +< it('each with nested @index', function() { +--- +> it('each with nested @index', () => { +301,306c257,258 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +308d259 +< .withMessage('The @index variable is used') +314c265 +< it('each with block params', function() { +--- +> it('each with block params', () => { +320c271 +< world: 'world' +--- +> world: 'world', +322,324c273 +< .toCompileTo( +< '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!' +< ); +--- +> .toCompileTo('0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'); +327,330c276,277 +< it('each object with @index', function() { +< expectTemplate( +< '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' +< ) +--- +> it('each object with @index', () => { +> expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') +335c282 +< c: { text: 'GOODBYE' } +--- +> c: { text: 'GOODBYE' }, +337c284 +< world: 'world' +--- +> world: 'world', +339d285 +< .withMessage('The @index variable is used') +343,346c289,290 +< it('each with @first', function() { +< expectTemplate( +< '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' +< ) +--- +> it('each with @first', () => { +> expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') +348,353c292,293 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +355d294 +< .withMessage('The @first variable is used') +359c298 +< it('each with nested @first', function() { +--- +> it('each with nested @first', () => { +364,369c303,304 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +371,374c306 +< .withMessage('The @first variable is used') +< .toCompileTo( +< '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!' +< ); +--- +> .toCompileTo('(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'); +377,380c309,310 +< it('each object with @first', function() { +< expectTemplate( +< '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' +< ) +--- +> it('each object with @first', () => { +> expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') +383c313 +< world: 'world' +--- +> world: 'world', +385d314 +< .withMessage('The @first variable is used') +389,392c318,319 +< it('each with @last', function() { +< expectTemplate( +< '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' +< ) +--- +> it('each with @last', () => { +> expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') +394,399c321,322 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +401d323 +< .withMessage('The @last variable is used') +405,408c327,328 +< it('each object with @last', function() { +< expectTemplate( +< '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' +< ) +--- +> it('each object with @last', () => { +> expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') +411c331 +< world: 'world' +--- +> world: 'world', +413d332 +< .withMessage('The @last variable is used') +417c336 +< it('each with nested @last', function() { +--- +> it('each with nested @last', () => { +422,427c341,342 +< goodbyes: [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ], +< world: 'world' +--- +> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], +> world: 'world', +429,432c344 +< .withMessage('The @last variable is used') +< .toCompileTo( +< '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!' +< ); +--- +> .toCompileTo('(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'); +435,436c347,348 +< it('each with function argument', function() { +< var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; +--- +> it('each with function argument', () => { +> const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; +440,445c352,353 +< goodbyes: function() { +< return [ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ]; +--- +> goodbyes() { +> return [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]; +447c355 +< world: 'world' +--- +> world: 'world', +449,451d356 +< .withMessage( +< 'each with array function argument iterates over the contents when not empty' +< ) +457c362 +< world: 'world' +--- +> world: 'world', +459,461d363 +< .withMessage( +< 'each with array function argument ignores the contents when empty' +< ) +465,468c367,368 +< it('each object when last key is an empty string', function() { +< expectTemplate( +< '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' +< ) +--- +> it('each object when last key is an empty string', () => { +> expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') +473c373 +< '': { text: 'GOODBYE' } +--- +> '': { text: 'GOODBYE' }, +475c375 +< world: 'world' +--- +> world: 'world', +477d376 +< .withMessage('Empty string key is not skipped') +481,484c380,381 +< it('data passed to helpers', function() { +< expectTemplate( +< '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}' +< ) +--- +> it('data passed to helpers', () => { +> expectTemplate('{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}') +486c383,385 +< .withMessage('should output data') +--- +> .withHelper('detectDataInsideEach', function (options) { +> return options.data && options.data.exclaim; +> }) +489,490c388,389 +< exclaim: '!' +< } +--- +> exclaim: '!', +> }, +495,499c394,395 +< it('each on implicit context', function() { +< expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow( +< handlebarsEnv.Exception, +< 'Must pass iterator to #each' +< ); +--- +> it('each on implicit context', () => { +> expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(Handlebars.Exception); +502,504c398,403 +< if (global.Symbol && global.Symbol.iterator) { +< it('each on iterable', function() { +< function Iterator(arr) { +--- +> it('each on iterable', () => { +> class Iterator { +> private arr: any[]; +> private index: number = 0; +> +> constructor(arr: any[]) { +506d404 +< this.index = 0; +508,510c406,409 +< Iterator.prototype.next = function() { +< var value = this.arr[this.index]; +< var done = this.index === this.arr.length; +--- +> +> next() { +> const value = this.arr[this.index]; +> const done = this.index === this.arr.length; +514,516c413,420 +< return { value: value, done: done }; +< }; +< function Iterable(arr) { +--- +> return { value, done }; +> } +> } +> +> class Iterable { +> private arr: any[]; +> +> constructor(arr: any[]) { +519c423,424 +< Iterable.prototype[global.Symbol.iterator] = function() { +--- +> +> [Symbol.iterator]() { +521,522c426,427 +< }; +< var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; +--- +> } +> } +524,536c429 +< expectTemplate(string) +< .withInput({ +< goodbyes: new Iterable([ +< { text: 'goodbye' }, +< { text: 'Goodbye' }, +< { text: 'GOODBYE' } +< ]), +< world: 'world' +< }) +< .withMessage( +< 'each with array argument iterates over the contents when not empty' +< ) +< .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); +--- +> const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; +538,548c431,444 +< expectTemplate(string) +< .withInput({ +< goodbyes: new Iterable([]), +< world: 'world' +< }) +< .withMessage( +< 'each with array argument ignores the contents when empty' +< ) +< .toCompileTo('cruel world!'); +< }); +< } +--- +> expectTemplate(string) +> .withInput({ +> goodbyes: new Iterable([{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]), +> world: 'world', +> }) +> .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); +> +> expectTemplate(string) +> .withInput({ +> goodbyes: new Iterable([]), +> world: 'world', +> }) +> .toCompileTo('cruel world!'); +> }); +551c447 +< describe('#log', function() { +--- +> describe('#log', function () { +553,555c449,451 +< if (typeof console === 'undefined') { +< return; +< } +--- +> let $log: typeof console.log; +> let $info: typeof console.info; +> let $error: typeof console.error; +557,558c453 +< var $log, $info, $error; +< beforeEach(function() { +--- +> beforeEach(function () { +561a457,458 +> +> global.kbnHandlebarsEnv = Handlebars.create(); +563c460,461 +< afterEach(function() { +--- +> +> afterEach(function () { +569,571c467,470 +< it('should call logger at default level', function() { +< var levelArg, logArg; +< handlebarsEnv.log = function(level, arg) { +--- +> it('should call logger at default level', function () { +> let levelArg; +> let logArg; +> kbnHandlebarsEnv!.log = function (level, arg) { +576,581c475,477 +< expectTemplate('{{log blah}}') +< .withInput({ blah: 'whee' }) +< .withMessage('log should not display') +< .toCompileTo(''); +< equals(1, levelArg, 'should call log with 1'); +< equals('whee', logArg, "should call log with 'whee'"); +--- +> expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo(''); +> expect(1).toEqual(levelArg); +> expect('whee').toEqual(logArg); +584,586c480,483 +< it('should call logger at data level', function() { +< var levelArg, logArg; +< handlebarsEnv.log = function(level, arg) { +--- +> it('should call logger at data level', function () { +> let levelArg; +> let logArg; +> kbnHandlebarsEnv!.log = function (level, arg) { +596,597c493,494 +< equals('03', levelArg); +< equals('whee', logArg); +--- +> expect('03').toEqual(levelArg); +> expect('whee').toEqual(logArg); +600,601c497,498 +< it('should output to info', function() { +< var called; +--- +> it('should output to info', function () { +> let called; +603,604c500,501 +< console.info = function(info) { +< equals('whee', info); +--- +> console.info = function (info) { +> expect('whee').toEqual(info); +609,610c506,507 +< console.log = function(log) { +< equals('whee', log); +--- +> console.log = function (log) { +> expect('whee').toEqual(log); +616,619c513,514 +< expectTemplate('{{log blah}}') +< .withInput({ blah: 'whee' }) +< .toCompileTo(''); +< equals(true, called); +--- +> expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo(''); +> expect(true).toEqual(called); +622,623c517,518 +< it('should log at data level', function() { +< var called; +--- +> it('should log at data level', function () { +> let called; +625,626c520,521 +< console.error = function(log) { +< equals('whee', log); +--- +> console.error = function (log) { +> expect('whee').toEqual(log); +636c531 +< equals(true, called); +--- +> expect(true).toEqual(called); +639,640c534,535 +< it('should handle missing logger', function() { +< var called = false; +--- +> it('should handle missing logger', function () { +> let called = false; +641a537 +> // @ts-expect-error +643,644c539,540 +< console.log = function(log) { +< equals('whee', log); +--- +> console.log = function (log) { +> expect('whee').toEqual(log); +654c550 +< equals(true, called); +--- +> expect(true).toEqual(called); +657,658c553,554 +< it('should handle string log levels', function() { +< var called; +--- +> it('should handle string log levels', function () { +> let called; +660,661c556,557 +< console.error = function(log) { +< equals('whee', log); +--- +> console.error = function (log) { +> expect('whee').toEqual(log); +670c566 +< equals(true, called); +--- +> expect(true).toEqual(called); +679c575 +< equals(true, called); +--- +> expect(true).toEqual(called); +682,683c578,579 +< it('should handle hash log levels', function() { +< var called; +--- +> it('should handle hash log levels [1]', function () { +> let called; +685,686c581,582 +< console.error = function(log) { +< equals('whee', log); +--- +> console.error = function (log) { +> expect('whee').toEqual(log); +690,693c586,587 +< expectTemplate('{{log blah level="error"}}') +< .withInput({ blah: 'whee' }) +< .toCompileTo(''); +< equals(true, called); +--- +> expectTemplate('{{log blah level="error"}}').withInput({ blah: 'whee' }).toCompileTo(''); +> expect(true).toEqual(called); +696,697c590,591 +< it('should handle hash log levels', function() { +< var called = false; +--- +> it('should handle hash log levels [2]', function () { +> let called = false; +699,702c593,600 +< console.info = console.log = console.error = console.debug = function() { +< called = true; +< console.info = console.log = console.error = console.debug = $log; +< }; +--- +> console.info = +> console.log = +> console.error = +> console.debug = +> function () { +> called = true; +> console.info = console.log = console.error = console.debug = $log; +> }; +704,707c602,603 +< expectTemplate('{{log blah level="debug"}}') +< .withInput({ blah: 'whee' }) +< .toCompileTo(''); +< equals(false, called); +--- +> expectTemplate('{{log blah level="debug"}}').withInput({ blah: 'whee' }).toCompileTo(''); +> expect(false).toEqual(called); +710,711c606,607 +< it('should pass multiple log arguments', function() { +< var called; +--- +> it('should pass multiple log arguments', function () { +> let called; +713,716c609,612 +< console.info = console.log = function(log1, log2, log3) { +< equals('whee', log1); +< equals('foo', log2); +< equals(1, log3); +--- +> console.info = console.log = function (log1, log2, log3) { +> expect('whee').toEqual(log1); +> expect('foo').toEqual(log2); +> expect(1).toEqual(log3); +721,724c617,618 +< expectTemplate('{{log blah "foo" 1}}') +< .withInput({ blah: 'whee' }) +< .toCompileTo(''); +< equals(true, called); +--- +> expectTemplate('{{log blah "foo" 1}}').withInput({ blah: 'whee' }).toCompileTo(''); +> expect(true).toEqual(called); +727,728c621,622 +< it('should pass zero log arguments', function() { +< var called; +--- +> it('should pass zero log arguments', function () { +> let called; +730,731c624,625 +< console.info = console.log = function() { +< expect(arguments.length).to.equal(0); +--- +> console.info = console.log = function () { +> expect(arguments.length).toEqual(0); +736,739c630,631 +< expectTemplate('{{log}}') +< .withInput({ blah: 'whee' }) +< .toCompileTo(''); +< expect(called).to.be.true(); +--- +> expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo(''); +> expect(called).toEqual(true); +744,745c636,637 +< describe('#lookup', function() { +< it('should lookup arbitrary content', function() { +--- +> describe('#lookup', () => { +> it('should lookup arbitrary content', () => { +751c643 +< it('should not fail on undefined value', function() { +--- +> it('should not fail on undefined value', () => { diff --git a/packages/kbn-handlebars/.patches/compiler.patch b/packages/kbn-handlebars/.patches/compiler.patch new file mode 100644 index 0000000000000..0028d85e395e8 --- /dev/null +++ b/packages/kbn-handlebars/.patches/compiler.patch @@ -0,0 +1,272 @@ +1,92c1,24 +< describe('compiler', function() { +< if (!Handlebars.compile) { +< return; +< } +< +< describe('#equals', function() { +< function compile(string) { +< var ast = Handlebars.parse(string); +< return new Handlebars.Compiler().compile(ast, {}); +< } +< +< it('should treat as equal', function() { +< equal(compile('foo').equals(compile('foo')), true); +< equal(compile('{{foo}}').equals(compile('{{foo}}')), true); +< equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true); +< equal( +< compile('{{foo.bar baz "foo" true false bat=1}}').equals( +< compile('{{foo.bar baz "foo" true false bat=1}}') +< ), +< true +< ); +< equal( +< compile('{{foo.bar (baz bat=1)}}').equals( +< compile('{{foo.bar (baz bat=1)}}') +< ), +< true +< ); +< equal( +< compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), +< true +< ); +< }); +< it('should treat as not equal', function() { +< equal(compile('foo').equals(compile('bar')), false); +< equal(compile('{{foo}}').equals(compile('{{bar}}')), false); +< equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false); +< equal( +< compile('{{foo.bar baz bat=1}}').equals( +< compile('{{foo.bar bar bat=1}}') +< ), +< false +< ); +< equal( +< compile('{{foo.bar (baz bat=1)}}').equals( +< compile('{{foo.bar (bar bat=1)}}') +< ), +< false +< ); +< equal( +< compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), +< false +< ); +< equal( +< compile('{{#foo}} {{/foo}}').equals( +< compile('{{#foo}} {{foo}}{{/foo}}') +< ), +< false +< ); +< }); +< }); +< +< describe('#compile', function() { +< it('should fail with invalid input', function() { +< shouldThrow( +< function() { +< Handlebars.compile(null); +< }, +< Error, +< 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null' +< ); +< shouldThrow( +< function() { +< Handlebars.compile({}); +< }, +< Error, +< 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]' +< ); +< }); +< +< it('should include the location in the error (row and column)', function() { +< try { +< Handlebars.compile(' \n {{#if}}\n{{/def}}')(); +< equal( +< true, +< false, +< 'Statement must throw exception. This line should not be executed.' +< ); +< } catch (err) { +< equal( +< err.message, +< "if doesn't match def - 2:5", +< 'Checking error message' +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import Handlebars from '..'; +> +> describe('compiler', () => { +> const compileFns = ['compile', 'compileAST']; +> if (process.env.AST) compileFns.splice(0, 1); +> else if (process.env.EVAL) compileFns.splice(1, 1); +> +> compileFns.forEach((compileName) => { +> // @ts-expect-error +> const compile = Handlebars[compileName]; +> +> describe(`#${compileName}`, () => { +> it('should fail with invalid input', () => { +> expect(function () { +> compile(null); +> }).toThrow( +> `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed null` +94,102d25 +< if (Object.getOwnPropertyDescriptor(err, 'column').writable) { +< // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, +< // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) +< // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. +< equal(err.column, 5, 'Checking error column'); +< } +< equal(err.lineNumber, 2, 'Checking error row'); +< } +< }); +104,116c27,30 +< it('should include the location as enumerable property', function() { +< try { +< Handlebars.compile(' \n {{#if}}\n{{/def}}')(); +< equal( +< true, +< false, +< 'Statement must throw exception. This line should not be executed.' +< ); +< } catch (err) { +< equal( +< Object.prototype.propertyIsEnumerable.call(err, 'column'), +< true, +< 'Checking error column' +--- +> expect(function () { +> compile({}); +> }).toThrow( +> `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed [object Object]` +118,129c32 +< } +< }); +< +< it('can utilize AST instance', function() { +< equal( +< Handlebars.compile({ +< type: 'Program', +< body: [{ type: 'ContentStatement', value: 'Hello' }] +< })(), +< 'Hello' +< ); +< }); +--- +> }); +131,152c34,48 +< it('can pass through an empty string', function() { +< equal(Handlebars.compile('')(), ''); +< }); +< +< it('should not modify the options.data property(GH-1327)', function() { +< var options = { data: [{ a: 'foo' }, { a: 'bar' }] }; +< Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); +< equal( +< JSON.stringify(options, 0, 2), +< JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2) +< ); +< }); +< +< it('should not modify the options.knownHelpers property(GH-1327)', function() { +< var options = { knownHelpers: {} }; +< Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); +< equal( +< JSON.stringify(options, 0, 2), +< JSON.stringify({ knownHelpers: {} }, 0, 2) +< ); +< }); +< }); +--- +> it('should include the location in the error (row and column)', () => { +> try { +> compile(' \n {{#if}}\n{{/def}}')({}); +> expect(true).toEqual(false); +> } catch (err) { +> expect(err.message).toEqual("if doesn't match def - 2:5"); +> if (Object.getOwnPropertyDescriptor(err, 'column')!.writable) { +> // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, +> // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) +> // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. +> expect(err.column).toEqual(5); +> } +> expect(err.lineNumber).toEqual(2); +> } +> }); +154,170c50,57 +< describe('#precompile', function() { +< it('should fail with invalid input', function() { +< shouldThrow( +< function() { +< Handlebars.precompile(null); +< }, +< Error, +< 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null' +< ); +< shouldThrow( +< function() { +< Handlebars.precompile({}); +< }, +< Error, +< 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]' +< ); +< }); +--- +> it('should include the location as enumerable property', () => { +> try { +> compile(' \n {{#if}}\n{{/def}}')({}); +> expect(true).toEqual(false); +> } catch (err) { +> expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true); +> } +> }); +172,175c59,61 +< it('can utilize AST instance', function() { +< equal( +< /return "Hello"/.test( +< Handlebars.precompile({ +--- +> it('can utilize AST instance', () => { +> expect( +> compile({ +177,182c63,78 +< body: [{ type: 'ContentStatement', value: 'Hello' }] +< }) +< ), +< true +< ); +< }); +--- +> body: [{ type: 'ContentStatement', value: 'Hello' }], +> })({}) +> ).toEqual('Hello'); +> }); +> +> it('can pass through an empty string', () => { +> expect(compile('')({})).toEqual(''); +> }); +> +> it('should not modify the options.data property(GH-1327)', () => { +> const options = { data: [{ a: 'foo' }, { a: 'bar' }] }; +> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({}); +> expect(JSON.stringify(options, null, 2)).toEqual( +> JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, null, 2) +> ); +> }); +184,185c80,86 +< it('can pass through an empty string', function() { +< equal(/return ""/.test(Handlebars.precompile('')), true); +--- +> it('should not modify the options.knownHelpers property(GH-1327)', () => { +> const options = { knownHelpers: {} }; +> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({}); +> expect(JSON.stringify(options, null, 2)).toEqual( +> JSON.stringify({ knownHelpers: {} }, null, 2) +> ); +> }); diff --git a/packages/kbn-handlebars/.patches/data.patch b/packages/kbn-handlebars/.patches/data.patch new file mode 100644 index 0000000000000..2da3fdb2e2591 --- /dev/null +++ b/packages/kbn-handlebars/.patches/data.patch @@ -0,0 +1,273 @@ +1,2c1,12 +< describe('data', function() { +< it('passing in data to a compiled function that expects data - works with helpers', function() { +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import Handlebars from '..'; +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('data', () => { +> it('passing in data to a compiled function that expects data - works with helpers', () => { +5c15 +< .withHelper('hello', function(options) { +--- +> .withHelper('hello', function (this: any, options) { +10d19 +< .withMessage('Data output by helper') +14c23 +< it('data can be looked up via @foo', function() { +--- +> it('data can be looked up via @foo', () => { +17d25 +< .withMessage('@foo retrieves template data') +21,22c29,31 +< it('deep @foo triggers automatic top-level data', function() { +< var helpers = Handlebars.createFrame(handlebarsEnv.helpers); +--- +> it('deep @foo triggers automatic top-level data', () => { +> global.kbnHandlebarsEnv = Handlebars.create(); +> const helpers = Handlebars.createFrame(kbnHandlebarsEnv!.helpers); +24,25c33,34 +< helpers.let = function(options) { +< var frame = Handlebars.createFrame(options.data); +--- +> helpers.let = function (options: Handlebars.HelperOptions) { +> const frame = Handlebars.createFrame(options.data); +27c36 +< for (var prop in options.hash) { +--- +> for (const prop in options.hash) { +40d48 +< .withMessage('Automatic data was triggered') +44c52 +< it('parameter data can be looked up via @foo', function() { +--- +> it('parameter data can be looked up via @foo', () => { +47c55 +< .withHelper('hello', function(noun) { +--- +> .withHelper('hello', function (noun) { +50d57 +< .withMessage('@foo as a parameter retrieves template data') +54c61 +< it('hash values can be looked up via @foo', function() { +--- +> it('hash values can be looked up via @foo', () => { +57c64 +< .withHelper('hello', function(options) { +--- +> .withHelper('hello', function (options) { +60d66 +< .withMessage('@foo as a parameter retrieves template data') +64c70 +< it('nested parameter data can be looked up via @foo.bar', function() { +--- +> it('nested parameter data can be looked up via @foo.bar', () => { +67c73 +< .withHelper('hello', function(noun) { +--- +> .withHelper('hello', function (noun) { +70d75 +< .withMessage('@foo as a parameter retrieves template data') +74c79 +< it('nested parameter data does not fail with @world.bar', function() { +--- +> it('nested parameter data does not fail with @world.bar', () => { +77c82 +< .withHelper('hello', function(noun) { +--- +> .withHelper('hello', function (noun) { +80d84 +< .withMessage('@foo as a parameter retrieves template data') +84,87c88,89 +< it('parameter data throws when using complex scope references', function() { +< expectTemplate( +< '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}' +< ).toThrow(Error); +--- +> it('parameter data throws when using complex scope references', () => { +> expectTemplate('{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}').toThrow(Error); +90c92 +< it('data can be functions', function() { +--- +> it('data can be functions', () => { +94c96 +< hello: function() { +--- +> hello() { +96,97c98,99 +< } +< } +--- +> }, +> }, +102c104 +< it('data can be functions with params', function() { +--- +> it('data can be functions with params', () => { +106c108 +< hello: function(arg) { +--- +> hello(arg: any) { +108,109c110,111 +< } +< } +--- +> }, +> }, +114c116 +< it('data is inherited downstream', function() { +--- +> it('data is inherited downstream', () => { +120,122c122,124 +< .withHelper('let', function(options) { +< var frame = Handlebars.createFrame(options.data); +< for (var prop in options.hash) { +--- +> .withHelper('let', function (this: any, options) { +> const frame = Handlebars.createFrame(options.data); +> for (const prop in options.hash) { +130d131 +< .withMessage('data variables are inherited downstream') +134,147c135 +< it('passing in data to a compiled function that expects data - works with helpers in partials', function() { +< expectTemplate('{{>myPartial}}') +< .withCompileOptions({ data: true }) +< .withPartial('myPartial', '{{hello}}') +< .withHelper('hello', function(options) { +< return options.data.adjective + ' ' + this.noun; +< }) +< .withInput({ noun: 'cat' }) +< .withRuntimeOptions({ data: { adjective: 'happy' } }) +< .withMessage('Data output by helper inside partial') +< .toCompileTo('happy cat'); +< }); +< +< it('passing in data to a compiled function that expects data - works with helpers and parameters', function() { +--- +> it('passing in data to a compiled function that expects data - works with helpers and parameters', () => { +150c138 +< .withHelper('hello', function(noun, options) { +--- +> .withHelper('hello', function (this: any, noun, options) { +155d142 +< .withMessage('Data output by helper') +159c146 +< it('passing in data to a compiled function that expects data - works with block helpers', function() { +--- +> it('passing in data to a compiled function that expects data - works with block helpers', () => { +162c149 +< data: true +--- +> data: true, +164c151 +< .withHelper('hello', function(options) { +--- +> .withHelper('hello', function (this: any, options) { +167c154 +< .withHelper('world', function(options) { +--- +> .withHelper('world', function (this: any, options) { +172d158 +< .withMessage('Data output by helper') +176c162 +< it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() { +--- +> it('passing in data to a compiled function that expects data - works with block helpers that use ..', () => { +179c165 +< .withHelper('hello', function(options) { +--- +> .withHelper('hello', function (options) { +182c168 +< .withHelper('world', function(thing, options) { +--- +> .withHelper('world', function (this: any, thing, options) { +187d172 +< .withMessage('Data output by helper') +191c176 +< it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() { +--- +> it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', () => { +194c179 +< .withHelper('hello', function(options) { +--- +> .withHelper('hello', function (options) { +197c182 +< .withHelper('world', function(thing, options) { +--- +> .withHelper('world', function (this: any, thing, options) { +202d186 +< .withMessage('Data output by helper') +206c190 +< it('you can override inherited data when invoking a helper', function() { +--- +> it('you can override inherited data when invoking a helper', () => { +209,213c193,194 +< .withHelper('hello', function(options) { +< return options.fn( +< { exclaim: '?', zomg: 'world' }, +< { data: { adjective: 'sad' } } +< ); +--- +> .withHelper('hello', function (options) { +> return options.fn({ exclaim: '?', zomg: 'world' }, { data: { adjective: 'sad' } }); +215c196 +< .withHelper('world', function(thing, options) { +--- +> .withHelper('world', function (this: any, thing, options) { +220d200 +< .withMessage('Overriden data output by helper') +224c204 +< it('you can override inherited data when invoking a helper with depth', function() { +--- +> it('you can override inherited data when invoking a helper with depth', () => { +227c207 +< .withHelper('hello', function(options) { +--- +> .withHelper('hello', function (options) { +230c210 +< .withHelper('world', function(thing, options) { +--- +> .withHelper('world', function (this: any, thing, options) { +235d214 +< .withMessage('Overriden data output by helper') +239,240c218,219 +< describe('@root', function() { +< it('the root context can be looked up via @root', function() { +--- +> describe('@root', () => { +> it('the root context can be looked up via @root', () => { +246,248c225 +< expectTemplate('{{@root.foo}}') +< .withInput({ foo: 'hello' }) +< .toCompileTo('hello'); +--- +> expectTemplate('{{@root.foo}}').withInput({ foo: 'hello' }).toCompileTo('hello'); +251c228 +< it('passed root values take priority', function() { +--- +> it('passed root values take priority', () => { +259,260c236,237 +< describe('nesting', function() { +< it('the root context can be looked up via @root', function() { +--- +> describe('nesting', () => { +> it('the root context can be looked up via @root', () => { +265,266c242,243 +< .withHelper('helper', function(options) { +< var frame = Handlebars.createFrame(options.data); +--- +> .withHelper('helper', function (this: any, options) { +> const frame = Handlebars.createFrame(options.data); +272,273c249,250 +< depth: 0 +< } +--- +> depth: 0, +> }, diff --git a/packages/kbn-handlebars/.patches/helpers.patch b/packages/kbn-handlebars/.patches/helpers.patch new file mode 100644 index 0000000000000..f744c9cde9f52 --- /dev/null +++ b/packages/kbn-handlebars/.patches/helpers.patch @@ -0,0 +1,1096 @@ +1,2c1,16 +< describe('helpers', function() { +< it('helper with complex lookup$', function() { +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import Handlebars from '..'; +> import { expectTemplate } from '../__jest__/test_bench'; +> +> beforeEach(() => { +> global.kbnHandlebarsEnv = Handlebars.create(); +> }); +> +> describe('helpers', () => { +> it('helper with complex lookup$', () => { +6c20 +< goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] +--- +> goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], +8,11c22,23 +< .withHelper('link', function(prefix) { +< return ( +< '' + this.text + '' +< ); +--- +> .withHelper('link', function (this: any, prefix) { +> return '' + this.text + ''; +16c28 +< it('helper for raw block gets raw content', function() { +--- +> it('helper for raw block gets raw content', () => { +19c31 +< .withHelper('raw', function(options) { +--- +> .withHelper('raw', function (options) { +22d33 +< .withMessage('raw block helper gets raw content') +26c37 +< it('helper for raw block gets parameters', function() { +--- +> it('helper for raw block gets parameters', () => { +29,30c40,42 +< .withHelper('raw', function(a, b, c, options) { +< return options.fn() + a + b + c; +--- +> .withHelper('raw', function (a, b, c, options) { +> const ret = options.fn() + a + b + c; +> return ret; +32d43 +< .withMessage('raw block helper gets raw content') +36,37c47,48 +< describe('raw block parsing (with identity helper-function)', function() { +< function runWithIdentityHelper(template, expected) { +--- +> describe('raw block parsing (with identity helper-function)', () => { +> function runWithIdentityHelper(template: string, expected: string) { +39c50 +< .withHelper('identity', function(options) { +--- +> .withHelper('identity', function (options) { +45c56 +< it('helper for nested raw block gets raw content', function() { +--- +> it('helper for nested raw block gets raw content', () => { +52c63 +< it('helper for nested raw block works with empty content', function() { +--- +> it('helper for nested raw block works with empty content', () => { +56c67 +< xit('helper for nested raw block works if nested raw blocks are broken', function() { +--- +> it.skip('helper for nested raw block works if nested raw blocks are broken', () => { +67c78 +< it('helper for nested raw block closes after first matching close', function() { +--- +> it('helper for nested raw block closes after first matching close', () => { +74,75c85,86 +< it('helper for nested raw block throw exception when with missing closing braces', function() { +< var string = '{{{{a}}}} {{{{/a'; +--- +> it('helper for nested raw block throw exception when with missing closing braces', () => { +> const string = '{{{{a}}}} {{{{/a'; +80c91 +< it('helper block with identical context', function() { +--- +> it('helper block with identical context', () => { +83,86c94,97 +< .withHelper('goodbyes', function(options) { +< var out = ''; +< var byes = ['Goodbye', 'goodbye', 'GOODBYE']; +< for (var i = 0, j = byes.length; i < j; i++) { +--- +> .withHelper('goodbyes', function (this: any, options) { +> let out = ''; +> const byes = ['Goodbye', 'goodbye', 'GOODBYE']; +> for (let i = 0, j = byes.length; i < j; i++) { +94c105 +< it('helper block with complex lookup expression', function() { +--- +> it('helper block with complex lookup expression', () => { +97,100c108,111 +< .withHelper('goodbyes', function(options) { +< var out = ''; +< var byes = ['Goodbye', 'goodbye', 'GOODBYE']; +< for (var i = 0, j = byes.length; i < j; i++) { +--- +> .withHelper('goodbyes', function (options) { +> let out = ''; +> const byes = ['Goodbye', 'goodbye', 'GOODBYE']; +> for (let i = 0, j = byes.length; i < j; i++) { +108,111c119,120 +< it('helper with complex lookup and nested template', function() { +< expectTemplate( +< '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' +< ) +--- +> it('helper with complex lookup and nested template', () => { +> expectTemplate('{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}') +114c123 +< goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] +--- +> goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], +116,125c125,126 +< .withHelper('link', function(prefix, options) { +< return ( +< '' + +< options.fn(this) + +< '' +< ); +--- +> .withHelper('link', function (this: any, prefix, options) { +> return '' + options.fn(this) + ''; +130,133c131,132 +< it('helper with complex lookup and nested template in VM+Compiler', function() { +< expectTemplate( +< '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' +< ) +--- +> it('helper with complex lookup and nested template in VM+Compiler', () => { +> expectTemplate('{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}') +136c135 +< goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] +--- +> goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], +138,147c137,138 +< .withHelper('link', function(prefix, options) { +< return ( +< '' + +< options.fn(this) + +< '' +< ); +--- +> .withHelper('link', function (this: any, prefix, options) { +> return '' + options.fn(this) + ''; +152c143 +< it('helper returning undefined value', function() { +--- +> it('helper returning undefined value', () => { +155c146 +< nothere: function() {} +--- +> nothere() {}, +161c152 +< nothere: function() {} +--- +> nothere() {}, +166c157 +< it('block helper', function() { +--- +> it('block helper', () => { +169c160 +< .withHelper('goodbyes', function(options) { +--- +> .withHelper('goodbyes', function (options) { +172d162 +< .withMessage('Block helper executed') +176c166 +< it('block helper staying in the same context', function() { +--- +> it('block helper staying in the same context', () => { +179c169 +< .withHelper('form', function(options) { +--- +> .withHelper('form', function (this: any, options) { +182d171 +< .withMessage('Block helper executed with current context') +186,187c175,176 +< it('block helper should have context in this', function() { +< function link(options) { +--- +> it('block helper should have context in this', () => { +> function link(this: any, options: Handlebars.HelperOptions) { +191,193c180 +< expectTemplate( +< '
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
' +< ) +--- +> expectTemplate('
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
') +197,198c184,185 +< { name: 'Yehuda', id: 2 } +< ] +--- +> { name: 'Yehuda', id: 2 }, +> ], +206c193 +< it('block helper for undefined value', function() { +--- +> it('block helper for undefined value', () => { +210c197 +< it('block helper passing a new context', function() { +--- +> it('block helper passing a new context', () => { +213c200 +< .withHelper('form', function(context, options) { +--- +> .withHelper('form', function (context, options) { +216d202 +< .withMessage('Context variable resolved') +220c206 +< it('block helper passing a complex path context', function() { +--- +> it('block helper passing a complex path context', () => { +223c209 +< .withHelper('form', function(context, options) { +--- +> .withHelper('form', function (context, options) { +226d211 +< .withMessage('Complex path variable resolved') +230,233c215,216 +< it('nested block helpers', function() { +< expectTemplate( +< '{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}' +< ) +--- +> it('nested block helpers', () => { +> expectTemplate('{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}') +235c218 +< yehuda: { name: 'Yehuda' } +--- +> yehuda: { name: 'Yehuda' }, +237c220 +< .withHelper('link', function(options) { +--- +> .withHelper('link', function (this: any, options) { +240c223 +< .withHelper('form', function(context, options) { +--- +> .withHelper('form', function (context, options) { +243d225 +< .withMessage('Both blocks executed') +247,249c229,231 +< it('block helper inverted sections', function() { +< var string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}"; +< function list(context, options) { +--- +> it('block helper inverted sections', () => { +> const string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}"; +> function list(this: any, context: any, options: Handlebars.HelperOptions) { +251,252c233,234 +< var out = '
    '; +< for (var i = 0, j = context.length; i < j; i++) { +--- +> let out = '
      '; +> for (let i = 0, j = context.length; i < j; i++) { +268,269c250 +< .withHelpers({ list: list }) +< .withMessage('an inverse wrapper is passed in as a new context') +--- +> .withHelpers({ list }) +274,275c255 +< .withHelpers({ list: list }) +< .withMessage('an inverse wrapper can be optionally called') +--- +> .withHelpers({ list }) +281c261 +< message: "Nobody's here" +--- +> message: "Nobody's here", +283,284c263 +< .withHelpers({ list: list }) +< .withMessage('the context of an inverse is the parent of the block') +--- +> .withHelpers({ list }) +288,292c267,269 +< it('pathed lambas with parameters', function() { +< var hash = { +< helper: function() { +< return 'winning'; +< } +--- +> it('pathed lambas with parameters', () => { +> const hash = { +> helper: () => 'winning', +293a271 +> // @ts-expect-error +295,298c273,275 +< var helpers = { +< './helper': function() { +< return 'fail'; +< } +--- +> +> const helpers = { +> './helper': () => 'fail', +301,309c278,279 +< expectTemplate('{{./helper 1}}') +< .withInput(hash) +< .withHelpers(helpers) +< .toCompileTo('winning'); +< +< expectTemplate('{{hash/helper 1}}') +< .withInput(hash) +< .withHelpers(helpers) +< .toCompileTo('winning'); +--- +> expectTemplate('{{./helper 1}}').withInput(hash).withHelpers(helpers).toCompileTo('winning'); +> expectTemplate('{{hash/helper 1}}').withInput(hash).withHelpers(helpers).toCompileTo('winning'); +312,313c282,283 +< describe('helpers hash', function() { +< it('providing a helpers hash', function() { +--- +> describe('helpers hash', () => { +> it('providing a helpers hash', () => { +317c287 +< world: function() { +--- +> world() { +319c289 +< } +--- +> }, +321d290 +< .withMessage('helpers hash is available') +327c296 +< world: function() { +--- +> world() { +329c298 +< } +--- +> }, +331d299 +< .withMessage('helpers hash is available inside other blocks') +335c303 +< it('in cases of conflict, helpers win', function() { +--- +> it('in cases of conflict, helpers win', () => { +339c307 +< lookup: function() { +--- +> lookup() { +341c309 +< } +--- +> }, +343d310 +< .withMessage('helpers hash has precedence escaped expansion') +349c316 +< lookup: function() { +--- +> lookup() { +351c318 +< } +--- +> }, +353d319 +< .withMessage('helpers hash has precedence simple expansion') +357c323 +< it('the helpers hash is available is nested contexts', function() { +--- +> it('the helpers hash is available is nested contexts', () => { +361c327 +< helper: function() { +--- +> helper() { +363c329 +< } +--- +> }, +365d330 +< .withMessage('helpers hash is available in nested contexts.') +369,370c334,335 +< it('the helper hash should augment the global hash', function() { +< handlebarsEnv.registerHelper('test_helper', function() { +--- +> it('the helper hash should augment the global hash', () => { +> kbnHandlebarsEnv!.registerHelper('test_helper', function () { +374,376c339 +< expectTemplate( +< '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' +< ) +--- +> expectTemplate('{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}') +379c342 +< world: function() { +--- +> world() { +381c344 +< } +--- +> }, +387,389c350,352 +< describe('registration', function() { +< it('unregisters', function() { +< handlebarsEnv.helpers = {}; +--- +> describe('registration', () => { +> it('unregisters', () => { +> deleteAllKeys(kbnHandlebarsEnv!.helpers); +391c354 +< handlebarsEnv.registerHelper('foo', function() { +--- +> kbnHandlebarsEnv!.registerHelper('foo', function () { +394,395c357,359 +< handlebarsEnv.unregisterHelper('foo'); +< equals(handlebarsEnv.helpers.foo, undefined); +--- +> expect(kbnHandlebarsEnv!.helpers.foo).toBeDefined(); +> kbnHandlebarsEnv!.unregisterHelper('foo'); +> expect(kbnHandlebarsEnv!.helpers.foo).toBeUndefined(); +398,404c362,368 +< it('allows multiple globals', function() { +< var helpers = handlebarsEnv.helpers; +< handlebarsEnv.helpers = {}; +< +< handlebarsEnv.registerHelper({ +< if: helpers['if'], +< world: function() { +--- +> it('allows multiple globals', () => { +> const ifHelper = kbnHandlebarsEnv!.helpers.if; +> deleteAllKeys(kbnHandlebarsEnv!.helpers); +> +> kbnHandlebarsEnv!.registerHelper({ +> if: ifHelper, +> world() { +407c371 +< testHelper: function() { +--- +> testHelper() { +409c373 +< } +--- +> }, +412,414c376 +< expectTemplate( +< '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' +< ) +--- +> expectTemplate('{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}') +419,429c381,387 +< it('fails with multiple and args', function() { +< shouldThrow( +< function() { +< handlebarsEnv.registerHelper( +< { +< world: function() { +< return 'world!'; +< }, +< testHelper: function() { +< return 'found it!'; +< } +--- +> it('fails with multiple and args', () => { +> expect(() => { +> kbnHandlebarsEnv!.registerHelper( +> // @ts-expect-error TypeScript is complaining about the invalid input just as the thrown error +> { +> world() { +> return 'world!'; +431,436c389,395 +< {} +< ); +< }, +< Error, +< 'Arg not supported with multiple helpers' +< ); +--- +> testHelper() { +> return 'found it!'; +> }, +> }, +> {} +> ); +> }).toThrow('Arg not supported with multiple helpers'); +440c399 +< it('decimal number literals work', function() { +--- +> it('decimal number literals work', () => { +442c401 +< .withHelper('hello', function(times, times2) { +--- +> .withHelper('hello', function (times, times2) { +451d409 +< .withMessage('template with a negative integer literal') +455c413 +< it('negative number literals work', function() { +--- +> it('negative number literals work', () => { +457c415 +< .withHelper('hello', function(times) { +--- +> .withHelper('hello', function (times) { +463d420 +< .withMessage('template with a negative integer literal') +467,468c424,425 +< describe('String literal parameters', function() { +< it('simple literals work', function() { +--- +> describe('String literal parameters', () => { +> it('simple literals work', () => { +470c427 +< .withHelper('hello', function(param, times, bool1, bool2) { +--- +> .withHelper('hello', function (param, times, bool1, bool2) { +480,482c437 +< return ( +< 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2 +< ); +--- +> return 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2; +484d438 +< .withMessage('template with a simple String literal') +488c442 +< it('using a quote in the middle of a parameter raises an error', function() { +--- +> it('using a quote in the middle of a parameter raises an error', () => { +492c446 +< it('escaping a String is possible', function() { +--- +> it('escaping a String is possible', () => { +494c448 +< .withHelper('hello', function(param) { +--- +> .withHelper('hello', function (param) { +497d450 +< .withMessage('template with an escaped String literal') +501c454 +< it("it works with ' marks", function() { +--- +> it("it works with ' marks", () => { +503c456 +< .withHelper('hello', function(param) { +--- +> .withHelper('hello', function (param) { +506d458 +< .withMessage("template with a ' mark") +511,524c463,464 +< it('negative number literals work', function() { +< expectTemplate('Message: {{hello -12}}') +< .withHelper('hello', function(times) { +< if (typeof times !== 'number') { +< times = 'NaN'; +< } +< return 'Hello ' + times + ' times'; +< }) +< .withMessage('template with a negative integer literal') +< .toCompileTo('Message: Hello -12 times'); +< }); +< +< describe('multiple parameters', function() { +< it('simple multi-params work', function() { +--- +> describe('multiple parameters', () => { +> it('simple multi-params work', () => { +527c467 +< .withHelper('goodbye', function(cruel, world) { +--- +> .withHelper('goodbye', function (cruel, world) { +530d469 +< .withMessage('regular helpers with multiple params') +534,537c473,474 +< it('block multi-params work', function() { +< expectTemplate( +< 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}' +< ) +--- +> it('block multi-params work', () => { +> expectTemplate('Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}') +539c476 +< .withHelper('goodbye', function(cruel, world, options) { +--- +> .withHelper('goodbye', function (cruel, world, options) { +542d478 +< .withMessage('block helpers with multiple params') +547,548c483,484 +< describe('hash', function() { +< it('helpers can take an optional hash', function() { +--- +> describe('hash', () => { +> it('helpers can take an optional hash', () => { +550c486 +< .withHelper('goodbye', function(options) { +--- +> .withHelper('goodbye', function (options) { +561d496 +< .withMessage('Helper output hash') +565,566c500,501 +< it('helpers can take an optional hash with booleans', function() { +< function goodbye(options) { +--- +> it('helpers can take an optional hash with booleans', () => { +> function goodbye(options: Handlebars.HelperOptions) { +578d512 +< .withMessage('Helper output hash') +583d516 +< .withMessage('Boolean helper parameter honored') +587c520 +< it('block helpers can take an optional hash', function() { +--- +> it('block helpers can take an optional hash', () => { +589c522 +< .withHelper('goodbye', function(options) { +--- +> .withHelper('goodbye', function (this: any, options) { +600d532 +< .withMessage('Hash parameters output') +604c536 +< it('block helpers can take an optional hash with single quoted stings', function() { +--- +> it('block helpers can take an optional hash with single quoted stings', () => { +606c538 +< .withHelper('goodbye', function(options) { +--- +> .withHelper('goodbye', function (this: any, options) { +617d548 +< .withMessage('Hash parameters output') +621,622c552,553 +< it('block helpers can take an optional hash with booleans', function() { +< function goodbye(options) { +--- +> it('block helpers can take an optional hash with booleans', () => { +> function goodbye(this: any, options: Handlebars.HelperOptions) { +634d564 +< .withMessage('Boolean hash parameter honored') +639d568 +< .withMessage('Boolean hash parameter honored') +644,648c573,575 +< describe('helperMissing', function() { +< it('if a context is not found, helperMissing is used', function() { +< expectTemplate('{{hello}} {{link_to world}}').toThrow( +< /Missing helper: "link_to"/ +< ); +--- +> describe('helperMissing', () => { +> it('if a context is not found, helperMissing is used', () => { +> expectTemplate('{{hello}} {{link_to world}}').toThrow(/Missing helper: "link_to"/); +651c578 +< it('if a context is not found, custom helperMissing is used', function() { +--- +> it('if a context is not found, custom helperMissing is used', () => { +654c581 +< .withHelper('helperMissing', function(mesg, options) { +--- +> .withHelper('helperMissing', function (mesg, options) { +662c589 +< it('if a value is not found, custom helperMissing is used', function() { +--- +> it('if a value is not found, custom helperMissing is used', () => { +665c592 +< .withHelper('helperMissing', function(options) { +--- +> .withHelper('helperMissing', function (options) { +674,675c601,602 +< describe('knownHelpers', function() { +< it('Known helper should render helper', function() { +--- +> describe('knownHelpers', () => { +> it('Known helper should render helper', () => { +678c605 +< knownHelpers: { hello: true } +--- +> knownHelpers: { hello: true }, +680c607 +< .withHelper('hello', function() { +--- +> .withHelper('hello', function () { +686c613 +< it('Unknown helper in knownHelpers only mode should be passed as undefined', function() { +--- +> it('Unknown helper in knownHelpers only mode should be passed as undefined', () => { +690c617 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +692c619 +< .withHelper('typeof', function(arg) { +--- +> .withHelper('typeof', function (arg) { +695c622 +< .withHelper('hello', function() { +--- +> .withHelper('hello', function () { +701c628 +< it('Builtin helpers available in knownHelpers only mode', function() { +--- +> it('Builtin helpers available in knownHelpers only mode', () => { +704c631 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +709c636 +< it('Field lookup works in knownHelpers only mode', function() { +--- +> it('Field lookup works in knownHelpers only mode', () => { +712c639 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +718c645 +< it('Conditional blocks work in knownHelpers only mode', function() { +--- +> it('Conditional blocks work in knownHelpers only mode', () => { +721c648 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +727c654 +< it('Invert blocks work in knownHelpers only mode', function() { +--- +> it('Invert blocks work in knownHelpers only mode', () => { +730c657 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +736c663 +< it('Functions are bound to the context in knownHelpers only mode', function() { +--- +> it('Functions are bound to the context in knownHelpers only mode', () => { +739c666 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +742c669 +< foo: function() { +--- +> foo() { +745c672 +< bar: 'bar' +--- +> bar: 'bar', +750c677 +< it('Unknown helper call in knownHelpers only mode should throw', function() { +--- +> it('Unknown helper call in knownHelpers only mode should throw', () => { +757,758c684,685 +< describe('blockHelperMissing', function() { +< it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() { +--- +> describe('blockHelperMissing', () => { +> it('lambdas are resolved by blockHelperMissing, not handlebars proper', () => { +761c688 +< truthy: function() { +--- +> truthy() { +763c690 +< } +--- +> }, +768c695 +< it('lambdas resolved by blockHelperMissing are bound to the context', function() { +--- +> it('lambdas resolved by blockHelperMissing are bound to the context', () => { +771c698 +< truthy: function() { +--- +> truthy() { +774c701 +< truthiness: function() { +--- +> truthiness() { +776c703 +< } +--- +> }, +782,785c709,712 +< describe('name field', function() { +< var helpers = { +< blockHelperMissing: function() { +< return 'missing: ' + arguments[arguments.length - 1].name; +--- +> describe('name field', () => { +> const helpers = { +> blockHelperMissing(...args: any[]) { +> return 'missing: ' + args[args.length - 1].name; +787,788c714,718 +< helperMissing: function() { +< return 'helper missing: ' + arguments[arguments.length - 1].name; +--- +> helperMissing(...args: any[]) { +> return 'helper missing: ' + args[args.length - 1].name; +> }, +> helper(...args: any[]) { +> return 'ran: ' + args[args.length - 1].name; +790,792d719 +< helper: function() { +< return 'ran: ' + arguments[arguments.length - 1].name; +< } +795,798c722,723 +< it('should include in ambiguous mustache calls', function() { +< expectTemplate('{{helper}}') +< .withHelpers(helpers) +< .toCompileTo('ran: helper'); +--- +> it('should include in ambiguous mustache calls', () => { +> expectTemplate('{{helper}}').withHelpers(helpers).toCompileTo('ran: helper'); +801,804c726,727 +< it('should include in helper mustache calls', function() { +< expectTemplate('{{helper 1}}') +< .withHelpers(helpers) +< .toCompileTo('ran: helper'); +--- +> it('should include in helper mustache calls', () => { +> expectTemplate('{{helper 1}}').withHelpers(helpers).toCompileTo('ran: helper'); +807,810c730,731 +< it('should include in ambiguous block calls', function() { +< expectTemplate('{{#helper}}{{/helper}}') +< .withHelpers(helpers) +< .toCompileTo('ran: helper'); +--- +> it('should include in ambiguous block calls', () => { +> expectTemplate('{{#helper}}{{/helper}}').withHelpers(helpers).toCompileTo('ran: helper'); +813c734 +< it('should include in simple block calls', function() { +--- +> it('should include in simple block calls', () => { +819,822c740,741 +< it('should include in helper block calls', function() { +< expectTemplate('{{#helper 1}}{{/helper}}') +< .withHelpers(helpers) +< .toCompileTo('ran: helper'); +--- +> it('should include in helper block calls', () => { +> expectTemplate('{{#helper 1}}{{/helper}}').withHelpers(helpers).toCompileTo('ran: helper'); +825c744 +< it('should include in known helper calls', function() { +--- +> it('should include in known helper calls', () => { +829c748 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +835c754 +< it('should include full id', function() { +--- +> it('should include full id', () => { +842c761 +< it('should include full id if a hash is passed', function() { +--- +> it('should include full id if a hash is passed', () => { +850,851c769,770 +< describe('name conflicts', function() { +< it('helpers take precedence over same-named context properties', function() { +--- +> describe('name conflicts', () => { +> it('helpers take precedence over same-named context properties', () => { +853c772 +< .withHelper('goodbye', function() { +--- +> .withHelper('goodbye', function (this: any) { +856c775 +< .withHelper('cruel', function(world) { +--- +> .withHelper('cruel', function (world) { +861c780 +< world: 'world' +--- +> world: 'world', +863d781 +< .withMessage('Helper executed') +867c785 +< it('helpers take precedence over same-named context properties$', function() { +--- +> it('helpers take precedence over same-named context properties$', () => { +869c787 +< .withHelper('goodbye', function(options) { +--- +> .withHelper('goodbye', function (this: any, options) { +872c790 +< .withHelper('cruel', function(world) { +--- +> .withHelper('cruel', function (world) { +877c795 +< world: 'world' +--- +> world: 'world', +879d796 +< .withMessage('Helper executed') +883c800 +< it('Scoped names take precedence over helpers', function() { +--- +> it('Scoped names take precedence over helpers', () => { +885c802 +< .withHelper('goodbye', function() { +--- +> .withHelper('goodbye', function (this: any) { +888c805 +< .withHelper('cruel', function(world) { +--- +> .withHelper('cruel', function (world) { +893c810 +< world: 'world' +--- +> world: 'world', +895d811 +< .withMessage('Helper not executed') +899,903c815,817 +< it('Scoped names take precedence over block helpers', function() { +< expectTemplate( +< '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}' +< ) +< .withHelper('goodbye', function(options) { +--- +> it('Scoped names take precedence over block helpers', () => { +> expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}') +> .withHelper('goodbye', function (this: any, options) { +906c820 +< .withHelper('cruel', function(world) { +--- +> .withHelper('cruel', function (world) { +911c825 +< world: 'world' +--- +> world: 'world', +913d826 +< .withMessage('Helper executed') +918,919c831,832 +< describe('block params', function() { +< it('should take presedence over context values', function() { +--- +> describe('block params', () => { +> it('should take presedence over context values', () => { +922,923c835,836 +< .withHelper('goodbyes', function(options) { +< equals(options.fn.blockParams, 1); +--- +> .withHelper('goodbyes', function (options) { +> expect(options.fn.blockParams).toEqual(1); +929c842 +< it('should take presedence over helper values', function() { +--- +> it('should take presedence over helper values', () => { +931c844 +< .withHelper('value', function() { +--- +> .withHelper('value', function () { +934,935c847,848 +< .withHelper('goodbyes', function(options) { +< equals(options.fn.blockParams, 1); +--- +> .withHelper('goodbyes', function (options) { +> expect(options.fn.blockParams).toEqual(1); +941,944c854,855 +< it('should not take presedence over pathed values', function() { +< expectTemplate( +< '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}' +< ) +--- +> it('should not take presedence over pathed values', () => { +> expectTemplate('{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}') +946c857 +< .withHelper('value', function() { +--- +> .withHelper('value', function () { +949,950c860,861 +< .withHelper('goodbyes', function(options) { +< equals(options.fn.blockParams, 1); +--- +> .withHelper('goodbyes', function (this: any, options) { +> expect(options.fn.blockParams).toEqual(1); +956,957c867,868 +< it('should take presednece over parent block params', function() { +< var value = 1; +--- +> it('should take presednece over parent block params', () => { +> let value: number; +959c870,875 +< '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}' +--- +> '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}', +> { +> beforeEach() { +> value = 1; +> }, +> } +962c878 +< .withHelper('goodbyes', function(options) { +--- +> .withHelper('goodbyes', function (options) { +966,967c882 +< blockParams: +< options.fn.blockParams === 1 ? [value++, value++] : undefined +--- +> blockParams: options.fn.blockParams === 1 ? [value++, value++] : undefined, +974,977c889,890 +< it('should allow block params on chained helpers', function() { +< expectTemplate( +< '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}' +< ) +--- +> it('should allow block params on chained helpers', () => { +> expectTemplate('{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}') +979,980c892,893 +< .withHelper('goodbyes', function(options) { +< equals(options.fn.blockParams, 1); +--- +> .withHelper('goodbyes', function (options) { +> expect(options.fn.blockParams).toEqual(1); +987,991c900,902 +< describe('built-in helpers malformed arguments ', function() { +< it('if helper - too few arguments', function() { +< expectTemplate('{{#if}}{{/if}}').toThrow( +< /#if requires exactly one argument/ +< ); +--- +> describe('built-in helpers malformed arguments ', () => { +> it('if helper - too few arguments', () => { +> expectTemplate('{{#if}}{{/if}}').toThrow(/#if requires exactly one argument/); +994,997c905,906 +< it('if helper - too many arguments, string', function() { +< expectTemplate('{{#if test "string"}}{{/if}}').toThrow( +< /#if requires exactly one argument/ +< ); +--- +> it('if helper - too many arguments, string', () => { +> expectTemplate('{{#if test "string"}}{{/if}}').toThrow(/#if requires exactly one argument/); +1000,1003c909,910 +< it('if helper - too many arguments, undefined', function() { +< expectTemplate('{{#if test undefined}}{{/if}}').toThrow( +< /#if requires exactly one argument/ +< ); +--- +> it('if helper - too many arguments, undefined', () => { +> expectTemplate('{{#if test undefined}}{{/if}}').toThrow(/#if requires exactly one argument/); +1006,1009c913,914 +< it('if helper - too many arguments, null', function() { +< expectTemplate('{{#if test null}}{{/if}}').toThrow( +< /#if requires exactly one argument/ +< ); +--- +> it('if helper - too many arguments, null', () => { +> expectTemplate('{{#if test null}}{{/if}}').toThrow(/#if requires exactly one argument/); +1012,1015c917,918 +< it('unless helper - too few arguments', function() { +< expectTemplate('{{#unless}}{{/unless}}').toThrow( +< /#unless requires exactly one argument/ +< ); +--- +> it('unless helper - too few arguments', () => { +> expectTemplate('{{#unless}}{{/unless}}').toThrow(/#unless requires exactly one argument/); +1018c921 +< it('unless helper - too many arguments', function() { +--- +> it('unless helper - too many arguments', () => { +1024,1027c927,928 +< it('with helper - too few arguments', function() { +< expectTemplate('{{#with}}{{/with}}').toThrow( +< /#with requires exactly one argument/ +< ); +--- +> it('with helper - too few arguments', () => { +> expectTemplate('{{#with}}{{/with}}').toThrow(/#with requires exactly one argument/); +1030c931 +< it('with helper - too many arguments', function() { +--- +> it('with helper - too many arguments', () => { +1037,1038c938,939 +< describe('the lookupProperty-option', function() { +< it('should be passed to custom helpers', function() { +--- +> describe('the lookupProperty-option', () => { +> it('should be passed to custom helpers', () => { +1040c941 +< .withHelper('testHelper', function testHelper(options) { +--- +> .withHelper('testHelper', function testHelper(this: any, options) { +1047a949,954 +> +> function deleteAllKeys(obj: { [key: string]: any }) { +> for (const key of Object.keys(obj)) { +> delete obj[key]; +> } +> } diff --git a/packages/kbn-handlebars/.patches/regressions.patch b/packages/kbn-handlebars/.patches/regressions.patch new file mode 100644 index 0000000000000..2fcd491310619 --- /dev/null +++ b/packages/kbn-handlebars/.patches/regressions.patch @@ -0,0 +1,518 @@ +1,2c1,11 +< describe('Regressions', function() { +< it('GH-94: Cannot read property of undefined', function() { +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('Regressions', () => { +> it('GH-94: Cannot read property of undefined', () => { +9,10c18,19 +< name: 'Charles Darwin' +< } +--- +> name: 'Charles Darwin', +> }, +13,15c22,24 +< title: 'Lazarillo de Tormes' +< } +< ] +--- +> title: 'Lazarillo de Tormes', +> }, +> ], +17d25 +< .withMessage('Renders without an undefined property error') +21,43c29,34 +< it("GH-150: Inverted sections print when they shouldn't", function() { +< var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; +< +< expectTemplate(string) +< .withMessage( +< "inverted sections run when property isn't present in context" +< ) +< .toCompileTo('not set :: '); +< +< expectTemplate(string) +< .withInput({ set: undefined }) +< .withMessage('inverted sections run when property is undefined') +< .toCompileTo('not set :: '); +< +< expectTemplate(string) +< .withInput({ set: false }) +< .withMessage('inverted sections run when property is false') +< .toCompileTo('not set :: '); +< +< expectTemplate(string) +< .withInput({ set: true }) +< .withMessage("inverted sections don't run when property is true") +< .toCompileTo(' :: set'); +--- +> it("GH-150: Inverted sections print when they shouldn't", () => { +> const string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; +> expectTemplate(string).toCompileTo('not set :: '); +> expectTemplate(string).withInput({ set: undefined }).toCompileTo('not set :: '); +> expectTemplate(string).withInput({ set: false }).toCompileTo('not set :: '); +> expectTemplate(string).withInput({ set: true }).toCompileTo(' :: set'); +46c37 +< it('GH-158: Using array index twice, breaks the template', function() { +--- +> it('GH-158: Using array index twice, breaks the template', () => { +49d39 +< .withMessage('it works as expected') +53,54c43,44 +< it("bug reported by @fat where lambdas weren't being properly resolved", function() { +< var string = +--- +> it("bug reported by @fat where lambdas weren't being properly resolved", () => { +> const string = +69,70c59,60 +< var data = { +< thing: function() { +--- +> const data = { +> thing() { +76c66 +< { className: 'three', word: '@sayrer' } +--- +> { className: 'three', word: '@sayrer' }, +78c68 +< hasThings: function() { +--- +> hasThings() { +80c70 +< } +--- +> }, +83c73 +< var output = +--- +> const output = +92,94c82 +< expectTemplate(string) +< .withInput(data) +< .toCompileTo(output); +--- +> expectTemplate(string).withInput(data).toCompileTo(output); +97,100c85,86 +< it('GH-408: Multiple loops fail', function() { +< expectTemplate( +< '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}' +< ) +--- +> it('GH-408: Multiple loops fail', () => { +> expectTemplate('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}') +103c89 +< { name: 'Jane Doe', location: { city: 'New York' } } +--- +> { name: 'Jane Doe', location: { city: 'New York' } }, +105d90 +< .withMessage('It should output multiple times') +109,110c94,95 +< it('GS-428: Nested if else rendering', function() { +< var succeedingTemplate = +--- +> it('GS-428: Nested if else rendering', () => { +> const succeedingTemplate = +112c97 +< var failingTemplate = +--- +> const failingTemplate = +115,116c100,101 +< var helpers = { +< blk: function(block) { +--- +> const helpers = { +> blk(block: Handlebars.HelperOptions) { +119c104 +< inverse: function(block) { +--- +> inverse(block: Handlebars.HelperOptions) { +121c106 +< } +--- +> }, +124,130c109,110 +< expectTemplate(succeedingTemplate) +< .withHelpers(helpers) +< .toCompileTo(' Expected '); +< +< expectTemplate(failingTemplate) +< .withHelpers(helpers) +< .toCompileTo(' Expected '); +--- +> expectTemplate(succeedingTemplate).withHelpers(helpers).toCompileTo(' Expected '); +> expectTemplate(failingTemplate).withHelpers(helpers).toCompileTo(' Expected '); +133,136c113,114 +< it('GH-458: Scoped this identifier', function() { +< expectTemplate('{{./foo}}') +< .withInput({ foo: 'bar' }) +< .toCompileTo('bar'); +--- +> it('GH-458: Scoped this identifier', () => { +> expectTemplate('{{./foo}}').withInput({ foo: 'bar' }).toCompileTo('bar'); +139c117 +< it('GH-375: Unicode line terminators', function() { +--- +> it('GH-375: Unicode line terminators', () => { +143c121 +< it('GH-534: Object prototype aliases', function() { +--- +> it('GH-534: Object prototype aliases', () => { +144a123 +> // @ts-expect-error +147,149c126 +< expectTemplate('{{foo}}') +< .withInput({ foo: 'bar' }) +< .toCompileTo('bar'); +--- +> expectTemplate('{{foo}}').withInput({ foo: 'bar' }).toCompileTo('bar'); +150a128 +> // @ts-expect-error +155,157c133,135 +< it('GH-437: Matching escaping', function() { +< expectTemplate('{{{a}}').toThrow(Error, /Parse error on/); +< expectTemplate('{{a}}}').toThrow(Error, /Parse error on/); +--- +> it('GH-437: Matching escaping', () => { +> expectTemplate('{{{a}}').toThrow(/Parse error on/); +> expectTemplate('{{a}}}').toThrow(/Parse error on/); +160,166c138,140 +< it('GH-676: Using array in escaping mustache fails', function() { +< var data = { arr: [1, 2] }; +< +< expectTemplate('{{arr}}') +< .withInput(data) +< .withMessage('it works as expected') +< .toCompileTo(data.arr.toString()); +--- +> it('GH-676: Using array in escaping mustache fails', () => { +> const data = { arr: [1, 2] }; +> expectTemplate('{{arr}}').withInput(data).toCompileTo(data.arr.toString()); +169c143 +< it('Mustache man page', function() { +--- +> it('Mustache man page', () => { +177c151 +< in_ca: true +--- +> in_ca: true, +179,182c153 +< .withMessage('the hello world mustache example works') +< .toCompileTo( +< 'Hello Chris. You have just won $10000! Well, $6000, after taxes.' +< ); +--- +> .toCompileTo('Hello Chris. You have just won $10000! Well, $6000, after taxes.'); +185c156 +< it('GH-731: zero context rendering', function() { +--- +> it('GH-731: zero context rendering', () => { +189c160 +< bar: 'OK' +--- +> bar: 'OK', +194,197c165,166 +< it('GH-820: zero pathed rendering', function() { +< expectTemplate('{{foo.bar}}') +< .withInput({ foo: 0 }) +< .toCompileTo(''); +--- +> it('GH-820: zero pathed rendering', () => { +> expectTemplate('{{foo.bar}}').withInput({ foo: 0 }).toCompileTo(''); +200c169 +< it('GH-837: undefined values for helpers', function() { +--- +> it('GH-837: undefined values for helpers', () => { +203c172 +< str: function(value) { +--- +> str(value) { +205c174 +< } +--- +> }, +210c179 +< it('GH-926: Depths and de-dupe', function() { +--- +> it('GH-926: Depths and de-dupe', () => { +217c186 +< notData: [1] +--- +> notData: [1], +222c191 +< it('GH-1021: Each empty string key', function() { +--- +> it('GH-1021: Each empty string key', () => { +228,229c197,198 +< value: 10000 +< } +--- +> value: 10000, +> }, +234,248c203,204 +< it('GH-1054: Should handle simple safe string responses', function() { +< expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}') +< .withHelpers({ +< wrap: function(options) { +< return new Handlebars.SafeString(options.fn()); +< } +< }) +< .withPartials({ +< partial: '{{#wrap}}{{/wrap}}' +< }) +< .toCompileTo(''); +< }); +< +< it('GH-1065: Sparse arrays', function() { +< var array = []; +--- +> it('GH-1065: Sparse arrays', () => { +> const array = []; +252c208 +< .withInput({ array: array }) +--- +> .withInput({ array }) +256c212 +< it('GH-1093: Undefined helper context', function() { +--- +> it('GH-1093: Undefined helper context', () => { +260c216 +< helper: function() { +--- +> helper(this: any) { +263c219 +< for (var name in this) { +--- +> for (const name in this) { +270c226 +< } +--- +> }, +275,306c231 +< it('should support multiple levels of inline partials', function() { +< expectTemplate( +< '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}' +< ) +< .withPartials({ +< doctype: 'doctype{{> content}}', +< layout: +< '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' +< }) +< .toCompileTo('doctypelayoutsubcontent'); +< }); +< +< it('GH-1089: should support failover content in multiple levels of inline partials', function() { +< expectTemplate('{{#> layout}}{{/layout}}') +< .withPartials({ +< doctype: 'doctype{{> content}}', +< layout: +< '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' +< }) +< .toCompileTo('doctypelayoutsubcontent'); +< }); +< +< it('GH-1099: should support greater than 3 nested levels of inline partials', function() { +< expectTemplate('{{#> layout}}Outer{{/layout}}') +< .withPartials({ +< layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', +< inner: '' +< }) +< .toCompileTo('Outer'); +< }); +< +< it('GH-1135 : Context handling within each iteration', function() { +--- +> it('GH-1135 : Context handling within each iteration', () => { +315c240 +< myif: function(conditional, options) { +--- +> myif(conditional, options) { +321c246 +< } +--- +> }, +326,343c251,252 +< it('GH-1186: Support block params for existing programs', function() { +< expectTemplate( +< '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' + +< '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + +< '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}' +< ) +< .withInput({ +< listOne: ['a'], +< listTwo: ['b'] +< }) +< .withMessage('') +< .toCompileTo('ab'); +< }); +< +< it('GH-1319: "unless" breaks when "each" value equals "null"', function() { +< expectTemplate( +< '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}' +< ) +--- +> it('GH-1319: "unless" breaks when "each" value equals "null"', () => { +> expectTemplate('{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}') +346c255 +< list: [null, 'a'] +--- +> list: [null, 'a'], +348d256 +< .withMessage('') +352,457c260 +< it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() { +< expectTemplate('template {{>partial}} template') +< .withPartials({ +< partialWithBlock: +< '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', +< partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' +< }) +< .toCompileTo('template block partial block template'); +< }); +< +< describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() { +< it('should compile and execute templates', function() { +< var newHandlebarsInstance = Handlebars.create(); +< +< registerTemplate(newHandlebarsInstance, compiledTemplateVersion7()); +< newHandlebarsInstance.registerHelper('loud', function(value) { +< return value.toUpperCase(); +< }); +< var result = newHandlebarsInstance.templates['test.hbs']({ +< name: 'yehuda' +< }); +< equals(result.trim(), 'YEHUDA'); +< }); +< +< it('should call "helperMissing" if a helper is missing', function() { +< var newHandlebarsInstance = Handlebars.create(); +< +< shouldThrow( +< function() { +< registerTemplate(newHandlebarsInstance, compiledTemplateVersion7()); +< newHandlebarsInstance.templates['test.hbs']({}); +< }, +< Handlebars.Exception, +< 'Missing helper: "loud"' +< ); +< }); +< +< it('should pass "options.lookupProperty" to "lookup"-helper, even with old templates', function() { +< var newHandlebarsInstance = Handlebars.create(); +< registerTemplate( +< newHandlebarsInstance, +< compiledTemplateVersion7_usingLookupHelper() +< ); +< +< newHandlebarsInstance.templates['test.hbs']({}); +< +< expect( +< newHandlebarsInstance.templates['test.hbs']({ +< property: 'a', +< test: { a: 'b' } +< }) +< ).to.equal('b'); +< }); +< +< function registerTemplate(Handlebars, compileTemplate) { +< var template = Handlebars.template, +< templates = (Handlebars.templates = Handlebars.templates || {}); +< templates['test.hbs'] = template(compileTemplate); +< } +< +< function compiledTemplateVersion7() { +< return { +< compiler: [7, '>= 4.0.0'], +< main: function(container, depth0, helpers, partials, data) { +< return ( +< container.escapeExpression( +< ( +< helpers.loud || +< (depth0 && depth0.loud) || +< helpers.helperMissing +< ).call( +< depth0 != null ? depth0 : container.nullContext || {}, +< depth0 != null ? depth0.name : depth0, +< { name: 'loud', hash: {}, data: data } +< ) +< ) + '\n\n' +< ); +< }, +< useData: true +< }; +< } +< +< function compiledTemplateVersion7_usingLookupHelper() { +< // This is the compiled version of "{{lookup test property}}" +< return { +< compiler: [7, '>= 4.0.0'], +< main: function(container, depth0, helpers, partials, data) { +< return container.escapeExpression( +< helpers.lookup.call( +< depth0 != null ? depth0 : container.nullContext || {}, +< depth0 != null ? depth0.test : depth0, +< depth0 != null ? depth0.property : depth0, +< { +< name: 'lookup', +< hash: {}, +< data: data +< } +< ) +< ); +< }, +< useData: true +< }; +< } +< }); +< +< it('should allow hash with protected array names', function() { +--- +> it('should allow hash with protected array names', () => { +461c264 +< helpa: function(options) { +--- +> helpa(options) { +463c266 +< } +--- +> }, +468,496c271,272 +< describe('GH-1598: Performance degradation for partials since v4.3.0', function() { +< // Do not run test for runs without compiler +< if (!Handlebars.compile) { +< return; +< } +< +< var newHandlebarsInstance; +< beforeEach(function() { +< newHandlebarsInstance = Handlebars.create(); +< }); +< afterEach(function() { +< sinon.restore(); +< }); +< +< it('should only compile global partials once', function() { +< var templateSpy = sinon.spy(newHandlebarsInstance, 'template'); +< newHandlebarsInstance.registerPartial({ +< dude: 'I am a partial' +< }); +< var string = 'Dudes: {{> dude}} {{> dude}}'; +< newHandlebarsInstance.compile(string)(); // This should compile template + partial once +< newHandlebarsInstance.compile(string)(); // This should only compile template +< equal(templateSpy.callCount, 3); +< sinon.restore(); +< }); +< }); +< +< describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", function() { +< it('should treat undefined helpers like non-existing helpers', function() { +--- +> describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", () => { +> it('should treat undefined helpers like non-existing helpers', () => { diff --git a/packages/kbn-handlebars/.patches/security.patch b/packages/kbn-handlebars/.patches/security.patch new file mode 100644 index 0000000000000..89d2060d8977b --- /dev/null +++ b/packages/kbn-handlebars/.patches/security.patch @@ -0,0 +1,443 @@ +1,10c1,15 +< describe('security issues', function() { +< describe('GH-1495: Prevent Remote Code Execution via constructor', function() { +< it('should not allow constructors to be accessed', function() { +< expectTemplate('{{lookup (lookup this "constructor") "name"}}') +< .withInput({}) +< .toCompileTo(''); +< +< expectTemplate('{{constructor.name}}') +< .withInput({}) +< .toCompileTo(''); +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import Handlebars from '..'; +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('security issues', () => { +> describe('GH-1495: Prevent Remote Code Execution via constructor', () => { +> it('should not allow constructors to be accessed', () => { +> expectTemplate('{{lookup (lookup this "constructor") "name"}}').withInput({}).toCompileTo(''); +> expectTemplate('{{constructor.name}}').withInput({}).toCompileTo(''); +13c18 +< it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() { +--- +> it('GH-1603: should not allow constructors to be accessed (lookup via toString)', () => { +16c21 +< .withHelper('list', function(element) { +--- +> .withHelper('list', function (element) { +22c27 +< it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() { +--- +> it('should allow the "constructor" property to be accessed if it is an "ownProperty"', () => { +32c37 +< it('should allow the "constructor" property to be accessed if it is an "own property"', function() { +--- +> it('should allow the "constructor" property to be accessed if it is an "own property"', () => { +39,45c44,46 +< describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() { +< if (!Handlebars.compile) { +< return; +< } +< +< describe('without the option "allowExplicitCallOfHelperMissing"', function() { +< it('should throw an exception when calling "{{helperMissing}}" ', function() { +--- +> describe('GH-1558: Prevent explicit call of helperMissing-helpers', () => { +> describe('without the option "allowExplicitCallOfHelperMissing"', () => { +> it('should throw an exception when calling "{{helperMissing}}" ', () => { +49c50 +< it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { +--- +> it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', () => { +53,56c54,57 +< it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { +< var functionCalls = []; +< expect(function() { +< var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); +--- +> it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', () => { +> const functionCalls = []; +> expect(() => { +> const template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); +58c59 +< fn: function() { +--- +> fn() { +60c61 +< } +--- +> }, +62,63c63,64 +< }).to.throw(Error); +< expect(functionCalls.length).to.equal(0); +--- +> }).toThrow(Error); +> expect(functionCalls.length).toEqual(0); +66c67 +< it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { +--- +> it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', () => { +69c70 +< fn: function() { +--- +> fn() { +71c72 +< } +--- +> }, +76,110d76 +< +< describe('with the option "allowCallsToHelperMissing" set to true', function() { +< it('should not throw an exception when calling "{{helperMissing}}" ', function() { +< var template = Handlebars.compile('{{helperMissing}}'); +< template({}, { allowCallsToHelperMissing: true }); +< }); +< +< it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { +< var template = Handlebars.compile( +< '{{#helperMissing}}{{/helperMissing}}' +< ); +< template({}, { allowCallsToHelperMissing: true }); +< }); +< +< it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { +< var functionCalls = []; +< var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); +< template( +< { +< fn: function() { +< functionCalls.push('called'); +< } +< }, +< { allowCallsToHelperMissing: true } +< ); +< equals(functionCalls.length, 1); +< }); +< +< it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { +< var template = Handlebars.compile( +< '{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}' +< ); +< template({}, { allowCallsToHelperMissing: true }); +< }); +< }); +113,114c79,81 +< describe('GH-1563', function() { +< it('should not allow to access constructor after overriding via __defineGetter__', function() { +--- +> describe('GH-1563', () => { +> it('should not allow to access constructor after overriding via __defineGetter__', () => { +> // @ts-expect-error +116c83 +< return this.skip(); // Browser does not support this exploit anyway +--- +> return; // Browser does not support this exploit anyway +130,131c97,98 +< describe('GH-1595: dangerous properties', function() { +< var templates = [ +--- +> describe('GH-1595: dangerous properties', () => { +> const templates = [ +141c108 +< '{{lookup this "__proto__"}}' +--- +> '{{lookup this "__proto__"}}', +144,257c111,114 +< templates.forEach(function(template) { +< describe('access should be denied to ' + template, function() { +< it('by default', function() { +< expectTemplate(template) +< .withInput({}) +< .toCompileTo(''); +< }); +< it(' with proto-access enabled', function() { +< expectTemplate(template) +< .withInput({}) +< .withRuntimeOptions({ +< allowProtoPropertiesByDefault: true, +< allowProtoMethodsByDefault: true +< }) +< .toCompileTo(''); +< }); +< }); +< }); +< }); +< describe('GH-1631: disallow access to prototype functions', function() { +< function TestClass() {} +< +< TestClass.prototype.aProperty = 'propertyValue'; +< TestClass.prototype.aMethod = function() { +< return 'returnValue'; +< }; +< +< beforeEach(function() { +< handlebarsEnv.resetLoggedPropertyAccesses(); +< }); +< +< afterEach(function() { +< sinon.restore(); +< }); +< +< describe('control access to prototype methods via "allowedProtoMethods"', function() { +< checkProtoMethodAccess({}); +< +< describe('in compat mode', function() { +< checkProtoMethodAccess({ compat: true }); +< }); +< +< function checkProtoMethodAccess(compileOptions) { +< it('should be prohibited by default and log a warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aMethod}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .toCompileTo(''); +< +< expect(spy.calledOnce).to.be.true(); +< expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); +< }); +< +< it('should only log the warning once', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aMethod}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .toCompileTo(''); +< +< expectTemplate('{{aMethod}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .toCompileTo(''); +< +< expect(spy.calledOnce).to.be.true(); +< expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); +< }); +< +< it('can be allowed, which disables the warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aMethod}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowedProtoMethods: { +< aMethod: true +< } +< }) +< .toCompileTo('returnValue'); +< +< expect(spy.callCount).to.equal(0); +< }); +< +< it('can be turned on by default, which disables the warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aMethod}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowProtoMethodsByDefault: true +< }) +< .toCompileTo('returnValue'); +< +< expect(spy.callCount).to.equal(0); +< }); +< +< it('can be turned off by default, which disables the warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aMethod}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowProtoMethodsByDefault: false +< }) +< .toCompileTo(''); +< +< expect(spy.callCount).to.equal(0); +--- +> templates.forEach((template) => { +> describe('access should be denied to ' + template, () => { +> it('by default', () => { +> expectTemplate(template).withInput({}).toCompileTo(''); +259,399d115 +< +< it('can be turned off, if turned on by default', function() { +< expectTemplate('{{aMethod}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowProtoMethodsByDefault: true, +< allowedProtoMethods: { +< aMethod: false +< } +< }) +< .toCompileTo(''); +< }); +< } +< +< it('should cause the recursive lookup by default (in "compat" mode)', function() { +< expectTemplate('{{#aString}}{{trim}}{{/aString}}') +< .withInput({ aString: ' abc ', trim: 'trim' }) +< .withCompileOptions({ compat: true }) +< .toCompileTo('trim'); +< }); +< +< it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function() { +< expectTemplate('{{#aString}}{{trim}}{{/aString}}') +< .withInput({ aString: ' abc ', trim: 'trim' }) +< .withCompileOptions({ compat: true }) +< .withRuntimeOptions({ +< allowedProtoMethods: { +< trim: true +< } +< }) +< .toCompileTo('abc'); +< }); +< }); +< +< describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function() { +< checkProtoPropertyAccess({}); +< +< describe('in compat-mode', function() { +< checkProtoPropertyAccess({ compat: true }); +< }); +< +< describe('in strict-mode', function() { +< checkProtoPropertyAccess({ strict: true }); +< }); +< +< function checkProtoPropertyAccess(compileOptions) { +< it('should be prohibited by default and log a warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aProperty}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .toCompileTo(''); +< +< expect(spy.calledOnce).to.be.true(); +< expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); +< }); +< +< it('can be explicitly prohibited by default, which disables the warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aProperty}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowProtoPropertiesByDefault: false +< }) +< .toCompileTo(''); +< +< expect(spy.callCount).to.equal(0); +< }); +< +< it('can be turned on, which disables the warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aProperty}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowedProtoProperties: { +< aProperty: true +< } +< }) +< .toCompileTo('propertyValue'); +< +< expect(spy.callCount).to.equal(0); +< }); +< +< it('can be turned on by default, which disables the warning', function() { +< var spy = sinon.spy(console, 'error'); +< +< expectTemplate('{{aProperty}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowProtoPropertiesByDefault: true +< }) +< .toCompileTo('propertyValue'); +< +< expect(spy.callCount).to.equal(0); +< }); +< +< it('can be turned off, if turned on by default', function() { +< expectTemplate('{{aProperty}}') +< .withInput(new TestClass()) +< .withCompileOptions(compileOptions) +< .withRuntimeOptions({ +< allowProtoPropertiesByDefault: true, +< allowedProtoProperties: { +< aProperty: false +< } +< }) +< .toCompileTo(''); +< }); +< } +< }); +< +< describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function() { +< beforeEach(function simulateRuntimeWithoutLookupProperty() { +< var oldTemplateMethod = handlebarsEnv.template; +< sinon.replace(handlebarsEnv, 'template', function(templateSpec) { +< templateSpec.main = wrapToAdjustContainer(templateSpec.main); +< return oldTemplateMethod.call(this, templateSpec); +< }); +< }); +< +< afterEach(function() { +< sinon.restore(); +< }); +< +< it('should work with simple properties', function() { +< expectTemplate('{{aProperty}}') +< .withInput({ aProperty: 'propertyValue' }) +< .toCompileTo('propertyValue'); +< }); +< +< it('should work with Array.prototype.length', function() { +< expectTemplate('{{anArray.length}}') +< .withInput({ anArray: ['a', 'b', 'c'] }) +< .toCompileTo('3'); +404,409c120,122 +< describe('escapes template variables', function() { +< it('in compat mode', function() { +< expectTemplate("{{'a\\b'}}") +< .withCompileOptions({ compat: true }) +< .withInput({ 'a\\b': 'c' }) +< .toCompileTo('c'); +--- +> describe('escapes template variables', () => { +> it('in default mode', () => { +> expectTemplate("{{'a\\b'}}").withCompileOptions().withInput({ 'a\\b': 'c' }).toCompileTo('c'); +412,418c125 +< it('in default mode', function() { +< expectTemplate("{{'a\\b'}}") +< .withCompileOptions() +< .withInput({ 'a\\b': 'c' }) +< .toCompileTo('c'); +< }); +< it('in default mode', function() { +--- +> it('in strict mode', () => { +426,432d132 +< +< function wrapToAdjustContainer(precompiledTemplateFunction) { +< return function templateFunctionWrapper(container /*, more args */) { +< delete container.lookupProperty; +< return precompiledTemplateFunction.apply(this, arguments); +< }; +< } diff --git a/packages/kbn-handlebars/.patches/strict.patch b/packages/kbn-handlebars/.patches/strict.patch new file mode 100644 index 0000000000000..be50113e1416d --- /dev/null +++ b/packages/kbn-handlebars/.patches/strict.patch @@ -0,0 +1,180 @@ +1,5c1,12 +< var Exception = Handlebars.Exception; +< +< describe('strict', function() { +< describe('strict mode', function() { +< it('should error on missing property lookup', function() { +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('strict', () => { +> describe('strict mode', () => { +> it('should error on missing property lookup', () => { +8c15 +< .toThrow(Exception, /"hello" not defined in/); +--- +> .toThrow(/"hello" not defined in/); +11c18 +< it('should error on missing child', function() { +--- +> it('should error on missing child', () => { +20c27 +< .toThrow(Exception, /"bar" not defined in/); +--- +> .toThrow(/"bar" not defined in/); +23c30 +< it('should handle explicit undefined', function() { +--- +> it('should handle explicit undefined', () => { +30c37 +< it('should error on missing property lookup in known helpers mode', function() { +--- +> it('should error on missing property lookup in known helpers mode', () => { +34c41 +< knownHelpersOnly: true +--- +> knownHelpersOnly: true, +36c43 +< .toThrow(Exception, /"hello" not defined in/); +--- +> .toThrow(/"hello" not defined in/); +39,42c46,47 +< it('should error on missing context', function() { +< expectTemplate('{{hello}}') +< .withCompileOptions({ strict: true }) +< .toThrow(Error); +--- +> it('should error on missing context', () => { +> expectTemplate('{{hello}}').withCompileOptions({ strict: true }).toThrow(Error); +45,47c50,52 +< it('should error on missing data lookup', function() { +< var xt = expectTemplate('{{@hello}}').withCompileOptions({ +< strict: true +--- +> it('should error on missing data lookup', () => { +> const xt = expectTemplate('{{@hello}}').withCompileOptions({ +> strict: true, +55c60 +< it('should not run helperMissing for helper calls', function() { +--- +> it('should not run helperMissing for helper calls', () => { +59c64 +< .toThrow(Exception, /"hello" not defined in/); +--- +> .toThrow(/"hello" not defined in/); +64c69 +< .toThrow(Exception, /"hello" not defined in/); +--- +> .toThrow(/"hello" not defined in/); +67c72 +< it('should throw on ambiguous blocks', function() { +--- +> it('should throw on ambiguous blocks', () => { +70c75 +< .toThrow(Exception, /"hello" not defined in/); +--- +> .toThrow(/"hello" not defined in/); +74c79 +< .toThrow(Exception, /"hello" not defined in/); +--- +> .toThrow(/"hello" not defined in/); +79c84 +< .toThrow(Exception, /"bar" not defined in/); +--- +> .toThrow(/"bar" not defined in/); +82c87 +< it('should allow undefined parameters when passed to helpers', function() { +--- +> it('should allow undefined parameters when passed to helpers', () => { +88c93 +< it('should allow undefined hash when passed to helpers', function() { +--- +> it('should allow undefined hash when passed to helpers', () => { +91c96 +< strict: true +--- +> strict: true, +94,96c99,101 +< helper: function(options) { +< equals('value' in options.hash, true); +< equals(options.hash.value, undefined); +--- +> helper(options) { +> expect('value' in options.hash).toEqual(true); +> expect(options.hash.value).toBeUndefined(); +98c103 +< } +--- +> }, +103c108 +< it('should show error location on missing property lookup', function() { +--- +> it('should show error location on missing property lookup', () => { +106c111 +< .toThrow(Exception, '"hello" not defined in [object Object] - 4:5'); +--- +> .toThrow('"hello" not defined in [object Object] - 4:5'); +109c114 +< it('should error contains correct location properties on missing property lookup', function() { +--- +> it('should error contains correct location properties on missing property lookup', () => { +111,114c116,118 +< var template = CompilerContext.compile('\n\n\n {{hello}}', { +< strict: true +< }); +< template({}); +--- +> expectTemplate('\n\n\n {{hello}}') +> .withCompileOptions({ strict: true }) +> .toCompileTo('throw before asserting this'); +116,119c120,123 +< equals(error.lineNumber, 4); +< equals(error.endLineNumber, 4); +< equals(error.column, 5); +< equals(error.endColumn, 10); +--- +> expect(error.lineNumber).toEqual(4); +> expect(error.endLineNumber).toEqual(4); +> expect(error.column).toEqual(5); +> expect(error.endColumn).toEqual(10); +124,128c128,130 +< describe('assume objects', function() { +< it('should ignore missing property', function() { +< expectTemplate('{{hello}}') +< .withCompileOptions({ assumeObjects: true }) +< .toCompileTo(''); +--- +> describe('assume objects', () => { +> it('should ignore missing property', () => { +> expectTemplate('{{hello}}').withCompileOptions({ assumeObjects: true }).toCompileTo(''); +131c133 +< it('should ignore missing child', function() { +--- +> it('should ignore missing child', () => { +138,141c140,141 +< it('should error on missing object', function() { +< expectTemplate('{{hello.bar}}') +< .withCompileOptions({ assumeObjects: true }) +< .toThrow(Error); +--- +> it('should error on missing object', () => { +> expectTemplate('{{hello.bar}}').withCompileOptions({ assumeObjects: true }).toThrow(Error); +144c144 +< it('should error on missing context', function() { +--- +> it('should error on missing context', () => { +151c151 +< it('should error on missing data lookup', function() { +--- +> it('should error on missing data lookup', () => { +158c158 +< it('should execute blockHelperMissing', function() { +--- +> it('should execute blockHelperMissing', () => { diff --git a/packages/kbn-handlebars/.patches/subexpressions.patch b/packages/kbn-handlebars/.patches/subexpressions.patch new file mode 100644 index 0000000000000..bc29db240f3c1 --- /dev/null +++ b/packages/kbn-handlebars/.patches/subexpressions.patch @@ -0,0 +1,318 @@ +1,2c1,12 +< describe('subexpressions', function() { +< it('arg-less helper', function() { +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import Handlebars from '..'; +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('subexpressions', () => { +> it('arg-less helper', () => { +5c15 +< foo: function(val) { +--- +> foo(val) { +8c18 +< bar: function() { +--- +> bar() { +10c20 +< } +--- +> }, +15c25 +< it('helper w args', function() { +--- +> it('helper w args', () => { +19c29 +< blog: function(val) { +--- +> blog(val) { +22c32 +< equal: function(x, y) { +--- +> equal(x, y) { +24c34 +< } +--- +> }, +29c39 +< it('mixed paths and helpers', function() { +--- +> it('mixed paths and helpers', () => { +33c43 +< blog: function(val, that, theOther) { +--- +> blog(val, that, theOther) { +36c46 +< equal: function(x, y) { +--- +> equal(x, y) { +38c48 +< } +--- +> }, +43c53 +< it('supports much nesting', function() { +--- +> it('supports much nesting', () => { +47c57 +< blog: function(val) { +--- +> blog(val) { +50c60 +< equal: function(x, y) { +--- +> equal(x, y) { +52c62 +< } +--- +> }, +57,60c67,70 +< it('GH-800 : Complex subexpressions', function() { +< var context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } }; +< var helpers = { +< dash: function(a, b) { +--- +> it('GH-800 : Complex subexpressions', () => { +> const context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } }; +> const helpers = { +> dash(a: any, b: any) { +63c73 +< concat: function(a, b) { +--- +> concat(a: any, b: any) { +65c75 +< } +--- +> }, +94,97c104,107 +< it('provides each nested helper invocation its own options hash', function() { +< var lastOptions = null; +< var helpers = { +< equal: function(x, y, options) { +--- +> it('provides each nested helper invocation its own options hash', () => { +> let lastOptions: Handlebars.HelperOptions; +> const helpers = { +> equal(x: any, y: any, options: Handlebars.HelperOptions) { +103c113 +< } +--- +> }, +105,107c115 +< expectTemplate('{{equal (equal true true) true}}') +< .withHelpers(helpers) +< .toCompileTo('true'); +--- +> expectTemplate('{{equal (equal true true) true}}').withHelpers(helpers).toCompileTo('true'); +110c118 +< it('with hashes', function() { +--- +> it('with hashes', () => { +114c122 +< blog: function(val) { +--- +> blog(val) { +117c125 +< equal: function(x, y) { +--- +> equal(x, y) { +119c127 +< } +--- +> }, +124c132 +< it('as hashes', function() { +--- +> it('as hashes', () => { +127c135 +< blog: function(options) { +--- +> blog(options) { +130c138 +< equal: function(x, y) { +--- +> equal(x, y) { +132c140 +< } +--- +> }, +137,140c145,146 +< it('multiple subexpressions in a hash', function() { +< expectTemplate( +< '{{input aria-label=(t "Name") placeholder=(t "Example User")}}' +< ) +--- +> it('multiple subexpressions in a hash', () => { +> expectTemplate('{{input aria-label=(t "Name") placeholder=(t "Example User")}}') +142,145c148,151 +< input: function(options) { +< var hash = options.hash; +< var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); +< var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); +--- +> input(options) { +> const hash = options.hash; +> const ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); +> const placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); +147,151c153 +< '' +--- +> '' +154c156 +< t: function(defaultString) { +--- +> t(defaultString) { +156c158 +< } +--- +> }, +161,164c163,164 +< it('multiple subexpressions in a hash with context', function() { +< expectTemplate( +< '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}' +< ) +--- +> it('multiple subexpressions in a hash with context', () => { +> expectTemplate('{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}') +168,169c168,169 +< placeholder: 'Example User' +< } +--- +> placeholder: 'Example User', +> }, +172,175c172,175 +< input: function(options) { +< var hash = options.hash; +< var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); +< var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); +--- +> input(options) { +> const hash = options.hash; +> const ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); +> const placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); +177,181c177 +< '' +--- +> '' +184c180 +< t: function(defaultString) { +--- +> t(defaultString) { +186,212d181 +< } +< }) +< .toCompileTo(''); +< }); +< +< it('in string params mode,', function() { +< expectTemplate('{{snog (blorg foo x=y) yeah a=b}}') +< .withCompileOptions({ stringParams: true }) +< .withHelpers({ +< snog: function(a, b, options) { +< equals(a, 'foo'); +< equals( +< options.types.length, +< 2, +< 'string params for outer helper processed correctly' +< ); +< equals( +< options.types[0], +< 'SubExpression', +< 'string params for outer helper processed correctly' +< ); +< equals( +< options.types[1], +< 'PathExpression', +< 'string params for outer helper processed correctly' +< ); +< return a + b; +214,231d182 +< +< blorg: function(a, options) { +< equals( +< options.types.length, +< 1, +< 'string params for inner helper processed correctly' +< ); +< equals( +< options.types[0], +< 'PathExpression', +< 'string params for inner helper processed correctly' +< ); +< return a; +< } +< }) +< .withInput({ +< foo: {}, +< yeah: {} +233,248c184 +< .toCompileTo('fooyeah'); +< }); +< +< it('as hashes in string params mode', function() { +< expectTemplate('{{blog fun=(bork)}}') +< .withCompileOptions({ stringParams: true }) +< .withHelpers({ +< blog: function(options) { +< equals(options.hashTypes.fun, 'SubExpression'); +< return 'val is ' + options.hash.fun; +< }, +< bork: function() { +< return 'BORK'; +< } +< }) +< .toCompileTo('val is BORK'); +--- +> .toCompileTo(''); +251c187 +< it('subexpression functions on the context', function() { +--- +> it('subexpression functions on the context', () => { +254c190 +< bar: function() { +--- +> bar() { +256c192 +< } +--- +> }, +259c195 +< foo: function(val) { +--- +> foo(val) { +261c197 +< } +--- +> }, +266c202 +< it("subexpressions can't just be property lookups", function() { +--- +> it("subexpressions can't just be property lookups", () => { +269c205 +< bar: 'LOL' +--- +> bar: 'LOL', +272c208 +< foo: function(val) { +--- +> foo(val) { +274c210 +< } +--- +> }, diff --git a/packages/kbn-handlebars/.patches/utils.patch b/packages/kbn-handlebars/.patches/utils.patch new file mode 100644 index 0000000000000..485d69652544c --- /dev/null +++ b/packages/kbn-handlebars/.patches/utils.patch @@ -0,0 +1,109 @@ +1,86c1,21 +< describe('utils', function() { +< describe('#SafeString', function() { +< it('constructing a safestring from a string and checking its type', function() { +< var safe = new Handlebars.SafeString('testing 1, 2, 3'); +< if (!(safe instanceof Handlebars.SafeString)) { +< throw new Error('Must be instance of SafeString'); +< } +< equals( +< safe.toString(), +< 'testing 1, 2, 3', +< 'SafeString is equivalent to its underlying string' +< ); +< }); +< +< it('it should not escape SafeString properties', function() { +< var name = new Handlebars.SafeString('Sean O'Malley'); +< +< expectTemplate('{{name}}') +< .withInput({ name: name }) +< .toCompileTo('Sean O'Malley'); +< }); +< }); +< +< describe('#escapeExpression', function() { +< it('shouhld escape html', function() { +< equals( +< Handlebars.Utils.escapeExpression('foo<&"\'>'), +< 'foo<&"'>' +< ); +< equals(Handlebars.Utils.escapeExpression('foo='), 'foo='); +< }); +< it('should not escape SafeString', function() { +< var string = new Handlebars.SafeString('foo<&"\'>'); +< equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>'); +< +< var obj = { +< toHTML: function() { +< return 'foo<&"\'>'; +< } +< }; +< equals(Handlebars.Utils.escapeExpression(obj), 'foo<&"\'>'); +< }); +< it('should handle falsy', function() { +< equals(Handlebars.Utils.escapeExpression(''), ''); +< equals(Handlebars.Utils.escapeExpression(undefined), ''); +< equals(Handlebars.Utils.escapeExpression(null), ''); +< +< equals(Handlebars.Utils.escapeExpression(false), 'false'); +< equals(Handlebars.Utils.escapeExpression(0), '0'); +< }); +< it('should handle empty objects', function() { +< equals(Handlebars.Utils.escapeExpression({}), {}.toString()); +< equals(Handlebars.Utils.escapeExpression([]), [].toString()); +< }); +< }); +< +< describe('#isEmpty', function() { +< it('should not be empty', function() { +< equals(Handlebars.Utils.isEmpty(undefined), true); +< equals(Handlebars.Utils.isEmpty(null), true); +< equals(Handlebars.Utils.isEmpty(false), true); +< equals(Handlebars.Utils.isEmpty(''), true); +< equals(Handlebars.Utils.isEmpty([]), true); +< }); +< +< it('should be empty', function() { +< equals(Handlebars.Utils.isEmpty(0), false); +< equals(Handlebars.Utils.isEmpty([1]), false); +< equals(Handlebars.Utils.isEmpty('foo'), false); +< equals(Handlebars.Utils.isEmpty({ bar: 1 }), false); +< }); +< }); +< +< describe('#extend', function() { +< it('should ignore prototype values', function() { +< function A() { +< this.a = 1; +< } +< A.prototype.b = 4; +< +< var b = { b: 2 }; +< +< Handlebars.Utils.extend(b, new A()); +< +< equals(b.a, 1); +< equals(b.b, 2); +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import Handlebars from '..'; +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('utils', function () { +> describe('#SafeString', function () { +> it('constructing a safestring from a string and checking its type', function () { +> const safe = new Handlebars.SafeString('testing 1, 2, 3'); +> expect(safe).toBeInstanceOf(Handlebars.SafeString); +> expect(safe.toString()).toEqual('testing 1, 2, 3'); +> }); +> +> it('it should not escape SafeString properties', function () { +> const name = new Handlebars.SafeString('Sean O'Malley'); +> expectTemplate('{{name}}').withInput({ name }).toCompileTo('Sean O'Malley'); diff --git a/packages/kbn-handlebars/.patches/whitespace-control.patch b/packages/kbn-handlebars/.patches/whitespace-control.patch new file mode 100644 index 0000000000000..f9e32bc2260ca --- /dev/null +++ b/packages/kbn-handlebars/.patches/whitespace-control.patch @@ -0,0 +1,187 @@ +1,24c1,17 +< describe('whitespace control', function() { +< it('should strip whitespace around mustache calls', function() { +< var hash = { foo: 'bar<' }; +< +< expectTemplate(' {{~foo~}} ') +< .withInput(hash) +< .toCompileTo('bar<'); +< +< expectTemplate(' {{~foo}} ') +< .withInput(hash) +< .toCompileTo('bar< '); +< +< expectTemplate(' {{foo~}} ') +< .withInput(hash) +< .toCompileTo(' bar<'); +< +< expectTemplate(' {{~&foo~}} ') +< .withInput(hash) +< .toCompileTo('bar<'); +< +< expectTemplate(' {{~{foo}~}} ') +< .withInput(hash) +< .toCompileTo('bar<'); +< +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +> +> import { expectTemplate } from '../__jest__/test_bench'; +> +> describe('whitespace control', () => { +> it('should strip whitespace around mustache calls', () => { +> const hash = { foo: 'bar<' }; +> expectTemplate(' {{~foo~}} ').withInput(hash).toCompileTo('bar<'); +> expectTemplate(' {{~foo}} ').withInput(hash).toCompileTo('bar< '); +> expectTemplate(' {{foo~}} ').withInput(hash).toCompileTo(' bar<'); +> expectTemplate(' {{~&foo~}} ').withInput(hash).toCompileTo('bar<'); +> expectTemplate(' {{~{foo}~}} ').withInput(hash).toCompileTo('bar<'); +28,46c21,28 +< describe('blocks', function() { +< it('should strip whitespace around simple block calls', function() { +< var hash = { foo: 'bar<' }; +< +< expectTemplate(' {{~#if foo~}} bar {{~/if~}} ') +< .withInput(hash) +< .toCompileTo('bar'); +< +< expectTemplate(' {{#if foo~}} bar {{/if~}} ') +< .withInput(hash) +< .toCompileTo(' bar '); +< +< expectTemplate(' {{~#if foo}} bar {{~/if}} ') +< .withInput(hash) +< .toCompileTo(' bar '); +< +< expectTemplate(' {{#if foo}} bar {{/if}} ') +< .withInput(hash) +< .toCompileTo(' bar '); +--- +> describe('blocks', () => { +> it('should strip whitespace around simple block calls', () => { +> const hash = { foo: 'bar<' }; +> +> expectTemplate(' {{~#if foo~}} bar {{~/if~}} ').withInput(hash).toCompileTo('bar'); +> expectTemplate(' {{#if foo~}} bar {{/if~}} ').withInput(hash).toCompileTo(' bar '); +> expectTemplate(' {{~#if foo}} bar {{~/if}} ').withInput(hash).toCompileTo(' bar '); +> expectTemplate(' {{#if foo}} bar {{/if}} ').withInput(hash).toCompileTo(' bar '); +57c39 +< it('should strip whitespace around inverse block calls', function() { +--- +> it('should strip whitespace around inverse block calls', () => { +59d40 +< +61d41 +< +63d42 +< +65,68c44 +< +< expectTemplate( +< ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ' +< ).toCompileTo('bar'); +--- +> expectTemplate(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ').toCompileTo('bar'); +71,80c47,48 +< it('should strip whitespace around complex block calls', function() { +< var hash = { foo: 'bar<' }; +< +< expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}') +< .withInput(hash) +< .toCompileTo('bar'); +< +< expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}') +< .withInput(hash) +< .toCompileTo('bar '); +--- +> it('should strip whitespace around complex block calls', () => { +> const hash = { foo: 'bar<' }; +82,84c50,54 +< expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}') +< .withInput(hash) +< .toCompileTo(' bar'); +--- +> expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); +> expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo('bar '); +> expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo(' bar'); +> expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo(' bar '); +> expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); +86,90c56 +< expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}') +< .withInput(hash) +< .toCompileTo(' bar '); +< +< expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}') +--- +> expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') +94,102c60 +< expectTemplate( +< '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' +< ) +< .withInput(hash) +< .toCompileTo('bar'); +< +< expectTemplate( +< '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' +< ) +--- +> expectTemplate('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') +106,109c64 +< expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo( +< 'baz' +< ); +< +--- +> expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo('baz'); +111,120c66,69 +< +< expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo( +< ' baz' +< ); +< +< expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo( +< ' baz ' +< ); +< +< expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo( +--- +> expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo(' baz'); +> expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo(' baz '); +> expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo('baz'); +> expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n').toCompileTo( +123,126d71 +< +< expectTemplate( +< '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' +< ).toCompileTo('baz'); +130,152c75 +< it('should strip whitespace around partials', function() { +< expectTemplate('foo {{~> dude~}} ') +< .withPartials({ dude: 'bar' }) +< .toCompileTo('foobar'); +< +< expectTemplate('foo {{> dude~}} ') +< .withPartials({ dude: 'bar' }) +< .toCompileTo('foo bar'); +< +< expectTemplate('foo {{> dude}} ') +< .withPartials({ dude: 'bar' }) +< .toCompileTo('foo bar '); +< +< expectTemplate('foo\n {{~> dude}} ') +< .withPartials({ dude: 'bar' }) +< .toCompileTo('foobar'); +< +< expectTemplate('foo\n {{> dude}} ') +< .withPartials({ dude: 'bar' }) +< .toCompileTo('foo\n bar'); +< }); +< +< it('should only strip whitespace once', function() { +--- +> it('should only strip whitespace once', () => { diff --git a/packages/kbn-handlebars/BUILD.bazel b/packages/kbn-handlebars/BUILD.bazel new file mode 100644 index 0000000000000..362c153fb90af --- /dev/null +++ b/packages/kbn-handlebars/BUILD.bazel @@ -0,0 +1,115 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_BASE_NAME = "kbn-handlebars" +PKG_REQUIRE_NAME = "@kbn/handlebars" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__handlebars" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + "**/__jest__/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +RUNTIME_DEPS = [ + "@npm//handlebars", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/node", + "@npm//handlebars", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-handlebars/LICENSE b/packages/kbn-handlebars/LICENSE new file mode 100644 index 0000000000000..55b4f257a1e98 --- /dev/null +++ b/packages/kbn-handlebars/LICENSE @@ -0,0 +1,29 @@ +The MIT License (MIT) + +Copyright (c) Elasticsearch BV +Copyright (c) Copyright (C) 2011-2019 by Yehuda Katz + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at the following locations: + - https://github.com/handlebars-lang/handlebars.js + - https://github.com/elastic/kibana/tree/main/packages/kbn-handlebars + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/kbn-handlebars/README.md b/packages/kbn-handlebars/README.md new file mode 100644 index 0000000000000..e9a6a8c839fd9 --- /dev/null +++ b/packages/kbn-handlebars/README.md @@ -0,0 +1,192 @@ +# @kbn/handlebars + +A custom version of the handlebars package which, to improve security, does not use `eval` or `new Function`. This means that templates can't be compiled into JavaScript functions in advance and hence, rendering the templates is a lot slower. + +## Limitations + +- Only the following compile options are supported: + - `knownHelpers` + - `knownHelpersOnly` + - `strict` + - `assumeObjects` + - `noEscape` + - `data` + +- Only the following runtime options are supported: + - `helpers` + - `blockParams` + - `data` + +The [Inline partials](https://handlebarsjs.com/guide/partials.html#inline-partials) handlebars template feature is currently not supported by `@kbn/handlebars`. + +## Implementation differences + +The standard `handlebars` implementation: + +1. When given a template string, e.g. `Hello {{x}}`, return a "render" function which takes an "input" object, e.g. `{ x: 'World' }`. +1. The first time the "render" function is called the following happens: + 1. Turn the template string into an Abstract Syntax Tree (AST). + 1. Convert the AST into a hyper optimized JavaScript function which takes the input object as an argument. + 1. Call the generate JavaScript function with the given "input" object to produce and return the final output string (`Hello World`). +1. Subsequent calls to the "render" function will re-use the already generated JavaScript function. + +The custom `@kbn/handlebars` implementation: + +1. When given a template string, e.g. `Hello {{x}}`, return a "render" function which takes an "input" object, e.g. `{ x: 'World' }`. +1. The first time the "render" function is called the following happens: + 1. Turn the template string into an Abstract Syntax Tree (AST). + 1. Process the AST with the given "input" object to produce and return the final output string (`Hello World`). +1. Subsequent calls to the "render" function will re-use the already generated AST. + +_Note: Not parsing of the template string until the first call to the "render" function is deliberate as it mimics the original `handlebars` implementation. This means that any errors that occur due to an invalid template string will not be thrown until the first call to the "render" function._ + +## Technical details + +The `handlebars` library exposes the API for both [generating the AST](https://github.com/handlebars-lang/handlebars.js/blob/master/docs/compiler-api.md#ast) and walking it by implementing the [Visitor API](https://github.com/handlebars-lang/handlebars.js/blob/master/docs/compiler-api.md#ast-visitor). We can leverage that to our advantage and create our own "render" function, which internally calls this API to generate the AST and then the API to walk the AST. + +The `@kbn/handlebars` implementation of the `Visitor` class implements all the necessary methods called by the parent `Visitor` code when instructed to walk the AST. They all start with an upppercase letter, e.g. `MustacheStatement` or `SubExpression`. We call this class `ElasticHandlebarsVisitor`. + +To parse the template string to an AST representation, we call `Handlebars.parse(templateString)`, which returns an AST object. + +The AST object contains a bunch of nodes, one for each element of the template string, all arranged in a tree-like structure. The root of the AST object is a node of type `Program`. This is a special node, which we do not need to worry about, but each of its direct children has a type named like the method which will be called when the walking algorithm reaches that node, e.g. `ContentStatement` or `BlockStatement`. These are the methods that our `Visitor` implementation implements. + +To instruct our `ElasticHandlebarsVisitor` class to start walking the AST object, we call the `accept()` method inherited from the parent `Visitor` class with the main AST object. The `Visitor` will walk each node in turn that is directly attached to the root `Program` node. For each node it traverses, it will call the matching method in our `ElasticHandlebarsVisitor` class. + +To instruct the `Visitor` code to traverse any child nodes of a given node, our implementation needs to manually call `accept(childNode)`, `acceptArray(arrayOfChildNodes)`, `acceptKey(node, childKeyName)`, or `acceptRequired(node, childKeyName)` from within any of the "node" methods, otherwise the child nodes are ignored. + +### State + +We keep state internally in the `ElasticHandlebarsVisitor` object using the following private properties: + +- `scopes`: An array (stack) of `context` objects. In a simple template this array will always only contain a single element: The main `context` object. In more complicated scenarios, new `context` objects will be pushed and popped to and from the `scopes` stack as needed. +- `output`: An array containing the "rendered" output of each node (normally just one element per node). In the most simple template, this is simply joined together into a the final output string after the AST has been traversed. In more complicated templates, we use this array temporarily to collect parameters to give to helper functions (see the `getParams` function). + +## Development + +Some of the tests have been copied from the upstream `handlebars` project and modified to fit our use-case, test-suite, and coding conventions. They are all located under the `packages/kbn-handlebars/src/upstream` directory. To check if any of the copied files have received updates upstream that we might want to include in our copies, you can run the following script: + +```sh +./packages/kbn-handlebars/scripts/check_for_test_changes.sh +``` + +If the script outputs a diff for a given file, it means that this file has been updated. + +Once all updates have been manually merged with our versions of the files, run the following script to "lock" us into the new updates: + +```sh +./packages/kbn-handlebars/scripts/update_test_patches.sh +``` + +This will update the `.patch` files inside the `packages/kbn-handlebars/.patches` directory. Make sure to commit those changes. + +_Note: If we manually make changes to our test files in the `upstream` directory, we need to run the `update_test_patches.sh` script as well._ + +## Debugging + +### Print AST + +To output the generated AST object structure in a somewhat readable form, use the following script: + +```sh +./packages/kbn-handlebars/scripts/print_ast.js +``` + +Example: + +```sh +./packages/kbn-handlebars/scripts/print_ast.js '{{value}}' +``` + +Output: + +```js +{ + type: 'Program', + body: [ + { + type: 'MustacheStatement', + path: { + type: 'PathExpression', + data: false, + depth: 0, + parts: [ 'value' ], + original: 'value' + }, + params: [], + hash: undefined, + escaped: true, + strip: { open: false, close: false } + } + ], + strip: {} +} +``` + +You can also filter which properties not to display, e.g: + +```sh +./packages/kbn-handlebars/scripts/print_ast.js '{{#myBlock}}Hello {{name}}{{/myBlock}}' params,hash,loc,strip,data,depth,parts,inverse,openStrip,inverseStrip,closeStrip,blockParams,escaped +``` + +Output: + +```js +{ + type: 'Program', + body: [ + { + type: 'BlockStatement', + path: { type: 'PathExpression', original: 'myBlock' }, + program: { + type: 'Program', + body: [ + { + type: 'ContentStatement', + original: 'Hello ', + value: 'Hello ' + }, + { + type: 'MustacheStatement', + path: { type: 'PathExpression', original: 'name' } + } + ] + } + } + ] +} +``` + +### Environment variables + +By default each test will run both the original `handlebars` code and the modified `@kbn/handlebars` code to compare if the output of the two are identical. When debugging, it can be beneficial to isolate a test run to just one or the other. To control this, you can use the following environment variables: + +- `EVAL=1` - Set to only run the original `handlebars` implementation that uses `eval`. +- `AST=1` - Set to only run the modified `@kbn/handlebars` implementation that doesn't use `eval`. + +### Print generated code + +It's possible to see the generated JavaScript code that `handlebars` create for a given template using the following command line tool: + +```sh +./node_modules/handlebars/print-script