diff --git a/.eslintignore b/.eslintignore index 972c818d791bf..93c69b4f9b207 100644 --- a/.eslintignore +++ b/.eslintignore @@ -39,7 +39,7 @@ target /x-pack/legacy/plugins/maps/public/vendor/** # package overrides -/packages/eslint-config-kibana +/packages/elastic-eslint-config-kibana /packages/kbn-interpreter/src/common/lib/grammar.js /packages/kbn-plugin-generator/template /packages/kbn-pm/dist diff --git a/.eslintrc.js b/.eslintrc.js index ff4ac180c3774..9b75c36c95abd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -94,12 +94,6 @@ module.exports = { 'jsx-a11y/no-onchange': 'off', }, }, - { - files: ['src/plugins/es_ui_shared/**/*.{js,mjs,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['src/plugins/kibana_react/**/*.{js,mjs,ts,tsx}'], rules: { @@ -125,25 +119,12 @@ module.exports = { 'jsx-a11y/click-events-have-key-events': 'off', }, }, - { - files: ['x-pack/legacy/plugins/index_management/**/*.{js,mjs,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - 'react-hooks/rules-of-hooks': 'off', - }, - }, { files: ['x-pack/plugins/ml/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['x-pack/legacy/plugins/snapshot_restore/**/*.{js,mjs,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, /** * Files that require Apache 2.0 headers, settings diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 66fb31cc91d5a..1d0f6fc50ee9b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -85,7 +85,6 @@ /x-pack/plugins/ingest_manager/ @elastic/ingest-management /x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management /x-pack/plugins/observability/ @elastic/observability-ui -/x-pack/legacy/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/uptime @elastic/uptime diff --git a/.github/ISSUE_TEMPLATE/APM.md b/.github/ISSUE_TEMPLATE/APM.md new file mode 100644 index 0000000000000..983806f70bc3f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/APM.md @@ -0,0 +1,11 @@ +--- +name: APM Issue +about: Issues related to the APM solution in Kibana +labels: Team:apm +title: [APM] +--- + +**Versions** +Kibana: (if relevant) +APM Server: (if relevant) +Elasticsearch: (if relevant) diff --git a/docs/canvas/canvas-tinymath-functions.asciidoc b/docs/canvas/canvas-tinymath-functions.asciidoc index 73808fc6625d1..f92f7c642a2ee 100644 --- a/docs/canvas/canvas-tinymath-functions.asciidoc +++ b/docs/canvas/canvas-tinymath-functions.asciidoc @@ -492,37 +492,6 @@ find the mean by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The maximum value of all numbers if -`args` contains only numbers. Returns an array with the the maximum values at each -index, including all scalar numbers in `args` in the calculation at each index if -`args` contains at least one array. - -*Throws*: `'Array length mismatch'` if `args` contains arrays of different lengths - -*Example* -[source, js] ------------- -max(1, 2, 3) // returns 3 -max([10, 20, 30, 40], 15) // returns [15, 20, 30, 40] -max([1, 9], 4, [3, 5]) // returns [max([1, 4, 3]), max([9, 4, 5])] = [4, 9] ------------- - -[float] -=== mean( ...args ) - -Finds the mean value of one of more numbers/arrays of numbers passed into the function. -If at least one array of numbers is passed into the function, the function will -find the mean by index. - -[cols="3*^<"] -|=== -|Param |Type |Description - -|...args -|number \| Array. -|one or more numbers or arrays of numbers -|=== - *Returns*: `number` | `Array.`. The mean value of all numbers if `args` contains only numbers. Returns an array with the the mean values of each index, including all scalar numbers in `args` in the calculation at each index if `args` diff --git a/docs/developer/contributing/development-github.asciidoc b/docs/developer/contributing/development-github.asciidoc index a6d4e29940487..84f51843098a7 100644 --- a/docs/developer/contributing/development-github.asciidoc +++ b/docs/developer/contributing/development-github.asciidoc @@ -1,5 +1,5 @@ [[development-github]] -== How we use git and github +== How we use Git and GitHub [discrete] === Forking @@ -12,17 +12,21 @@ repo, which we'll refer to in later code snippets. [discrete] === Branching -* All work on the next major release goes into master. -* Past major release branches are named `{majorVersion}.x`. They contain -work that will go into the next minor release. For example, if the next -minor release is `5.2.0`, work for it should go into the `5.x` branch. -* Past minor release branches are named `{majorVersion}.{minorVersion}`. -They contain work that will go into the next patch release. For example, -if the next patch release is `5.3.1`, work for it should go into the -`5.3` branch. -* All work is done on feature branches and merged into one of these -branches. -* Where appropriate, we'll backport changes into older release branches. +At Elastic, all products in the stack, including Kibana, are released at the same time with the same version number. Most of these projects have the following branching strategy: + +* `master` is the next major version. +* `.x` is the next minor version. +* `.` is the next release of a minor version, including patch releases. + +As an example, let's assume that the `7.x` branch is currently a not-yet-released `7.6.0`. Once `7.6.0` has reached feature freeze, it will be branched to `7.6` and `7.x` will be updated to reflect `7.7.0`. The release of `7.6.0` and subsequent patch releases will be cut from the `7.6` branch. At any time, you can verify the current version of a branch by inspecting the `version` attribute in the `package.json` file within the Kibana source. + +Pull requests are made into the `master` branch and then backported when it is safe and appropriate. + +* Breaking changes do not get backported and only go into `master`. +* All non-breaking changes can be backported to the `.x` branch. +* Features should not be backported to a `.` branch. +* Bugs can be backported to a `.` branch if the changes are safe and appropriate. Safety is a judgment call you make based on factors like the bug's severity, test coverage, confidence in the changes, etc. Your reasoning should be included in the pull request description. +* Documentation changes can be backported to any branch at any time. [discrete] === Commits and Merging @@ -109,4 +113,4 @@ Assuming you've successfully rebased and you're happy with the code, you should [discrete] === Creating a pull request -See <> for the next steps on getting your code changes merged into {kib}. \ No newline at end of file +See <> for the next steps on getting your code changes merged into {kib}. diff --git a/docs/developer/getting-started/running-kibana-advanced.asciidoc b/docs/developer/getting-started/running-kibana-advanced.asciidoc index 44897184f88f2..277e52a3dc8e9 100644 --- a/docs/developer/getting-started/running-kibana-advanced.asciidoc +++ b/docs/developer/getting-started/running-kibana-advanced.asciidoc @@ -48,7 +48,7 @@ If you’re installing dependencies and seeing an error that looks something like .... -Unsupported URL Type: link:packages/eslint-config-kibana +Unsupported URL Type: link:packages/elastic-eslint-config-kibana .... you’re likely running `npm`. To install dependencies in {kib} you diff --git a/docs/development/core/public/kibana-plugin-core-public.appmountparameters.md b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.md index de79fc8281c45..f6c57603bedde 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appmountparameters.md +++ b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.md @@ -19,4 +19,5 @@ export interface AppMountParameters | [element](./kibana-plugin-core-public.appmountparameters.element.md) | HTMLElement | The container element to render the application into. | | [history](./kibana-plugin-core-public.appmountparameters.history.md) | ScopedHistory<HistoryLocationState> | A scoped history instance for your application. Should be used to wire up your applications Router. | | [onAppLeave](./kibana-plugin-core-public.appmountparameters.onappleave.md) | (handler: AppLeaveHandler) => void | A function that can be used to register a handler that will be called when the user is leaving the current application, allowing to prompt a confirmation message before actually changing the page.This will be called either when the user goes to another application, or when trying to close the tab or manually changing the url. | +| [setHeaderActionMenu](./kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md) | (menuMount: MountPoint | undefined) => void | A function that can be used to set the mount point used to populate the application action container in the chrome header.Calling the handler multiple time will erase the current content of the action menu with the mount from the latest call. Calling the handler with undefined will unmount the current mount point. Calling the handler after the application has been unmounted will have no effect. | diff --git a/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md new file mode 100644 index 0000000000000..ca9cee64bb1f9 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md @@ -0,0 +1,39 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) > [setHeaderActionMenu](./kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md) + +## AppMountParameters.setHeaderActionMenu property + +A function that can be used to set the mount point used to populate the application action container in the chrome header. + +Calling the handler multiple time will erase the current content of the action menu with the mount from the latest call. Calling the handler with `undefined` will unmount the current mount point. Calling the handler after the application has been unmounted will have no effect. + +Signature: + +```typescript +setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; +``` + +## Example + + +```ts +// application.tsx +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter, Route } from 'react-router-dom'; + +import { CoreStart, AppMountParameters } from 'src/core/public'; +import { MyPluginDepsStart } from './plugin'; + +export renderApp = ({ element, history, setHeaderActionMenu }: AppMountParameters) => { + const { renderApp } = await import('./application'); + const { renderActionMenu } = await import('./action_menu'); + setHeaderActionMenu((element) => { + return renderActionMenu(element); + }) + return renderApp({ element, history }); +} + +``` + diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md index 900f8e333f337..2c20fe2dab00f 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md @@ -7,5 +7,5 @@ Signature: ```typescript -filter?: string; +filter?: string | KueryNode; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md index ebd0a99531755..903462ac3039d 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md @@ -17,7 +17,7 @@ export interface SavedObjectsFindOptions | --- | --- | --- | | [defaultSearchOperator](./kibana-plugin-core-public.savedobjectsfindoptions.defaultsearchoperator.md) | 'AND' | 'OR' | | | [fields](./kibana-plugin-core-public.savedobjectsfindoptions.fields.md) | string[] | An array of fields to include in the results | -| [filter](./kibana-plugin-core-public.savedobjectsfindoptions.filter.md) | string | | +| [filter](./kibana-plugin-core-public.savedobjectsfindoptions.filter.md) | string | KueryNode | | | [hasReference](./kibana-plugin-core-public.savedobjectsfindoptions.hasreference.md) | {
type: string;
id: string;
} | | | [namespaces](./kibana-plugin-core-public.savedobjectsfindoptions.namespaces.md) | string[] | | | [page](./kibana-plugin-core-public.savedobjectsfindoptions.page.md) | number | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md index ae7b7a28bcd09..c98a4fe5e8796 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md @@ -7,5 +7,5 @@ Signature: ```typescript -filter?: string; +filter?: string | KueryNode; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md index 15a9d99b3d062..804c83f7c1b48 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md @@ -17,7 +17,7 @@ export interface SavedObjectsFindOptions | --- | --- | --- | | [defaultSearchOperator](./kibana-plugin-core-server.savedobjectsfindoptions.defaultsearchoperator.md) | 'AND' | 'OR' | | | [fields](./kibana-plugin-core-server.savedobjectsfindoptions.fields.md) | string[] | An array of fields to include in the results | -| [filter](./kibana-plugin-core-server.savedobjectsfindoptions.filter.md) | string | | +| [filter](./kibana-plugin-core-server.savedobjectsfindoptions.filter.md) | string | KueryNode | | | [hasReference](./kibana-plugin-core-server.savedobjectsfindoptions.hasreference.md) | {
type: string;
id: string;
} | | | [namespaces](./kibana-plugin-core-server.savedobjectsfindoptions.namespaces.md) | string[] | | | [page](./kibana-plugin-core-server.savedobjectsfindoptions.page.md) | number | | diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.dependencies_.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.dependencies_.md new file mode 100644 index 0000000000000..7475f0e3a4c1c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.dependencies_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) > [dependencies$](./kibana-plugin-core-server.statusservicesetup.dependencies_.md) + +## StatusServiceSetup.dependencies$ property + +Current status for all plugins this plugin depends on. Each key of the `Record` is a plugin id. + +Signature: + +```typescript +dependencies$: Observable>; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.derivedstatus_.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.derivedstatus_.md new file mode 100644 index 0000000000000..6c65e44270a06 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.derivedstatus_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) > [derivedStatus$](./kibana-plugin-core-server.statusservicesetup.derivedstatus_.md) + +## StatusServiceSetup.derivedStatus$ property + +The status of this plugin as derived from its dependencies. + +Signature: + +```typescript +derivedStatus$: Observable; +``` + +## Remarks + +By default, plugins inherit this derived status from their dependencies. Calling overrides this default status. + +This may emit multliple times for a single status change event as propagates through the dependency tree + diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md index 3d3b73ccda25f..ba0645be4d26c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md @@ -12,10 +12,73 @@ API for accessing status of Core and this plugin's dependencies as well as for c export interface StatusServiceSetup ``` +## Remarks + +By default, a plugin inherits it's current status from the most severe status level of any Core services and any plugins that it depends on. This default status is available on the API. + +Plugins may customize their status calculation by calling the API with an Observable. Within this Observable, a plugin may choose to only depend on the status of some of its dependencies, to ignore severe status levels of particular Core services they are not concerned with, or to make its status dependent on other external services. + +## Example 1 + +Customize a plugin's status to only depend on the status of SavedObjects: + +```ts +core.status.set( + core.status.core$.pipe( +. map((coreStatus) => { + return coreStatus.savedObjects; + }) ; + ); +); + +``` + +## Example 2 + +Customize a plugin's status to include an external service: + +```ts +const externalStatus$ = interval(1000).pipe( + switchMap(async () => { + const resp = await fetch(`https://myexternaldep.com/_healthz`); + const body = await resp.json(); + if (body.ok) { + return of({ level: ServiceStatusLevels.available, summary: 'External Service is up'}); + } else { + return of({ level: ServiceStatusLevels.available, summary: 'External Service is unavailable'}); + } + }), + catchError((error) => { + of({ level: ServiceStatusLevels.unavailable, summary: `External Service is down`, meta: { error }}) + }) +); + +core.status.set( + combineLatest([core.status.derivedStatus$, externalStatus$]).pipe( + map(([derivedStatus, externalStatus]) => { + if (externalStatus.level > derivedStatus) { + return externalStatus; + } else { + return derivedStatus; + } + }) + ) +); + +``` + ## Properties | Property | Type | Description | | --- | --- | --- | | [core$](./kibana-plugin-core-server.statusservicesetup.core_.md) | Observable<CoreStatus> | Current status for all Core services. | +| [dependencies$](./kibana-plugin-core-server.statusservicesetup.dependencies_.md) | Observable<Record<string, ServiceStatus>> | Current status for all plugins this plugin depends on. Each key of the Record is a plugin id. | +| [derivedStatus$](./kibana-plugin-core-server.statusservicesetup.derivedstatus_.md) | Observable<ServiceStatus> | The status of this plugin as derived from its dependencies. | | [overall$](./kibana-plugin-core-server.statusservicesetup.overall_.md) | Observable<ServiceStatus> | Overall system status for all of Kibana. | +## Methods + +| Method | Description | +| --- | --- | +| [set(status$)](./kibana-plugin-core-server.statusservicesetup.set.md) | Allows a plugin to specify a custom status dependent on its own criteria. Completely overrides the default inherited status. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.set.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.set.md new file mode 100644 index 0000000000000..143cd397c40ae --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.set.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) > [set](./kibana-plugin-core-server.statusservicesetup.set.md) + +## StatusServiceSetup.set() method + +Allows a plugin to specify a custom status dependent on its own criteria. Completely overrides the default inherited status. + +Signature: + +```typescript +set(status$: Observable): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| status$ | Observable<ServiceStatus> | | + +Returns: + +`void` + +## Remarks + +See the [StatusServiceSetup.derivedStatus$](./kibana-plugin-core-server.statusservicesetup.derivedstatus_.md) API for leveraging the default status calculation that is provided by Core. + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md index ce401bec87dbb..595992dc82b74 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md @@ -7,15 +7,8 @@ Signature: ```typescript -init(forceFieldRefresh?: boolean): Promise; +init(): Promise; ``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| forceFieldRefresh | boolean | | - Returns: `Promise` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index c15cb3358f689..37db063e284ec 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -50,7 +50,7 @@ export declare class IndexPattern implements IIndexPattern | [getScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md) | | | | [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | | | [getTimeField()](./kibana-plugin-plugins-data-public.indexpattern.gettimefield.md) | | | -| [init(forceFieldRefresh)](./kibana-plugin-plugins-data-public.indexpattern.init.md) | | | +| [init()](./kibana-plugin-plugins-data-public.indexpattern.init.md) | | | | [initFromSpec(spec)](./kibana-plugin-plugins-data-public.indexpattern.initfromspec.md) | | | | [isTimeBased()](./kibana-plugin-plugins-data-public.indexpattern.istimebased.md) | | | | [isTimeBasedWildcard()](./kibana-plugin-plugins-data-public.indexpattern.istimebasedwildcard.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index e139b326b7500..9f3ed8c1263ba 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/package.json b/package.json index b8cf2e1e27774..28f2025300f39 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "test:ftr:server": "node scripts/functional_tests_server", "test:ftr:runner": "node scripts/functional_test_runner", "test:coverage": "grunt test:coverage", - "typespec": "typings-tester --config x-pack/plugins/canvas/public/lib/aeroelastic/tsconfig.json x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts", "checkLicenses": "node scripts/check_licenses --dev", "build": "node scripts/build --all-platforms", "start": "node scripts/kibana --dev", @@ -117,7 +116,10 @@ "**/@types/*/**", "**/grunt-*", "**/grunt-*/**", - "x-pack/typescript" + "x-pack/typescript", + "@elastic/eui/rehype-react", + "@elastic/eui/remark-rehype", + "@elastic/eui/remark-rehype/**" ] }, "dependencies": { @@ -125,7 +127,7 @@ "@babel/register": "^7.10.5", "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "7.9.0-rc.2", - "@elastic/eui": "27.4.1", + "@elastic/eui": "28.2.0", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "^2.5.0", "@elastic/request-crypto": "1.1.4", @@ -142,6 +144,7 @@ "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", + "@types/yauzl": "^2.9.1", "JSONStream": "1.3.5", "abortcontroller-polyfill": "^1.4.0", "accept": "3.0.2", @@ -227,7 +230,7 @@ "@babel/parser": "^7.11.2", "@babel/types": "^7.11.0", "@elastic/apm-rum": "^5.5.0", - "@elastic/charts": "19.8.1", + "@elastic/charts": "21.0.1", "@elastic/ems-client": "7.9.3", "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.2", @@ -470,7 +473,6 @@ "topojson-client": "3.0.0", "tree-kill": "^1.2.2", "typescript": "4.0.2", - "typings-tester": "^0.3.2", "ui-select": "0.19.8", "vega": "^5.13.0", "vega-lite": "^4.13.1", diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js similarity index 100% rename from packages/eslint-config-kibana/.eslintrc.js rename to packages/elastic-eslint-config-kibana/.eslintrc.js diff --git a/packages/eslint-config-kibana/.gitignore b/packages/elastic-eslint-config-kibana/.gitignore similarity index 100% rename from packages/eslint-config-kibana/.gitignore rename to packages/elastic-eslint-config-kibana/.gitignore diff --git a/packages/eslint-config-kibana/.npmignore b/packages/elastic-eslint-config-kibana/.npmignore similarity index 100% rename from packages/eslint-config-kibana/.npmignore rename to packages/elastic-eslint-config-kibana/.npmignore diff --git a/packages/eslint-config-kibana/README.md b/packages/elastic-eslint-config-kibana/README.md similarity index 94% rename from packages/eslint-config-kibana/README.md rename to packages/elastic-eslint-config-kibana/README.md index 68c1639b834a5..2049440cd8ff7 100644 --- a/packages/eslint-config-kibana/README.md +++ b/packages/elastic-eslint-config-kibana/README.md @@ -1,4 +1,4 @@ -# eslint-config-kibana +# elastic-eslint-config-kibana The eslint config used by the kibana team diff --git a/packages/eslint-config-kibana/javascript.js b/packages/elastic-eslint-config-kibana/javascript.js similarity index 100% rename from packages/eslint-config-kibana/javascript.js rename to packages/elastic-eslint-config-kibana/javascript.js diff --git a/packages/eslint-config-kibana/jest.js b/packages/elastic-eslint-config-kibana/jest.js similarity index 100% rename from packages/eslint-config-kibana/jest.js rename to packages/elastic-eslint-config-kibana/jest.js diff --git a/packages/eslint-config-kibana/package.json b/packages/elastic-eslint-config-kibana/package.json similarity index 89% rename from packages/eslint-config-kibana/package.json rename to packages/elastic-eslint-config-kibana/package.json index 4ec3bcdfd7c05..a4bb8d5449ee8 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/elastic-eslint-config-kibana/package.json @@ -5,15 +5,15 @@ "main": ".eslintrc.js", "repository": { "type": "git", - "url": "git+https://github.com/elastic/eslint-config-kibana.git" + "url": "git+https://github.com/elastic/kibana.git" }, "keywords": [], "author": "Spencer Alger ", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/elastic/kibana/tree/master/packages/eslint-config-kibana" + "url": "https://github.com/elastic/kibana/tree/master/packages/elastic-eslint-config-kibana" }, - "homepage": "https://github.com/elastic/kibana/tree/master/packages/eslint-config-kibana", + "homepage": "https://github.com/elastic/kibana/tree/master/packages/elastic-eslint-config-kibana", "peerDependencies": { "@typescript-eslint/eslint-plugin": "^3.10.0", "@typescript-eslint/parser": "^3.10.0", diff --git a/packages/eslint-config-kibana/react.js b/packages/elastic-eslint-config-kibana/react.js similarity index 100% rename from packages/eslint-config-kibana/react.js rename to packages/elastic-eslint-config-kibana/react.js diff --git a/packages/eslint-config-kibana/restricted_globals.js b/packages/elastic-eslint-config-kibana/restricted_globals.js similarity index 100% rename from packages/eslint-config-kibana/restricted_globals.js rename to packages/elastic-eslint-config-kibana/restricted_globals.js diff --git a/packages/eslint-config-kibana/typescript.js b/packages/elastic-eslint-config-kibana/typescript.js similarity index 100% rename from packages/eslint-config-kibana/typescript.js rename to packages/elastic-eslint-config-kibana/typescript.js diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index c3670f648d309..dabf11fdd0b66 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -16,7 +16,7 @@ "glob": "^7.1.2", "node-fetch": "^2.6.0", "simple-git": "^1.91.0", - "tar-fs": "^1.16.3", + "tar-fs": "^2.1.0", "tree-kill": "^1.2.2", "yauzl": "^2.10.0" } diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index 129c58a4b4174..f292387c12521 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -23,10 +23,10 @@ "vinyl-fs": "^3.0.3" }, "devDependencies": { - "@types/decompress": "^4.2.3", + "@types/extract-zip": "^1.6.2", "@types/gulp-zip": "^4.0.1", "@types/inquirer": "^6.5.0", - "decompress": "^4.2.1", + "extract-zip": "^2.0.1", "typescript": "4.0.2" } } diff --git a/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts b/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts index 62f83cd672f3d..be23d8dbde646 100644 --- a/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts +++ b/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts @@ -22,7 +22,7 @@ import Fs from 'fs'; import execa from 'execa'; import { createStripAnsiSerializer, REPO_ROOT, createReplaceSerializer } from '@kbn/dev-utils'; -import decompress from 'decompress'; +import extract from 'extract-zip'; import del from 'del'; import globby from 'globby'; import loadJsonFile from 'load-json-file'; @@ -81,7 +81,7 @@ it('builds a generated plugin into a viable archive', async () => { info compressing plugin into [fooTestPlugin-7.5.0.zip]" `); - await decompress(PLUGIN_ARCHIVE, TMP_DIR); + await extract(PLUGIN_ARCHIVE, { dir: TMP_DIR }); const files = await globby(['**/*'], { cwd: TMP_DIR }); files.sort((a, b) => a.localeCompare(b)); diff --git a/packages/kbn-pm/README.md b/packages/kbn-pm/README.md index 05405e7b98374..c169b5c75e178 100644 --- a/packages/kbn-pm/README.md +++ b/packages/kbn-pm/README.md @@ -19,7 +19,7 @@ From a plugin perspective there are two different types of Kibana dependencies: runtime and static dependencies. Runtime dependencies are things that are instantiated at runtime and that are injected into the plugin, for example config and elasticsearch clients. Static dependencies are those dependencies -that we want to `import`. `eslint-config-kibana` is one example of this, and +that we want to `import`. `elastic-eslint-config-kibana` is one example of this, and it's actually needed because eslint requires it to be a separate package. But we also have dependencies like `datemath`, `flot`, `eui` and others that we control, but where we want to `import` them in plugins instead of injecting them diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 24655f8e57026..c84b0a93311bb 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -32,7 +32,7 @@ "parse-link-header": "^1.0.1", "rxjs": "^6.5.5", "strip-ansi": "^5.2.0", - "tar-fs": "^1.16.3", + "tar-fs": "^2.1.0", "tmp": "^0.1.0", "xml2js": "^0.4.22", "zlib": "^1.0.5" diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 531513481b1d4..0067228f1c1f3 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,8 +9,8 @@ "kbn:watch": "node scripts/build --dev --watch" }, "dependencies": { - "@elastic/charts": "19.8.1", - "@elastic/eui": "27.4.1", + "@elastic/charts": "21.0.1", + "@elastic/eui": "28.2.0", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", "@kbn/monaco": "1.0.0", diff --git a/rfcs/text/0010_service_status.md b/rfcs/text/0010_service_status.md index ded594930a367..76195c4f1ab89 100644 --- a/rfcs/text/0010_service_status.md +++ b/rfcs/text/0010_service_status.md @@ -137,7 +137,7 @@ interface StatusSetup { * Current status for all dependencies of the current plugin. * Each key of the `Record` is a plugin id. */ - plugins$: Observable>; + dependencies$: Observable>; /** * The status of this plugin as derived from its dependencies. diff --git a/src/core/public/application/__snapshots__/application_service.test.ts.snap b/src/core/public/application/__snapshots__/application_service.test.ts.snap index c63a22170c4f6..a6c9eb27e338a 100644 --- a/src/core/public/application/__snapshots__/application_service.test.ts.snap +++ b/src/core/public/application/__snapshots__/application_service.test.ts.snap @@ -80,6 +80,7 @@ exports[`#start() getComponent returns renderable JSX tree 1`] = ` } } mounters={Map {}} + setAppActionMenu={[Function]} setAppLeaveHandler={[Function]} setIsMounting={[Function]} /> diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index 47a8a01d917eb..2bdf56ee34211 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -20,6 +20,7 @@ import { History } from 'history'; import { BehaviorSubject, Subject } from 'rxjs'; +import type { MountPoint } from '../types'; import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; import { ApplicationSetup, @@ -87,6 +88,7 @@ const createInternalStartContractMock = (): jest.Mocked>(new Map()), capabilities: capabilitiesServiceMock.createStartContract().capabilities, currentAppId$: currentAppId$.asObservable(), + currentActionMenu$: new BehaviorSubject(undefined), getComponent: jest.fn(), getUrlForApp: jest.fn(), navigateToApp: jest.fn().mockImplementation((appId) => currentAppId$.next(appId)), diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index d7f15decb255d..df0f74c1914e9 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -22,6 +22,7 @@ import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; import { map, shareReplay, takeUntil, distinctUntilChanged, filter } from 'rxjs/operators'; import { createBrowserHistory, History } from 'history'; +import { MountPoint } from '../types'; import { InjectedMetadataSetup } from '../injected_metadata'; import { HttpSetup, HttpStart } from '../http'; import { OverlayStart } from '../overlays'; @@ -90,6 +91,11 @@ interface AppUpdaterWrapper { updater: AppUpdater; } +interface AppInternalState { + leaveHandler?: AppLeaveHandler; + actionMenu?: MountPoint; +} + /** * Service that is responsible for registering new applications. * @internal @@ -98,8 +104,9 @@ export class ApplicationService { private readonly apps = new Map | LegacyApp>(); private readonly mounters = new Map(); private readonly capabilities = new CapabilitiesService(); - private readonly appLeaveHandlers = new Map(); + private readonly appInternalStates = new Map(); private currentAppId$ = new BehaviorSubject(undefined); + private currentActionMenu$ = new BehaviorSubject(undefined); private readonly statusUpdaters$ = new BehaviorSubject>(new Map()); private readonly subscriptions: Subscription[] = []; private stop$ = new Subject(); @@ -293,12 +300,14 @@ export class ApplicationService { if (path === undefined) { path = applications$.value.get(appId)?.defaultPath; } - this.appLeaveHandlers.delete(this.currentAppId$.value!); + this.appInternalStates.delete(this.currentAppId$.value!); this.navigate!(getAppUrl(availableMounters, appId, path), state, replace); this.currentAppId$.next(appId); } }; + this.currentAppId$.subscribe(() => this.refreshCurrentActionMenu()); + return { applications$: applications$.pipe( map((apps) => new Map([...apps.entries()].map(([id, app]) => [id, getAppInfo(app)]))), @@ -310,6 +319,10 @@ export class ApplicationService { distinctUntilChanged(), takeUntil(this.stop$) ), + currentActionMenu$: this.currentActionMenu$.pipe( + distinctUntilChanged(), + takeUntil(this.stop$) + ), history: this.history, registerMountContext: this.mountContext.registerContext, getUrlForApp: ( @@ -338,6 +351,7 @@ export class ApplicationService { mounters={availableMounters} appStatuses$={applicationStatuses$} setAppLeaveHandler={this.setAppLeaveHandler} + setAppActionMenu={this.setAppActionMenu} setIsMounting={(isMounting) => httpLoadingCount$.next(isMounting ? 1 : 0)} /> ); @@ -346,7 +360,24 @@ export class ApplicationService { } private setAppLeaveHandler = (appId: string, handler: AppLeaveHandler) => { - this.appLeaveHandlers.set(appId, handler); + this.appInternalStates.set(appId, { + ...(this.appInternalStates.get(appId) ?? {}), + leaveHandler: handler, + }); + }; + + private setAppActionMenu = (appId: string, mount: MountPoint | undefined) => { + this.appInternalStates.set(appId, { + ...(this.appInternalStates.get(appId) ?? {}), + actionMenu: mount, + }); + this.refreshCurrentActionMenu(); + }; + + private refreshCurrentActionMenu = () => { + const appId = this.currentAppId$.getValue(); + const currentActionMenu = appId ? this.appInternalStates.get(appId)?.actionMenu : undefined; + this.currentActionMenu$.next(currentActionMenu); }; private async shouldNavigate(overlays: OverlayStart): Promise { @@ -354,7 +385,7 @@ export class ApplicationService { if (currentAppId === undefined) { return true; } - const action = getLeaveAction(this.appLeaveHandlers.get(currentAppId)); + const action = getLeaveAction(this.appInternalStates.get(currentAppId)?.leaveHandler); if (isConfirmAction(action)) { const confirmed = await overlays.openConfirm(action.text, { title: action.title, @@ -372,7 +403,7 @@ export class ApplicationService { if (currentAppId === undefined) { return; } - const action = getLeaveAction(this.appLeaveHandlers.get(currentAppId)); + const action = getLeaveAction(this.appInternalStates.get(currentAppId)?.leaveHandler); if (isConfirmAction(action)) { event.preventDefault(); // some browsers accept a string return value being the message displayed @@ -383,6 +414,7 @@ export class ApplicationService { public stop() { this.stop$.next(); this.currentAppId$.complete(); + this.currentActionMenu$.complete(); this.statusUpdaters$.complete(); this.subscriptions.forEach((sub) => sub.unsubscribe()); window.removeEventListener('beforeunload', this.onBeforeUnload); diff --git a/src/core/public/application/integration_tests/application_service.test.tsx b/src/core/public/application/integration_tests/application_service.test.tsx index b0419d276dfa1..9eafddd6a61fe 100644 --- a/src/core/public/application/integration_tests/application_service.test.tsx +++ b/src/core/public/application/integration_tests/application_service.test.tsx @@ -30,6 +30,8 @@ import { MockLifecycle } from '../test_types'; import { overlayServiceMock } from '../../overlays/overlay_service.mock'; import { AppMountParameters } from '../types'; import { ScopedHistory } from '../scoped_history'; +import { Observable } from 'rxjs'; +import { MountPoint } from 'kibana/public'; const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); @@ -309,4 +311,189 @@ describe('ApplicationService', () => { expect(history.entries[1].pathname).toEqual('/app/app1'); }); }); + + describe('registering action menus', () => { + const getValue = (obs: Observable): Promise => { + return obs.pipe(take(1)).toPromise(); + }; + + const mounter1: MountPoint = () => () => undefined; + const mounter2: MountPoint = () => () => undefined; + + it('updates the observable value when an application is mounted', async () => { + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: async ({ setHeaderActionMenu }: AppMountParameters) => { + setHeaderActionMenu(mounter1); + return () => undefined; + }, + }); + + const { navigateToApp, getComponent, currentActionMenu$ } = await service.start(startDeps); + update = createRenderer(getComponent()); + + expect(await getValue(currentActionMenu$)).toBeUndefined(); + + await act(async () => { + await navigateToApp('app1'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter1); + }); + + it('updates the observable value when switching application', async () => { + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: async ({ setHeaderActionMenu }: AppMountParameters) => { + setHeaderActionMenu(mounter1); + return () => undefined; + }, + }); + register(Symbol(), { + id: 'app2', + title: 'App2', + mount: async ({ setHeaderActionMenu }: AppMountParameters) => { + setHeaderActionMenu(mounter2); + return () => undefined; + }, + }); + + const { navigateToApp, getComponent, currentActionMenu$ } = await service.start(startDeps); + update = createRenderer(getComponent()); + + await act(async () => { + await navigateToApp('app1'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter1); + + await act(async () => { + await navigateToApp('app2'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter2); + }); + + it('updates the observable value to undefined when switching to an application without action menu', async () => { + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: async ({ setHeaderActionMenu }: AppMountParameters) => { + setHeaderActionMenu(mounter1); + return () => undefined; + }, + }); + register(Symbol(), { + id: 'app2', + title: 'App2', + mount: async ({}: AppMountParameters) => { + return () => undefined; + }, + }); + + const { navigateToApp, getComponent, currentActionMenu$ } = await service.start(startDeps); + update = createRenderer(getComponent()); + + await act(async () => { + await navigateToApp('app1'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter1); + + await act(async () => { + await navigateToApp('app2'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBeUndefined(); + }); + + it('allow applications to call `setHeaderActionMenu` multiple times', async () => { + const { register } = service.setup(setupDeps); + + let resolveMount: () => void; + const promise = new Promise((resolve) => { + resolveMount = resolve; + }); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: async ({ setHeaderActionMenu }: AppMountParameters) => { + setHeaderActionMenu(mounter1); + promise.then(() => { + setHeaderActionMenu(mounter2); + }); + return () => undefined; + }, + }); + + const { navigateToApp, getComponent, currentActionMenu$ } = await service.start(startDeps); + update = createRenderer(getComponent()); + + await act(async () => { + await navigateToApp('app1'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter1); + + await act(async () => { + resolveMount(); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter2); + }); + + it('allow applications to unset the current menu', async () => { + const { register } = service.setup(setupDeps); + + let resolveMount: () => void; + const promise = new Promise((resolve) => { + resolveMount = resolve; + }); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: async ({ setHeaderActionMenu }: AppMountParameters) => { + setHeaderActionMenu(mounter1); + promise.then(() => { + setHeaderActionMenu(undefined); + }); + return () => undefined; + }, + }); + + const { navigateToApp, getComponent, currentActionMenu$ } = await service.start(startDeps); + update = createRenderer(getComponent()); + + await act(async () => { + await navigateToApp('app1'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter1); + + await act(async () => { + resolveMount(); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBeUndefined(); + }); + }); }); diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx index f992e121437a9..6408b8123365e 100644 --- a/src/core/public/application/integration_tests/router.test.tsx +++ b/src/core/public/application/integration_tests/router.test.tsx @@ -59,6 +59,7 @@ describe('AppRouter', () => { mounters={mockMountersToMounters()} appStatuses$={mountersToAppStatus$()} setAppLeaveHandler={noop} + setAppActionMenu={noop} setIsMounting={noop} /> ); diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 0fe97431b1569..320416a8c2379 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -21,6 +21,7 @@ import { Observable } from 'rxjs'; import { History } from 'history'; import { RecursiveReadonly } from '@kbn/utility-types'; +import { MountPoint } from '../types'; import { Capabilities } from './capabilities'; import { ChromeStart } from '../chrome'; import { IContextProvider } from '../context'; @@ -495,6 +496,37 @@ export interface AppMountParameters { * ``` */ onAppLeave: (handler: AppLeaveHandler) => void; + + /** + * A function that can be used to set the mount point used to populate the application action container + * in the chrome header. + * + * Calling the handler multiple time will erase the current content of the action menu with the mount from the latest call. + * Calling the handler with `undefined` will unmount the current mount point. + * Calling the handler after the application has been unmounted will have no effect. + * + * @example + * + * ```ts + * // application.tsx + * import React from 'react'; + * import ReactDOM from 'react-dom'; + * import { BrowserRouter, Route } from 'react-router-dom'; + * + * import { CoreStart, AppMountParameters } from 'src/core/public'; + * import { MyPluginDepsStart } from './plugin'; + * + * export renderApp = ({ element, history, setHeaderActionMenu }: AppMountParameters) => { + * const { renderApp } = await import('./application'); + * const { renderActionMenu } = await import('./action_menu'); + * setHeaderActionMenu((element) => { + * return renderActionMenu(element); + * }) + * return renderApp({ element, history }); + * } + * ``` + */ + setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; } /** @@ -820,6 +852,14 @@ export interface InternalApplicationStart extends Omit; + /** * The global history instance, exposed only to Core. Undefined when rendering a legacy application. * @internal diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index a94313dd53abb..e26fe7e59fd04 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -29,6 +29,7 @@ import { ScopedHistory } from '../scoped_history'; describe('AppContainer', () => { const appId = 'someApp'; const setAppLeaveHandler = jest.fn(); + const setAppActionMenu = jest.fn(); const setIsMounting = jest.fn(); beforeEach(() => { @@ -76,6 +77,7 @@ describe('AppContainer', () => { appStatus={AppStatus.inaccessible} mounter={mounter} setAppLeaveHandler={setAppLeaveHandler} + setAppActionMenu={setAppActionMenu} setIsMounting={setIsMounting} createScopedHistory={(appPath: string) => // Create a history using the appPath as the current location @@ -116,6 +118,7 @@ describe('AppContainer', () => { appStatus={AppStatus.accessible} mounter={mounter} setAppLeaveHandler={setAppLeaveHandler} + setAppActionMenu={setAppActionMenu} setIsMounting={setIsMounting} createScopedHistory={(appPath: string) => // Create a history using the appPath as the current location @@ -158,6 +161,7 @@ describe('AppContainer', () => { appStatus={AppStatus.accessible} mounter={mounter} setAppLeaveHandler={setAppLeaveHandler} + setAppActionMenu={setAppActionMenu} setIsMounting={setIsMounting} createScopedHistory={(appPath: string) => // Create a history using the appPath as the current location diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index 332c31c64b6ba..f668cf851da55 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -25,8 +25,9 @@ import React, { useState, MutableRefObject, } from 'react'; - import { EuiLoadingSpinner } from '@elastic/eui'; + +import type { MountPoint } from '../../types'; import { AppLeaveHandler, AppStatus, AppUnmount, Mounter } from '../types'; import { AppNotFound } from './app_not_found_screen'; import { ScopedHistory } from '../scoped_history'; @@ -39,6 +40,7 @@ interface Props { mounter?: Mounter; appStatus: AppStatus; setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; + setAppActionMenu: (appId: string, mount: MountPoint | undefined) => void; createScopedHistory: (appUrl: string) => ScopedHistory; setIsMounting: (isMounting: boolean) => void; } @@ -48,6 +50,7 @@ export const AppContainer: FunctionComponent = ({ appId, appPath, setAppLeaveHandler, + setAppActionMenu, createScopedHistory, appStatus, setIsMounting, @@ -84,6 +87,7 @@ export const AppContainer: FunctionComponent = ({ history: createScopedHistory(appPath), element: elementRef.current!, onAppLeave: (handler) => setAppLeaveHandler(appId, handler), + setHeaderActionMenu: (menuMount) => setAppActionMenu(appId, menuMount), })) || null; } catch (e) { // TODO: add error UI @@ -98,7 +102,16 @@ export const AppContainer: FunctionComponent = ({ mount(); return unmount; - }, [appId, appStatus, mounter, createScopedHistory, setAppLeaveHandler, appPath, setIsMounting]); + }, [ + appId, + appStatus, + mounter, + createScopedHistory, + setAppLeaveHandler, + setAppActionMenu, + appPath, + setIsMounting, + ]); return ( diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx index f1f22237c32db..5021dd3ae765a 100644 --- a/src/core/public/application/ui/app_router.tsx +++ b/src/core/public/application/ui/app_router.tsx @@ -23,6 +23,7 @@ import { History } from 'history'; import { Observable } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; +import type { MountPoint } from '../../types'; import { AppLeaveHandler, AppStatus, Mounter } from '../types'; import { AppContainer } from './app_container'; import { ScopedHistory } from '../scoped_history'; @@ -32,6 +33,7 @@ interface Props { history: History; appStatuses$: Observable>; setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; + setAppActionMenu: (appId: string, mount: MountPoint | undefined) => void; setIsMounting: (isMounting: boolean) => void; } @@ -43,6 +45,7 @@ export const AppRouter: FunctionComponent = ({ history, mounters, setAppLeaveHandler, + setAppActionMenu, appStatuses$, setIsMounting, }) => { @@ -69,7 +72,7 @@ export const AppRouter: FunctionComponent = ({ appPath={path} appStatus={appStatuses.get(appId) ?? AppStatus.inaccessible} createScopedHistory={createScopedHistory} - {...{ appId, mounter, setAppLeaveHandler, setIsMounting }} + {...{ appId, mounter, setAppLeaveHandler, setAppActionMenu, setIsMounting }} /> )} /> @@ -94,7 +97,7 @@ export const AppRouter: FunctionComponent = ({ appId={id} appStatus={appStatuses.get(id) ?? AppStatus.inaccessible} createScopedHistory={createScopedHistory} - {...{ mounter, setAppLeaveHandler, setIsMounting }} + {...{ mounter, setAppLeaveHandler, setAppActionMenu, setIsMounting }} /> ); }} diff --git a/src/core/public/chrome/recently_accessed/persisted_log.test.ts b/src/core/public/chrome/recently_accessed/persisted_log.test.ts index 4229efdf7ca9d..345ad8f3a1f5a 100644 --- a/src/core/public/chrome/recently_accessed/persisted_log.test.ts +++ b/src/core/public/chrome/recently_accessed/persisted_log.test.ts @@ -28,12 +28,6 @@ const createMockStorage = () => ({ length: 0, }); -jest.mock('ui/chrome', () => { - return { - getBasePath: () => `/some/base/path`, - }; -}); - const historyName = 'testHistory'; const historyLimit = 10; const payload = [ diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index fe959e570ab98..79beaf79068ef 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -650,6 +650,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` data-test-subj="collapsibleNavGroup-recentlyViewed" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > @@ -901,6 +903,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` data-test-subj="collapsibleNavGroup-kibana" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > @@ -1188,6 +1192,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > @@ -1436,6 +1442,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` data-test-subj="collapsibleNavGroup-securitySolution" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > @@ -1636,6 +1644,8 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` data-test-subj="collapsibleNavGroup-management" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > @@ -2624,6 +2634,8 @@ exports[`CollapsibleNav renders the default nav 2`] = ` data-test-subj="collapsibleNavGroup-recentlyViewed" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > @@ -3305,6 +3317,8 @@ exports[`CollapsibleNav renders the default nav 3`] = ` data-test-subj="collapsibleNavGroup-recentlyViewed" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index f101c30a1241b..5ec7a4773967b 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -29,6 +29,15 @@ exports[`Header renders 1`] = ` "management": Object {}, "navLinks": Object {}, }, + "currentActionMenu$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, "currentAppId$": Observable { "_isScalar": false, "source": Subject { @@ -641,6 +650,15 @@ exports[`Header renders 2`] = ` "management": Object {}, "navLinks": Object {}, }, + "currentActionMenu$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, "currentAppId$": Observable { "_isScalar": false, "source": Subject { @@ -4854,6 +4872,15 @@ exports[`Header renders 3`] = ` "management": Object {}, "navLinks": Object {}, }, + "currentActionMenu$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, "currentAppId$": Observable { "_isScalar": false, "source": Subject { @@ -9265,6 +9292,8 @@ exports[`Header renders 3`] = ` data-test-subj="collapsibleNavGroup-recentlyViewed" id="mockId" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > @@ -9706,6 +9735,15 @@ exports[`Header renders 4`] = ` "management": Object {}, "navLinks": Object {}, }, + "currentActionMenu$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, "currentAppId$": Observable { "_isScalar": false, "source": Subject { diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 2f7f6fae94436..aefcb830d40bf 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -166,6 +166,7 @@ function createAppMountParametersMock(appBasePath = '') { element: document.createElement('div'), history, onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), }; return params; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 6f25f46c76fb9..bacbd6e757114 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -165,6 +165,7 @@ export interface AppMountParameters { element: HTMLElement; history: ScopedHistory; onAppLeave: (handler: AppLeaveHandler) => void; + setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; } // @public @@ -1188,8 +1189,10 @@ export interface SavedObjectsFindOptions { // (undocumented) defaultSearchOperator?: 'AND' | 'OR'; fields?: string[]; + // Warning: (ae-forgotten-export) The symbol "KueryNode" needs to be exported by the entry point index.d.ts + // // (undocumented) - filter?: string; + filter?: string | KueryNode; // (undocumented) hasReference?: { type: string; diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index adfdecdd7c976..7d5557be92b30 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -323,6 +323,9 @@ export class LegacyService implements CoreService { status: { core$: setupDeps.core.status.core$, overall$: setupDeps.core.status.overall$, + set: setupDeps.core.status.plugins.set.bind(null, 'legacy'), + dependencies$: setupDeps.core.status.plugins.getDependenciesStatus$('legacy'), + derivedStatus$: setupDeps.core.status.plugins.getDerivedStatus$('legacy'), }, uiSettings: { register: setupDeps.core.uiSettings.register, diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index fa2659ca130a0..eb31b2380d177 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -185,6 +185,9 @@ export function createPluginSetupContext( status: { core$: deps.status.core$, overall$: deps.status.overall$, + set: deps.status.plugins.set.bind(null, plugin.name), + dependencies$: deps.status.plugins.getDependenciesStatus$(plugin.name), + derivedStatus$: deps.status.plugins.getDerivedStatus$(plugin.name), }, uiSettings: { register: deps.uiSettings.register, diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 7af77491df1ab..71ac31db13f92 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -100,15 +100,27 @@ test('getPluginDependencies returns dependency tree of symbols', () => { pluginsSystem.addPlugin(createPlugin('no-dep')); expect(pluginsSystem.getPluginDependencies()).toMatchInlineSnapshot(` - Map { - Symbol(plugin-a) => Array [ - Symbol(no-dep), - ], - Symbol(plugin-b) => Array [ - Symbol(plugin-a), - Symbol(no-dep), - ], - Symbol(no-dep) => Array [], + Object { + "asNames": Map { + "plugin-a" => Array [ + "no-dep", + ], + "plugin-b" => Array [ + "plugin-a", + "no-dep", + ], + "no-dep" => Array [], + }, + "asOpaqueIds": Map { + Symbol(plugin-a) => Array [ + Symbol(no-dep), + ], + Symbol(plugin-b) => Array [ + Symbol(plugin-a), + Symbol(no-dep), + ], + Symbol(no-dep) => Array [], + }, } `); }); diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index f5c1b35d678a3..b2acd9a6fd04b 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -20,10 +20,11 @@ import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { PluginWrapper } from './plugin'; -import { DiscoveredPlugin, PluginName, PluginOpaqueId } from './types'; +import { DiscoveredPlugin, PluginName } from './types'; import { createPluginSetupContext, createPluginStartContext } from './plugin_context'; import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; import { withTimeout } from '../../utils'; +import { PluginDependencies } from '.'; const Sec = 1000; /** @internal */ @@ -45,9 +46,19 @@ export class PluginsSystem { * @returns a ReadonlyMap of each plugin and an Array of its available dependencies * @internal */ - public getPluginDependencies(): ReadonlyMap { - // Return dependency map of opaque ids - return new Map( + public getPluginDependencies(): PluginDependencies { + const asNames = new Map( + [...this.plugins].map(([name, plugin]) => [ + plugin.name, + [ + ...new Set([ + ...plugin.requiredPlugins, + ...plugin.optionalPlugins.filter((optPlugin) => this.plugins.has(optPlugin)), + ]), + ].map((depId) => this.plugins.get(depId)!.name), + ]) + ); + const asOpaqueIds = new Map( [...this.plugins].map(([name, plugin]) => [ plugin.opaqueId, [ @@ -58,6 +69,8 @@ export class PluginsSystem { ].map((depId) => this.plugins.get(depId)!.opaqueId), ]) ); + + return { asNames, asOpaqueIds }; } public async setupPlugins(deps: PluginsServiceSetupDeps) { diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index eb2a9ca3daf5f..517261b5bc9bb 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -93,6 +93,12 @@ export type PluginName = string; /** @public */ export type PluginOpaqueId = symbol; +/** @internal */ +export interface PluginDependencies { + asNames: ReadonlyMap; + asOpaqueIds: ReadonlyMap; +} + /** * Describes the set of required and optional properties plugin can define in its * mandatory JSON manifest file. diff --git a/src/core/server/saved_objects/service/lib/filter_utils.test.ts b/src/core/server/saved_objects/service/lib/filter_utils.test.ts index 4d9bcdda3c8ae..60e8aa0afdda4 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.test.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.test.ts @@ -83,7 +83,19 @@ const mockMappings = { describe('Filter Utils', () => { describe('#validateConvertFilterToKueryNode', () => { - test('Validate a simple filter', () => { + test('Empty string filters are ignored', () => { + expect(validateConvertFilterToKueryNode(['foo'], '', mockMappings)).toBeUndefined(); + }); + test('Validate a simple KQL KueryNode filter', () => { + expect( + validateConvertFilterToKueryNode( + ['foo'], + esKuery.nodeTypes.function.buildNode('is', `foo.attributes.title`, 'best', true), + mockMappings + ) + ).toEqual(esKuery.fromKueryExpression('foo.title: "best"')); + }); + test('Validate a simple KQL expression filter', () => { expect( validateConvertFilterToKueryNode(['foo'], 'foo.attributes.title: "best"', mockMappings) ).toEqual(esKuery.fromKueryExpression('foo.title: "best"')); diff --git a/src/core/server/saved_objects/service/lib/filter_utils.ts b/src/core/server/saved_objects/service/lib/filter_utils.ts index 5fbe62a074b29..d19f06d74e419 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.ts @@ -28,11 +28,12 @@ const astFunctionType = ['is', 'range', 'nested']; export const validateConvertFilterToKueryNode = ( allowedTypes: string[], - filter: string, + filter: string | KueryNode, indexMapping: IndexMapping ): KueryNode | undefined => { - if (filter && filter.length > 0 && indexMapping) { - const filterKueryNode = esKuery.fromKueryExpression(filter); + if (filter && indexMapping) { + const filterKueryNode = + typeof filter === 'string' ? esKuery.fromKueryExpression(filter) : filter; const validationFilterKuery = validateFilterKueryNode({ astFilter: filterKueryNode, diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 39433981dfd59..b1d6028465713 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -25,6 +25,8 @@ import { encodeHitVersion } from '../../version'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { DocumentMigrator } from '../../migrations/core/document_migrator'; import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { nodeTypes } from '../../../../../plugins/data/common/es_query'; jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() })); @@ -2529,7 +2531,7 @@ describe('SavedObjectsRepository', () => { expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, relevantOpts); }); - it(`accepts KQL filter and passes kueryNode to getSearchDsl`, async () => { + it(`accepts KQL expression filter and passes KueryNode to getSearchDsl`, async () => { const findOpts = { namespace, search: 'foo*', @@ -2570,6 +2572,47 @@ describe('SavedObjectsRepository', () => { `); }); + it(`accepts KQL KueryNode filter and passes KueryNode to getSearchDsl`, async () => { + const findOpts = { + namespace, + search: 'foo*', + searchFields: ['foo'], + type: ['dashboard'], + sortField: 'name', + sortOrder: 'desc', + defaultSearchOperator: 'AND', + hasReference: { + type: 'foo', + id: '1', + }, + indexPattern: undefined, + filter: nodeTypes.function.buildNode('is', `dashboard.attributes.otherField`, '*'), + }; + + await findSuccess(findOpts, namespace); + const { kueryNode } = getSearchDslNS.getSearchDsl.mock.calls[0][2]; + expect(kueryNode).toMatchInlineSnapshot(` + Object { + "arguments": Array [ + Object { + "type": "literal", + "value": "dashboard.otherField", + }, + Object { + "type": "wildcard", + "value": "@kuery-wildcard@", + }, + Object { + "type": "literal", + "value": false, + }, + ], + "function": "is", + "type": "function", + } + `); + }); + it(`supports multiple types`, async () => { const types = ['config', 'index-pattern']; await findSuccess({ type: types }); diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index edbdbe4d16784..000153cd542fa 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -39,6 +39,9 @@ import { SavedObjectUnsanitizedDoc } from './serialization'; import { SavedObjectsMigrationLogger } from './migrations/core/migration_logger'; import { SavedObject } from '../../types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KueryNode } from '../../../plugins/data/common'; + export { SavedObjectAttributes, SavedObjectAttribute, @@ -89,7 +92,7 @@ export interface SavedObjectsFindOptions { rootSearchFields?: string[]; hasReference?: { type: string; id: string }; defaultSearchOperator?: 'AND' | 'OR'; - filter?: string; + filter?: string | KueryNode; namespaces?: string[]; /** An optional ES preference value to be used for the query **/ preference?: string; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 49c97d837579d..05afad5a4f7a4 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2320,8 +2320,10 @@ export interface SavedObjectsFindOptions { // (undocumented) defaultSearchOperator?: 'AND' | 'OR'; fields?: string[]; + // Warning: (ae-forgotten-export) The symbol "KueryNode" needs to be exported by the entry point index.d.ts + // // (undocumented) - filter?: string; + filter?: string | KueryNode; // (undocumented) hasReference?: { type: string; @@ -2853,10 +2855,17 @@ export type SharedGlobalConfig = RecursiveReadonly<{ // @public export type StartServicesAccessor = () => Promise<[CoreStart, TPluginsStart, TStart]>; +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ServiceStatusSetup" +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ServiceStatusSetup" +// // @public export interface StatusServiceSetup { core$: Observable; + dependencies$: Observable>; + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "StatusSetup" + derivedStatus$: Observable; overall$: Observable; + set(status$: Observable): void; } // @public @@ -2949,8 +2958,8 @@ export const validBodyOutput: readonly ["data", "stream"]; // src/core/server/legacy/types.ts:165:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:166:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:167:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:266:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:266:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:268:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:272:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:272:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 417f66a2988c2..1bd364c2f87b7 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -41,6 +41,7 @@ import { Server } from './server'; import { getEnvOptions } from './config/__mocks__/env'; import { loggingSystemMock } from './logging/logging_system.mock'; import { rawConfigServiceMock } from './config/raw_config_service.mock'; +import { PluginName } from './plugins'; const env = new Env('.', getEnvOptions()); const logger = loggingSystemMock.create(); @@ -49,7 +50,7 @@ const rawConfigService = rawConfigServiceMock.create({}); beforeEach(() => { mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true })); mockPluginsService.discover.mockResolvedValue({ - pluginTree: new Map(), + pluginTree: { asOpaqueIds: new Map(), asNames: new Map() }, uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, }); }); @@ -98,7 +99,7 @@ test('injects legacy dependency to context#setup()', async () => { [pluginB, [pluginA]], ]); mockPluginsService.discover.mockResolvedValue({ - pluginTree: pluginDependencies, + pluginTree: { asOpaqueIds: pluginDependencies, asNames: new Map() }, uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, }); @@ -113,6 +114,31 @@ test('injects legacy dependency to context#setup()', async () => { }); }); +test('injects legacy dependency to status#setup()', async () => { + const server = new Server(rawConfigService, env, logger); + + const pluginDependencies = new Map([ + ['a', []], + ['b', ['a']], + ]); + mockPluginsService.discover.mockResolvedValue({ + pluginTree: { asOpaqueIds: new Map(), asNames: pluginDependencies }, + uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, + }); + + await server.setup(); + + expect(mockStatusService.setup).toHaveBeenCalledWith({ + elasticsearch: expect.any(Object), + savedObjects: expect.any(Object), + pluginDependencies: new Map([ + ['a', []], + ['b', ['a']], + ['legacy', ['a', 'b']], + ]), + }); +}); + test('runs services on "start"', async () => { const server = new Server(rawConfigService, env, logger); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index cc6d8171e7a03..e2f77f0551f34 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -121,10 +121,13 @@ export class Server { const contextServiceSetup = this.context.setup({ // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: - // 1) Can access context from any NP plugin + // 1) Can access context from any KP plugin // 2) Can register context providers that will only be available to other legacy plugins and will not leak into // New Platform plugins. - pluginDependencies: new Map([...pluginTree, [this.legacy.legacyId, [...pluginTree.keys()]]]), + pluginDependencies: new Map([ + ...pluginTree.asOpaqueIds, + [this.legacy.legacyId, [...pluginTree.asOpaqueIds.keys()]], + ]), }); const auditTrailSetup = this.auditTrail.setup(); @@ -154,6 +157,12 @@ export class Server { const statusSetup = await this.status.setup({ elasticsearch: elasticsearchServiceSetup, + // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy can access plugin status from + // any KP plugin + pluginDependencies: new Map([ + ...pluginTree.asNames, + ['legacy', [...pluginTree.asNames.keys()]], + ]), savedObjects: savedObjectsSetup, }); diff --git a/src/core/server/status/get_summary_status.test.ts b/src/core/server/status/get_summary_status.test.ts index 7516e82ee784d..d97083162b502 100644 --- a/src/core/server/status/get_summary_status.test.ts +++ b/src/core/server/status/get_summary_status.test.ts @@ -94,6 +94,38 @@ describe('getSummaryStatus', () => { describe('summary', () => { describe('when a single service is at highest level', () => { it('returns all information about that single service', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + meta: { + custom: { data: 'here' }, + }, + }, + }) + ) + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '[s2]: Lorem ipsum', + detail: 'See the status page for more information', + meta: { + affectedServices: { + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + meta: { + custom: { data: 'here' }, + }, + }, + }, + }, + }); + }); + + it('allows the single service to override the detail and documentationUrl fields', () => { expect( getSummaryStatus( Object.entries({ @@ -115,7 +147,17 @@ describe('getSummaryStatus', () => { detail: 'Vivamus pulvinar sem ac luctus ultrices.', documentationUrl: 'http://helpmenow.com/problem1', meta: { - custom: { data: 'here' }, + affectedServices: { + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + }, }, }); }); diff --git a/src/core/server/status/get_summary_status.ts b/src/core/server/status/get_summary_status.ts index 748a54f0bf8bb..1dc92839e8261 100644 --- a/src/core/server/status/get_summary_status.ts +++ b/src/core/server/status/get_summary_status.ts @@ -23,7 +23,10 @@ import { ServiceStatus, ServiceStatusLevels, ServiceStatusLevel } from './types' * Returns a single {@link ServiceStatus} that summarizes the most severe status level from a group of statuses. * @param statuses */ -export const getSummaryStatus = (statuses: Array<[string, ServiceStatus]>): ServiceStatus => { +export const getSummaryStatus = ( + statuses: Array<[string, ServiceStatus]>, + { allAvailableSummary = `All services are available` }: { allAvailableSummary?: string } = {} +): ServiceStatus => { const grouped = groupByLevel(statuses); const highestSeverityLevel = getHighestSeverityLevel(grouped.keys()); const highestSeverityGroup = grouped.get(highestSeverityLevel)!; @@ -31,13 +34,18 @@ export const getSummaryStatus = (statuses: Array<[string, ServiceStatus]>): Serv if (highestSeverityLevel === ServiceStatusLevels.available) { return { level: ServiceStatusLevels.available, - summary: `All services are available`, + summary: allAvailableSummary, }; } else if (highestSeverityGroup.size === 1) { const [serviceName, status] = [...highestSeverityGroup.entries()][0]; return { ...status, summary: `[${serviceName}]: ${status.summary!}`, + // TODO: include URL to status page + detail: status.detail ?? `See the status page for more information`, + meta: { + affectedServices: { [serviceName]: status }, + }, }; } else { return { diff --git a/src/core/server/status/plugins_status.test.ts b/src/core/server/status/plugins_status.test.ts new file mode 100644 index 0000000000000..b2d2ac8a5ef90 --- /dev/null +++ b/src/core/server/status/plugins_status.test.ts @@ -0,0 +1,338 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginName } from '../plugins'; +import { PluginsStatusService } from './plugins_status'; +import { of, Observable, BehaviorSubject } from 'rxjs'; +import { ServiceStatusLevels, CoreStatus, ServiceStatus } from './types'; +import { first } from 'rxjs/operators'; +import { ServiceStatusLevelSnapshotSerializer } from './test_utils'; + +expect.addSnapshotSerializer(ServiceStatusLevelSnapshotSerializer); + +describe('PluginStatusService', () => { + const coreAllAvailable$: Observable = of({ + elasticsearch: { level: ServiceStatusLevels.available, summary: 'elasticsearch avail' }, + savedObjects: { level: ServiceStatusLevels.available, summary: 'savedObjects avail' }, + }); + const coreOneDegraded$: Observable = of({ + elasticsearch: { level: ServiceStatusLevels.available, summary: 'elasticsearch avail' }, + savedObjects: { level: ServiceStatusLevels.degraded, summary: 'savedObjects degraded' }, + }); + const coreOneCriticalOneDegraded$: Observable = of({ + elasticsearch: { level: ServiceStatusLevels.critical, summary: 'elasticsearch critical' }, + savedObjects: { level: ServiceStatusLevels.degraded, summary: 'savedObjects degraded' }, + }); + const pluginDependencies: Map = new Map([ + ['a', []], + ['b', ['a']], + ['c', ['a', 'b']], + ]); + + describe('getDerivedStatus$', () => { + it(`defaults to core's most severe status`, async () => { + const serviceAvailable = new PluginsStatusService({ + core$: coreAllAvailable$, + pluginDependencies, + }); + expect(await serviceAvailable.getDerivedStatus$('a').pipe(first()).toPromise()).toEqual({ + level: ServiceStatusLevels.available, + summary: 'All dependencies are available', + }); + + const serviceDegraded = new PluginsStatusService({ + core$: coreOneDegraded$, + pluginDependencies, + }); + expect(await serviceDegraded.getDerivedStatus$('a').pipe(first()).toPromise()).toEqual({ + level: ServiceStatusLevels.degraded, + summary: '[savedObjects]: savedObjects degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }); + + const serviceCritical = new PluginsStatusService({ + core$: coreOneCriticalOneDegraded$, + pluginDependencies, + }); + expect(await serviceCritical.getDerivedStatus$('a').pipe(first()).toPromise()).toEqual({ + level: ServiceStatusLevels.critical, + summary: '[elasticsearch]: elasticsearch critical', + detail: 'See the status page for more information', + meta: expect.any(Object), + }); + }); + + it(`provides a summary status when core and dependencies are at same severity level`, async () => { + const service = new PluginsStatusService({ core$: coreOneDegraded$, pluginDependencies }); + service.set('a', of({ level: ServiceStatusLevels.degraded, summary: 'a is degraded' })); + expect(await service.getDerivedStatus$('b').pipe(first()).toPromise()).toEqual({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }); + }); + + it(`allows dependencies status to take precedence over lower severity core statuses`, async () => { + const service = new PluginsStatusService({ core$: coreOneDegraded$, pluginDependencies }); + service.set('a', of({ level: ServiceStatusLevels.unavailable, summary: 'a is not working' })); + expect(await service.getDerivedStatus$('b').pipe(first()).toPromise()).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '[a]: a is not working', + detail: 'See the status page for more information', + meta: expect.any(Object), + }); + }); + + it(`allows core status to take precedence over lower severity dependencies statuses`, async () => { + const service = new PluginsStatusService({ + core$: coreOneCriticalOneDegraded$, + pluginDependencies, + }); + service.set('a', of({ level: ServiceStatusLevels.unavailable, summary: 'a is not working' })); + expect(await service.getDerivedStatus$('b').pipe(first()).toPromise()).toEqual({ + level: ServiceStatusLevels.critical, + summary: '[elasticsearch]: elasticsearch critical', + detail: 'See the status page for more information', + meta: expect.any(Object), + }); + }); + + it(`allows a severe dependency status to take precedence over a less severe dependency status`, async () => { + const service = new PluginsStatusService({ core$: coreOneDegraded$, pluginDependencies }); + service.set('a', of({ level: ServiceStatusLevels.degraded, summary: 'a is degraded' })); + service.set('b', of({ level: ServiceStatusLevels.unavailable, summary: 'b is not working' })); + expect(await service.getDerivedStatus$('c').pipe(first()).toPromise()).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '[b]: b is not working', + detail: 'See the status page for more information', + meta: expect.any(Object), + }); + }); + }); + + describe('getAll$', () => { + it('defaults to empty record if no plugins', async () => { + const service = new PluginsStatusService({ + core$: coreAllAvailable$, + pluginDependencies: new Map(), + }); + expect(await service.getAll$().pipe(first()).toPromise()).toEqual({}); + }); + + it('defaults to core status when no plugin statuses are set', async () => { + const serviceAvailable = new PluginsStatusService({ + core$: coreAllAvailable$, + pluginDependencies, + }); + expect(await serviceAvailable.getAll$().pipe(first()).toPromise()).toEqual({ + a: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' }, + b: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' }, + c: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' }, + }); + + const serviceDegraded = new PluginsStatusService({ + core$: coreOneDegraded$, + pluginDependencies, + }); + expect(await serviceDegraded.getAll$().pipe(first()).toPromise()).toEqual({ + a: { + level: ServiceStatusLevels.degraded, + summary: '[savedObjects]: savedObjects degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + b: { + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + c: { + level: ServiceStatusLevels.degraded, + summary: '[3] services are degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + }); + + const serviceCritical = new PluginsStatusService({ + core$: coreOneCriticalOneDegraded$, + pluginDependencies, + }); + expect(await serviceCritical.getAll$().pipe(first()).toPromise()).toEqual({ + a: { + level: ServiceStatusLevels.critical, + summary: '[elasticsearch]: elasticsearch critical', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + b: { + level: ServiceStatusLevels.critical, + summary: '[2] services are critical', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + c: { + level: ServiceStatusLevels.critical, + summary: '[3] services are critical', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + }); + }); + + it('uses the manually set status level if plugin specifies one', async () => { + const service = new PluginsStatusService({ core$: coreOneDegraded$, pluginDependencies }); + service.set('a', of({ level: ServiceStatusLevels.available, summary: 'a status' })); + + expect(await service.getAll$().pipe(first()).toPromise()).toEqual({ + a: { level: ServiceStatusLevels.available, summary: 'a status' }, // a is available depsite savedObjects being degraded + b: { + level: ServiceStatusLevels.degraded, + summary: '[savedObjects]: savedObjects degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + c: { + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + }); + }); + + it('updates when a new plugin status observable is set', async () => { + const service = new PluginsStatusService({ + core$: coreAllAvailable$, + pluginDependencies: new Map([['a', []]]), + }); + const statusUpdates: Array> = []; + const subscription = service + .getAll$() + .subscribe((pluginStatuses) => statusUpdates.push(pluginStatuses)); + + service.set('a', of({ level: ServiceStatusLevels.degraded, summary: 'a degraded' })); + service.set('a', of({ level: ServiceStatusLevels.unavailable, summary: 'a unavailable' })); + service.set('a', of({ level: ServiceStatusLevels.available, summary: 'a available' })); + subscription.unsubscribe(); + + expect(statusUpdates).toEqual([ + { a: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' } }, + { a: { level: ServiceStatusLevels.degraded, summary: 'a degraded' } }, + { a: { level: ServiceStatusLevels.unavailable, summary: 'a unavailable' } }, + { a: { level: ServiceStatusLevels.available, summary: 'a available' } }, + ]); + }); + }); + + describe('getDependenciesStatus$', () => { + it('only includes dependencies of specified plugin', async () => { + const service = new PluginsStatusService({ + core$: coreAllAvailable$, + pluginDependencies, + }); + expect(await service.getDependenciesStatus$('a').pipe(first()).toPromise()).toEqual({}); + expect(await service.getDependenciesStatus$('b').pipe(first()).toPromise()).toEqual({ + a: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' }, + }); + expect(await service.getDependenciesStatus$('c').pipe(first()).toPromise()).toEqual({ + a: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' }, + b: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' }, + }); + }); + + it('uses the manually set status level if plugin specifies one', async () => { + const service = new PluginsStatusService({ core$: coreOneDegraded$, pluginDependencies }); + service.set('a', of({ level: ServiceStatusLevels.available, summary: 'a status' })); + + expect(await service.getDependenciesStatus$('c').pipe(first()).toPromise()).toEqual({ + a: { level: ServiceStatusLevels.available, summary: 'a status' }, // a is available depsite savedObjects being degraded + b: { + level: ServiceStatusLevels.degraded, + summary: '[savedObjects]: savedObjects degraded', + detail: 'See the status page for more information', + meta: expect.any(Object), + }, + }); + }); + + it('throws error if unknown plugin passed', () => { + const service = new PluginsStatusService({ core$: coreAllAvailable$, pluginDependencies }); + expect(() => { + service.getDependenciesStatus$('dont-exist'); + }).toThrowError(); + }); + + it('debounces events in quick succession', async () => { + const service = new PluginsStatusService({ + core$: coreAllAvailable$, + pluginDependencies, + }); + const available: ServiceStatus = { + level: ServiceStatusLevels.available, + summary: 'a available', + }; + const degraded: ServiceStatus = { + level: ServiceStatusLevels.degraded, + summary: 'a degraded', + }; + const pluginA$ = new BehaviorSubject(available); + service.set('a', pluginA$); + + const statusUpdates: Array> = []; + const subscription = service + .getDependenciesStatus$('b') + .subscribe((status) => statusUpdates.push(status)); + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + + pluginA$.next(degraded); + pluginA$.next(available); + pluginA$.next(degraded); + pluginA$.next(available); + pluginA$.next(degraded); + pluginA$.next(available); + pluginA$.next(degraded); + // Waiting for the debounce timeout should cut a new update + await delay(100); + pluginA$.next(available); + await delay(100); + subscription.unsubscribe(); + + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "a": Object { + "level": degraded, + "summary": "a degraded", + }, + }, + Object { + "a": Object { + "level": available, + "summary": "a available", + }, + }, + ] + `); + }); + }); +}); diff --git a/src/core/server/status/plugins_status.ts b/src/core/server/status/plugins_status.ts new file mode 100644 index 0000000000000..df6f13eeec4e5 --- /dev/null +++ b/src/core/server/status/plugins_status.ts @@ -0,0 +1,98 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BehaviorSubject, Observable, combineLatest, of } from 'rxjs'; +import { map, distinctUntilChanged, switchMap, debounceTime } from 'rxjs/operators'; +import { isDeepStrictEqual } from 'util'; + +import { PluginName } from '../plugins'; +import { ServiceStatus, CoreStatus } from './types'; +import { getSummaryStatus } from './get_summary_status'; + +interface Deps { + core$: Observable; + pluginDependencies: ReadonlyMap; +} + +export class PluginsStatusService { + private readonly pluginStatuses = new Map>(); + private readonly update$ = new BehaviorSubject(true); + constructor(private readonly deps: Deps) {} + + public set(plugin: PluginName, status$: Observable) { + this.pluginStatuses.set(plugin, status$); + this.update$.next(true); // trigger all existing Observables to update from the new source Observable + } + + public getAll$(): Observable> { + return this.getPluginStatuses$([...this.deps.pluginDependencies.keys()]); + } + + public getDependenciesStatus$(plugin: PluginName): Observable> { + const dependencies = this.deps.pluginDependencies.get(plugin); + if (!dependencies) { + throw new Error(`Unknown plugin: ${plugin}`); + } + + return this.getPluginStatuses$(dependencies).pipe( + // Prevent many emissions at once from dependency status resolution from making this too noisy + debounceTime(100) + ); + } + + public getDerivedStatus$(plugin: PluginName): Observable { + return combineLatest([this.deps.core$, this.getDependenciesStatus$(plugin)]).pipe( + map(([coreStatus, pluginStatuses]) => { + return getSummaryStatus( + [...Object.entries(coreStatus), ...Object.entries(pluginStatuses)], + { + allAvailableSummary: `All dependencies are available`, + } + ); + }) + ); + } + + private getPluginStatuses$(plugins: PluginName[]): Observable> { + if (plugins.length === 0) { + return of({}); + } + + return this.update$.pipe( + switchMap(() => { + const pluginStatuses = plugins + .map( + (depName) => + [depName, this.pluginStatuses.get(depName) ?? this.getDerivedStatus$(depName)] as [ + PluginName, + Observable + ] + ) + .map(([pName, status$]) => + status$.pipe(map((status) => [pName, status] as [PluginName, ServiceStatus])) + ); + + return combineLatest(pluginStatuses).pipe( + map((statuses) => Object.fromEntries(statuses)), + distinctUntilChanged(isDeepStrictEqual) + ); + }) + ); + } +} diff --git a/src/core/server/status/status_service.mock.ts b/src/core/server/status/status_service.mock.ts index 47ef8659b4079..42b3eecdca310 100644 --- a/src/core/server/status/status_service.mock.ts +++ b/src/core/server/status/status_service.mock.ts @@ -40,6 +40,9 @@ const createSetupContractMock = () => { const setupContract: jest.Mocked = { core$: new BehaviorSubject(availableCoreStatus), overall$: new BehaviorSubject(available), + set: jest.fn(), + dependencies$: new BehaviorSubject({}), + derivedStatus$: new BehaviorSubject(available), }; return setupContract; @@ -50,6 +53,11 @@ const createInternalSetupContractMock = () => { core$: new BehaviorSubject(availableCoreStatus), overall$: new BehaviorSubject(available), isStatusPageAnonymous: jest.fn().mockReturnValue(false), + plugins: { + set: jest.fn(), + getDependenciesStatus$: jest.fn(), + getDerivedStatus$: jest.fn(), + }, }; return setupContract; diff --git a/src/core/server/status/status_service.test.ts b/src/core/server/status/status_service.test.ts index 863fe34e8ecea..341c40a86bf77 100644 --- a/src/core/server/status/status_service.test.ts +++ b/src/core/server/status/status_service.test.ts @@ -34,6 +34,7 @@ describe('StatusService', () => { service = new StatusService(mockCoreContext.create()); }); + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const available: ServiceStatus = { level: ServiceStatusLevels.available, summary: 'Available', @@ -53,6 +54,7 @@ describe('StatusService', () => { savedObjects: { status$: of(degraded), }, + pluginDependencies: new Map(), }); expect(await setup.core$.pipe(first()).toPromise()).toEqual({ elasticsearch: available, @@ -68,6 +70,7 @@ describe('StatusService', () => { savedObjects: { status$: of(degraded), }, + pluginDependencies: new Map(), }); const subResult1 = await setup.core$.pipe(first()).toPromise(); const subResult2 = await setup.core$.pipe(first()).toPromise(); @@ -96,6 +99,7 @@ describe('StatusService', () => { savedObjects: { status$: savedObjects$, }, + pluginDependencies: new Map(), }); const statusUpdates: CoreStatus[] = []; @@ -158,6 +162,7 @@ describe('StatusService', () => { savedObjects: { status$: of(degraded), }, + pluginDependencies: new Map(), }); expect(await setup.overall$.pipe(first()).toPromise()).toMatchObject({ level: ServiceStatusLevels.degraded, @@ -173,6 +178,7 @@ describe('StatusService', () => { savedObjects: { status$: of(degraded), }, + pluginDependencies: new Map(), }); const subResult1 = await setup.overall$.pipe(first()).toPromise(); const subResult2 = await setup.overall$.pipe(first()).toPromise(); @@ -201,26 +207,95 @@ describe('StatusService', () => { savedObjects: { status$: savedObjects$, }, + pluginDependencies: new Map(), }); const statusUpdates: ServiceStatus[] = []; const subscription = setup.overall$.subscribe((status) => statusUpdates.push(status)); + // Wait for timers to ensure that duplicate events are still filtered out regardless of debouncing. elasticsearch$.next(available); + await delay(100); elasticsearch$.next(available); + await delay(100); elasticsearch$.next({ level: ServiceStatusLevels.available, summary: `Wow another summary`, }); + await delay(100); savedObjects$.next(degraded); + await delay(100); savedObjects$.next(available); + await delay(100); savedObjects$.next(available); + await delay(100); subscription.unsubscribe(); expect(statusUpdates).toMatchInlineSnapshot(` Array [ Object { + "detail": "See the status page for more information", "level": degraded, + "meta": Object { + "affectedServices": Object { + "savedObjects": Object { + "level": degraded, + "summary": "This is degraded!", + }, + }, + }, + "summary": "[savedObjects]: This is degraded!", + }, + Object { + "level": available, + "summary": "All services are available", + }, + ] + `); + }); + + it('debounces events in quick succession', async () => { + const savedObjects$ = new BehaviorSubject(available); + const setup = await service.setup({ + elasticsearch: { + status$: new BehaviorSubject(available), + }, + savedObjects: { + status$: savedObjects$, + }, + pluginDependencies: new Map(), + }); + + const statusUpdates: ServiceStatus[] = []; + const subscription = setup.overall$.subscribe((status) => statusUpdates.push(status)); + + // All of these should debounced into a single `available` status + savedObjects$.next(degraded); + savedObjects$.next(available); + savedObjects$.next(degraded); + savedObjects$.next(available); + savedObjects$.next(degraded); + savedObjects$.next(available); + savedObjects$.next(degraded); + // Waiting for the debounce timeout should cut a new update + await delay(100); + savedObjects$.next(available); + await delay(100); + subscription.unsubscribe(); + + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "detail": "See the status page for more information", + "level": degraded, + "meta": Object { + "affectedServices": Object { + "savedObjects": Object { + "level": degraded, + "summary": "This is degraded!", + }, + }, + }, "summary": "[savedObjects]: This is degraded!", }, Object { diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts index aea335e64babf..59e81343597c9 100644 --- a/src/core/server/status/status_service.ts +++ b/src/core/server/status/status_service.ts @@ -18,7 +18,7 @@ */ import { Observable, combineLatest } from 'rxjs'; -import { map, distinctUntilChanged, shareReplay, take } from 'rxjs/operators'; +import { map, distinctUntilChanged, shareReplay, take, debounceTime } from 'rxjs/operators'; import { isDeepStrictEqual } from 'util'; import { CoreService } from '../../types'; @@ -26,13 +26,16 @@ import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { InternalElasticsearchServiceSetup } from '../elasticsearch'; import { InternalSavedObjectsServiceSetup } from '../saved_objects'; +import { PluginName } from '../plugins'; import { config, StatusConfigType } from './status_config'; import { ServiceStatus, CoreStatus, InternalStatusServiceSetup } from './types'; import { getSummaryStatus } from './get_summary_status'; +import { PluginsStatusService } from './plugins_status'; interface SetupDeps { elasticsearch: Pick; + pluginDependencies: ReadonlyMap; savedObjects: Pick; } @@ -40,17 +43,29 @@ export class StatusService implements CoreService { private readonly logger: Logger; private readonly config$: Observable; + private pluginsStatus?: PluginsStatusService; + constructor(coreContext: CoreContext) { this.logger = coreContext.logger.get('status'); this.config$ = coreContext.configService.atPath(config.path); } - public async setup(core: SetupDeps) { + public async setup({ elasticsearch, pluginDependencies, savedObjects }: SetupDeps) { const statusConfig = await this.config$.pipe(take(1)).toPromise(); - const core$ = this.setupCoreStatus(core); - const overall$: Observable = core$.pipe( - map((coreStatus) => { - const summary = getSummaryStatus(Object.entries(coreStatus)); + const core$ = this.setupCoreStatus({ elasticsearch, savedObjects }); + this.pluginsStatus = new PluginsStatusService({ core$, pluginDependencies }); + + const overall$: Observable = combineLatest( + core$, + this.pluginsStatus.getAll$() + ).pipe( + // Prevent many emissions at once from dependency status resolution from making this too noisy + debounceTime(100), + map(([coreStatus, pluginsStatus]) => { + const summary = getSummaryStatus([ + ...Object.entries(coreStatus), + ...Object.entries(pluginsStatus), + ]); this.logger.debug(`Recalculated overall status`, { status: summary }); return summary; }), @@ -60,6 +75,11 @@ export class StatusService implements CoreService { return { core$, overall$, + plugins: { + set: this.pluginsStatus.set.bind(this.pluginsStatus), + getDependenciesStatus$: this.pluginsStatus.getDependenciesStatus$.bind(this.pluginsStatus), + getDerivedStatus$: this.pluginsStatus.getDerivedStatus$.bind(this.pluginsStatus), + }, isStatusPageAnonymous: () => statusConfig.allowAnonymous, }; } @@ -68,7 +88,10 @@ export class StatusService implements CoreService { public stop() {} - private setupCoreStatus({ elasticsearch, savedObjects }: SetupDeps): Observable { + private setupCoreStatus({ + elasticsearch, + savedObjects, + }: Pick): Observable { return combineLatest([elasticsearch.status$, savedObjects.status$]).pipe( map(([elasticsearchStatus, savedObjectsStatus]) => ({ elasticsearch: elasticsearchStatus, diff --git a/src/core/server/status/types.ts b/src/core/server/status/types.ts index 2ecf11deb2960..f884b80316fa8 100644 --- a/src/core/server/status/types.ts +++ b/src/core/server/status/types.ts @@ -19,6 +19,7 @@ import { Observable } from 'rxjs'; import { deepFreeze } from '../../utils'; +import { PluginName } from '../plugins'; /** * The current status of a service at a point in time. @@ -116,6 +117,60 @@ export interface CoreStatus { /** * API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. + * + * @remarks + * By default, a plugin inherits it's current status from the most severe status level of any Core services and any + * plugins that it depends on. This default status is available on the + * {@link ServiceStatusSetup.derivedStatus$ | core.status.derviedStatus$} API. + * + * Plugins may customize their status calculation by calling the {@link ServiceStatusSetup.set | core.status.set} API + * with an Observable. Within this Observable, a plugin may choose to only depend on the status of some of its + * dependencies, to ignore severe status levels of particular Core services they are not concerned with, or to make its + * status dependent on other external services. + * + * @example + * Customize a plugin's status to only depend on the status of SavedObjects: + * ```ts + * core.status.set( + * core.status.core$.pipe( + * . map((coreStatus) => { + * return coreStatus.savedObjects; + * }) ; + * ); + * ); + * ``` + * + * @example + * Customize a plugin's status to include an external service: + * ```ts + * const externalStatus$ = interval(1000).pipe( + * switchMap(async () => { + * const resp = await fetch(`https://myexternaldep.com/_healthz`); + * const body = await resp.json(); + * if (body.ok) { + * return of({ level: ServiceStatusLevels.available, summary: 'External Service is up'}); + * } else { + * return of({ level: ServiceStatusLevels.available, summary: 'External Service is unavailable'}); + * } + * }), + * catchError((error) => { + * of({ level: ServiceStatusLevels.unavailable, summary: `External Service is down`, meta: { error }}) + * }) + * ); + * + * core.status.set( + * combineLatest([core.status.derivedStatus$, externalStatus$]).pipe( + * map(([derivedStatus, externalStatus]) => { + * if (externalStatus.level > derivedStatus) { + * return externalStatus; + * } else { + * return derivedStatus; + * } + * }) + * ) + * ); + * ``` + * * @public */ export interface StatusServiceSetup { @@ -134,9 +189,43 @@ export interface StatusServiceSetup { * only depend on the statuses of {@link StatusServiceSetup.core$ | Core} or their dependencies. */ overall$: Observable; + + /** + * Allows a plugin to specify a custom status dependent on its own criteria. + * Completely overrides the default inherited status. + * + * @remarks + * See the {@link StatusServiceSetup.derivedStatus$} API for leveraging the default status + * calculation that is provided by Core. + */ + set(status$: Observable): void; + + /** + * Current status for all plugins this plugin depends on. + * Each key of the `Record` is a plugin id. + */ + dependencies$: Observable>; + + /** + * The status of this plugin as derived from its dependencies. + * + * @remarks + * By default, plugins inherit this derived status from their dependencies. + * Calling {@link StatusSetup.set} overrides this default status. + * + * This may emit multliple times for a single status change event as propagates + * through the dependency tree + */ + derivedStatus$: Observable; } /** @internal */ -export interface InternalStatusServiceSetup extends StatusServiceSetup { +export interface InternalStatusServiceSetup extends Pick { isStatusPageAnonymous: () => boolean; + // Namespaced under `plugins` key to improve clarity that these are APIs for plugins specifically. + plugins: { + set(plugin: PluginName, status$: Observable): void; + getDependenciesStatus$(plugin: PluginName): Observable>; + getDerivedStatus$(plugin: PluginName): Observable; + }; } diff --git a/src/core/server/ui_settings/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts index b4cfc3c1efe8b..04979b69b32b9 100644 --- a/src/core/server/ui_settings/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -73,9 +73,9 @@ export function getServices() { httpServerMock.createKibanaRequest() ); - const uiSettings = kbnServer.server.uiSettingsServiceFactory({ - savedObjectsClient, - }); + const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient( + savedObjectsClient + ); services = { kbnServer, diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 74e1ec5e2b4ed..486c8563c5456 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -59,7 +59,6 @@ export default { '@elastic/eui/lib/(.*)?': '/node_modules/@elastic/eui/test-env/$1', '^src/plugins/(.*)': '/src/plugins/$1', '^plugins/([^/.]*)(.*)': '/src/legacy/core_plugins/$1/public$2', - '^ui/(.*)': '/src/legacy/ui/public/$1', '^uiExports/(.*)': '/src/dev/jest/mocks/file_mock.js', '^test_utils/(.*)': '/src/test_utils/public/$1', '^fixtures/(.*)': '/src/fixtures/$1', diff --git a/src/dev/jest/setup/mocks.js b/src/dev/jest/setup/mocks.js index 6e7160e858cd7..cea28d8abdbd1 100644 --- a/src/dev/jest/setup/mocks.js +++ b/src/dev/jest/setup/mocks.js @@ -34,10 +34,6 @@ * The mocks that are enabled that way live inside the `__mocks__` folders beside their implementation files. */ -jest.mock('ui/metadata'); -jest.mock('ui/documentation_links/documentation_links'); -jest.mock('ui/chrome'); - jest.mock('moment-timezone', () => { // We always want to mock the timezone moment-timezone guesses, since otherwise // test results might be depending on which time zone you are running them. diff --git a/src/fixtures/stubbed_logstash_index_pattern.js b/src/fixtures/stubbed_logstash_index_pattern.js index 5bb926799fcf6..5735b01eb3db4 100644 --- a/src/fixtures/stubbed_logstash_index_pattern.js +++ b/src/fixtures/stubbed_logstash_index_pattern.js @@ -21,7 +21,12 @@ import StubIndexPattern from 'test_utils/stub_index_pattern'; import stubbedLogstashFields from 'fixtures/logstash_fields'; import { getKbnFieldType } from '../plugins/data/common'; -import { npSetup } from '../legacy/ui/public/new_platform/new_platform.karma_mock'; +import { uiSettingsServiceMock } from '../core/public/ui_settings/ui_settings_service.mock'; + +const uiSettingSetupMock = uiSettingsServiceMock.createSetupContract(); +uiSettingSetupMock.get.mockImplementation((item, defaultValue) => { + return defaultValue; +}); export default function stubbedLogstashIndexPatternService() { const mockLogstashFields = stubbedLogstashFields(); @@ -41,13 +46,9 @@ export default function stubbedLogstashIndexPatternService() { }; }); - const indexPattern = new StubIndexPattern( - 'logstash-*', - (cfg) => cfg, - 'time', - fields, - npSetup.core - ); + const indexPattern = new StubIndexPattern('logstash-*', (cfg) => cfg, 'time', fields, { + uiSettings: uiSettingSetupMock, + }); indexPattern.id = 'logstash-*'; indexPattern.isTimeNanosBased = () => false; diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts deleted file mode 100644 index 3422d6a8cbb34..0000000000000 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from 'hapi'; -import KbnServer from '../kbn_server'; - -import { capabilitiesMixin } from './capabilities_mixin'; - -describe('capabilitiesMixin', () => { - let registerMock: jest.Mock; - - const getKbnServer = (pluginSpecs: any[] = []) => { - return ({ - afterPluginsInit: (callback: () => void) => callback(), - pluginSpecs, - newPlatform: { - setup: { - core: { - capabilities: { - registerProvider: registerMock, - }, - }, - }, - }, - } as unknown) as KbnServer; - }; - - let server: Server; - beforeEach(() => { - server = new Server(); - server.getUiNavLinks = () => []; - registerMock = jest.fn(); - }); - - it('calls capabilities#registerCapabilitiesProvider for each legacy plugin specs', async () => { - const getPluginSpec = (provider: () => any) => ({ - getUiCapabilitiesProvider: () => provider, - }); - - const capaA = { catalogue: { A: true } }; - const capaB = { catalogue: { B: true } }; - const kbnServer = getKbnServer([getPluginSpec(() => capaA), getPluginSpec(() => capaB)]); - await capabilitiesMixin(kbnServer, server); - - expect(registerMock).toHaveBeenCalledTimes(2); - expect(registerMock.mock.calls[0][0]()).toEqual(capaA); - expect(registerMock.mock.calls[1][0]()).toEqual(capaB); - }); -}); diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts deleted file mode 100644 index 1f8c869f17f66..0000000000000 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from 'hapi'; -import KbnServer from '../kbn_server'; - -export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { - const registerLegacyCapabilities = async () => { - const capabilitiesList = await Promise.all( - kbnServer.pluginSpecs - .map((spec) => spec.getUiCapabilitiesProvider()) - .filter((provider) => !!provider) - .map((provider) => provider(server)) - ); - - capabilitiesList.forEach((capabilities) => { - kbnServer.newPlatform.setup.core.capabilities.registerProvider(() => capabilities); - }); - }; - - // Some plugin capabilities are derived from data provided by other plugins, - // so we need to wait until after all plugins have been init'd to fetch uiCapabilities. - kbnServer.afterPluginsInit(async () => { - await registerLegacyCapabilities(); - }); -} diff --git a/src/legacy/server/capabilities/index.ts b/src/legacy/server/capabilities/index.ts deleted file mode 100644 index 8c5dea1226f2b..0000000000000 --- a/src/legacy/server/capabilities/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { capabilitiesMixin } from './capabilities_mixin'; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 9bb091383ab13..1a1f43b93f26e 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -45,7 +45,6 @@ import { LegacyConfig, ILegacyService, ILegacyInternals } from '../../core/serve import { UiPlugins } from '../../core/server/plugins'; import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch'; import { UsageCollectionSetup } from '../../plugins/usage_collection/server'; -import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; import { HomeServerPluginSetup } from '../../plugins/home/server'; // lot of legacy code was assuming this type only had these two methods @@ -78,7 +77,6 @@ declare module 'hapi' { name: string, factoryFn: (request: Request) => Record ) => void; - uiSettingsServiceFactory: (options?: UiSettingsServiceFactoryOptions) => IUiSettingsClient; logWithMetadata: (tags: string[], message: string, meta: Record) => void; newPlatform: KbnServer['newPlatform']; } diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index 1084521235ea0..320086b6d6531 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -34,7 +34,6 @@ import configCompleteMixin from './config/complete'; import { optimizeMixin } from '../../optimize'; import * as Plugins from './plugins'; import { savedObjectsMixin } from './saved_objects/saved_objects_mixin'; -import { capabilitiesMixin } from './capabilities'; import { serverExtensionsMixin } from './server_extensions'; import { uiMixin } from '../ui'; import { i18nMixin } from './i18n'; @@ -115,9 +114,6 @@ export default class KbnServer { // setup saved object routes savedObjectsMixin, - // setup capabilities routes - capabilitiesMixin, - // setup routes that serve the @kbn/optimizer output optimizeMixin, diff --git a/src/legacy/ui/public/.eslintrc b/src/legacy/ui/public/.eslintrc deleted file mode 100644 index cc44af915ba25..0000000000000 --- a/src/legacy/ui/public/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -rules: - no-console: 2 - 'import/no-default-export': error diff --git a/src/legacy/ui/public/__tests__/events.js b/src/legacy/ui/public/__tests__/events.js deleted file mode 100644 index c225c2a8ac1c0..0000000000000 --- a/src/legacy/ui/public/__tests__/events.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { EventsProvider } from '../events'; -import expect from '@kbn/expect'; -import '../private'; -import { createDefer } from 'ui/promises'; -import { createLegacyClass } from '../utils/legacy_class'; - -describe('Events', function () { - require('test_utils/no_digest_promises').activateForSuite(); - - let Events; - let Promise; - let eventsInstance; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function ($injector, Private) { - Promise = $injector.get('Promise'); - Events = Private(EventsProvider); - eventsInstance = new Events(); - }) - ); - - it('should handle on events', function () { - const obj = new Events(); - const prom = obj.on('test', function (message) { - expect(message).to.equal('Hello World'); - }); - - obj.emit('test', 'Hello World'); - - return prom; - }); - - it('should work with inherited objects', function () { - createLegacyClass(MyEventedObject).inherits(Events); - function MyEventedObject() { - MyEventedObject.Super.call(this); - } - const obj = new MyEventedObject(); - - const prom = obj.on('test', function (message) { - expect(message).to.equal('Hello World'); - }); - - obj.emit('test', 'Hello World'); - - return prom; - }); - - it('should clear events when off is called', function () { - const obj = new Events(); - obj.on('test', _.noop); - expect(obj._listeners).to.have.property('test'); - expect(obj._listeners.test).to.have.length(1); - obj.off(); - expect(obj._listeners).to.not.have.property('test'); - }); - - it('should clear a specific handler when off is called for an event', function () { - const obj = new Events(); - const handler1 = sinon.stub(); - const handler2 = sinon.stub(); - obj.on('test', handler1); - obj.on('test', handler2); - expect(obj._listeners).to.have.property('test'); - obj.off('test', handler1); - - return obj.emit('test', 'Hello World').then(function () { - sinon.assert.calledOnce(handler2); - sinon.assert.notCalled(handler1); - }); - }); - - it('should clear a all handlers when off is called for an event', function () { - const obj = new Events(); - const handler1 = sinon.stub(); - obj.on('test', handler1); - expect(obj._listeners).to.have.property('test'); - obj.off('test'); - expect(obj._listeners).to.not.have.property('test'); - - return obj.emit('test', 'Hello World').then(function () { - sinon.assert.notCalled(handler1); - }); - }); - - it('should handle multiple identical emits in the same tick', function () { - const obj = new Events(); - const handler1 = sinon.stub(); - - obj.on('test', handler1); - const emits = [obj.emit('test', 'one'), obj.emit('test', 'two'), obj.emit('test', 'three')]; - - return Promise.all(emits).then(function () { - expect(handler1.callCount).to.be(emits.length); - expect(handler1.getCall(0).calledWith('one')).to.be(true); - expect(handler1.getCall(1).calledWith('two')).to.be(true); - expect(handler1.getCall(2).calledWith('three')).to.be(true); - }); - }); - - it('should handle emits from the handler', function () { - const obj = new Events(); - const secondEmit = createDefer(Promise); - - const handler1 = sinon.spy(function () { - if (handler1.calledTwice) { - return; - } - obj.emit('test').then(_.bindKey(secondEmit, 'resolve')); - }); - - obj.on('test', handler1); - - return Promise.all([obj.emit('test'), secondEmit.promise]).then(function () { - expect(handler1.callCount).to.be(2); - }); - }); - - it('should only emit to handlers registered before emit is called', function () { - const obj = new Events(); - const handler1 = sinon.stub(); - const handler2 = sinon.stub(); - - obj.on('test', handler1); - const emits = [obj.emit('test', 'one'), obj.emit('test', 'two'), obj.emit('test', 'three')]; - - return Promise.all(emits).then(function () { - expect(handler1.callCount).to.be(emits.length); - - obj.on('test', handler2); - - const emits2 = [obj.emit('test', 'four'), obj.emit('test', 'five'), obj.emit('test', 'six')]; - - return Promise.all(emits2).then(function () { - expect(handler1.callCount).to.be(emits.length + emits2.length); - expect(handler2.callCount).to.be(emits2.length); - }); - }); - }); - - it('should pass multiple arguments from the emitter', function () { - const obj = new Events(); - const handler = sinon.stub(); - const payload = ['one', { hello: 'tests' }, null]; - - obj.on('test', handler); - - return obj.emit('test', payload[0], payload[1], payload[2]).then(function () { - expect(handler.callCount).to.be(1); - expect(handler.calledWithExactly(payload[0], payload[1], payload[2])).to.be(true); - }); - }); - - it('should preserve the scope of the handler', function () { - const obj = new Events(); - const expected = 'some value'; - let testValue; - - function handler() { - testValue = this.getVal(); - } - handler.getVal = _.constant(expected); - - obj.on('test', handler); - return obj.emit('test').then(function () { - expect(testValue).to.equal(expected); - }); - }); - - it('should always emit in the same order', function () { - const handler = sinon.stub(); - - const obj = new Events(); - obj.on('block', _.partial(handler, 'block')); - obj.on('last', _.partial(handler, 'last')); - - return Promise.all([ - obj.emit('block'), - obj.emit('block'), - obj.emit('block'), - obj.emit('block'), - obj.emit('block'), - obj.emit('block'), - obj.emit('block'), - obj.emit('block'), - obj.emit('block'), - obj.emit('last'), - ]).then(function () { - expect(handler.callCount).to.be(10); - handler.args.forEach(function (args, i) { - expect(args[0]).to.be(i < 9 ? 'block' : 'last'); - }); - }); - }); - - it('calls emitted handlers asynchronously', (done) => { - const listenerStub = sinon.stub(); - eventsInstance.on('test', listenerStub); - eventsInstance.emit('test'); - sinon.assert.notCalled(listenerStub); - - setTimeout(() => { - sinon.assert.calledOnce(listenerStub); - done(); - }, 100); - }); - - it('calling off after an emit that has not yet triggered the handler, will not call the handler', (done) => { - const listenerStub = sinon.stub(); - eventsInstance.on('test', listenerStub); - eventsInstance.emit('test'); - // It's called asynchronously so it shouldn't be called yet. - sinon.assert.notCalled(listenerStub); - eventsInstance.off('test', listenerStub); - - setTimeout(() => { - sinon.assert.notCalled(listenerStub); - done(); - }, 100); - }); -}); diff --git a/src/legacy/ui/public/__tests__/metadata.js b/src/legacy/ui/public/__tests__/metadata.js deleted file mode 100644 index c5051d70849cd..0000000000000 --- a/src/legacy/ui/public/__tests__/metadata.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { metadata } from '../metadata'; -describe('ui/metadata', () => { - it('is immutable', () => { - expect(() => (metadata.foo = 'something')).to.throw; - expect(() => (metadata.version = 'something')).to.throw; - expect(() => (metadata.vars = {})).to.throw; - expect(() => (metadata.vars.kbnIndex = 'something')).to.throw; - }); -}); diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss deleted file mode 100644 index a441b773d4a4e..0000000000000 --- a/src/legacy/ui/public/_index.scss +++ /dev/null @@ -1,9 +0,0 @@ -// Prefix all styles with "kbn" to avoid conflicts. -// Examples -// kbnChart -// kbnChart__legend -// kbnChart__legend--small -// kbnChart__legend-isLoading - -@import './accessibility/index'; -@import './directives/index'; diff --git a/src/legacy/ui/public/accessibility/__tests__/kbn_accessible_click.js b/src/legacy/ui/public/accessibility/__tests__/kbn_accessible_click.js deleted file mode 100644 index f3b7ab29d8a14..0000000000000 --- a/src/legacy/ui/public/accessibility/__tests__/kbn_accessible_click.js +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular from 'angular'; -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../kbn_accessible_click'; -import { keys } from '@elastic/eui'; - -describe('kbnAccessibleClick directive', () => { - let $compile; - let $rootScope; - - beforeEach(ngMock.module('kibana')); - - beforeEach( - ngMock.inject(function (_$compile_, _$rootScope_) { - $compile = _$compile_; - $rootScope = _$rootScope_; - }) - ); - - describe('throws an error', () => { - it('when the element is a button', () => { - const html = ``; - expect(() => { - $compile(html)($rootScope); - }).to.throwError(/kbnAccessibleClick doesn't need to be used on a button./); - }); - - it('when the element is a link with an href', () => { - const html = ``; - expect(() => { - $compile(html)($rootScope); - }).to.throwError( - /kbnAccessibleClick doesn't need to be used on a link if it has a href attribute./ - ); - }); - - it(`when the element doesn't have an ng-click`, () => { - const html = `
`; - expect(() => { - $compile(html)($rootScope); - }).to.throwError(/kbnAccessibleClick requires ng-click to be defined on its element./); - }); - }); - - describe(`doesn't throw an error`, () => { - it('when the element is a link without an href', () => { - const html = ``; - expect(() => { - $compile(html)($rootScope); - }).not.to.throwError(); - }); - }); - - describe('adds accessibility attributes', () => { - it('tabindex', () => { - const html = `
`; - const element = $compile(html)($rootScope); - expect(element.attr('tabindex')).to.be('0'); - }); - - it('role', () => { - const html = `
`; - const element = $compile(html)($rootScope); - expect(element.attr('role')).to.be('button'); - }); - }); - - describe(`doesn't override pre-existing accessibility attributes`, () => { - it('tabindex', () => { - const html = `
`; - const element = $compile(html)($rootScope); - expect(element.attr('tabindex')).to.be('1'); - }); - - it('role', () => { - const html = `
`; - const element = $compile(html)($rootScope); - expect(element.attr('role')).to.be('submit'); - }); - }); - - describe(`calls ng-click`, () => { - let scope; - let element; - - beforeEach(function () { - scope = $rootScope.$new(); - scope.handleClick = sinon.stub(); - const html = `
`; - element = $compile(html)(scope); - }); - - it(`on ENTER keyup`, () => { - const e = angular.element.Event('keyup'); // eslint-disable-line new-cap - e.key = keys.ENTER; - element.trigger(e); - sinon.assert.calledOnce(scope.handleClick); - }); - - it(`on SPACE keyup`, () => { - const e = angular.element.Event('keyup'); // eslint-disable-line new-cap - e.key = keys.SPACE; - element.trigger(e); - sinon.assert.calledOnce(scope.handleClick); - }); - }); -}); diff --git a/src/legacy/ui/public/accessibility/__tests__/kbn_ui_ace_keyboard_mode.js b/src/legacy/ui/public/accessibility/__tests__/kbn_ui_ace_keyboard_mode.js deleted file mode 100644 index ce1bf95bf0fb7..0000000000000 --- a/src/legacy/ui/public/accessibility/__tests__/kbn_ui_ace_keyboard_mode.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular from 'angular'; -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../kbn_ui_ace_keyboard_mode'; -import { keys } from '@elastic/eui'; - -describe('kbnUiAceKeyboardMode directive', () => { - let element; - - beforeEach(ngMock.module('kibana')); - - beforeEach( - ngMock.inject(($compile, $rootScope) => { - element = $compile(`
`)($rootScope.$new()); - }) - ); - - it('should add the hint element', () => { - expect(element.find('.kbnUiAceKeyboardHint').length).to.be(1); - }); - - describe('hint element', () => { - it('should be tabable', () => { - expect(element.find('.kbnUiAceKeyboardHint').attr('tabindex')).to.be('0'); - }); - - it('should move focus to textbox and be inactive if pressed enter on it', () => { - const textarea = element.find('textarea'); - sinon.spy(textarea[0], 'focus'); - const ev = angular.element.Event('keydown'); // eslint-disable-line new-cap - ev.key = keys.ENTER; - element.find('.kbnUiAceKeyboardHint').trigger(ev); - expect(textarea[0].focus.called).to.be(true); - expect( - element.find('.kbnUiAceKeyboardHint').hasClass('kbnUiAceKeyboardHint-isInactive') - ).to.be(true); - }); - - it('should be shown again, when pressing Escape in ace editor', () => { - const textarea = element.find('textarea'); - const hint = element.find('.kbnUiAceKeyboardHint'); - sinon.spy(hint[0], 'focus'); - const ev = angular.element.Event('keydown'); // eslint-disable-line new-cap - ev.key = keys.ESCAPE; - textarea.trigger(ev); - expect(hint[0].focus.called).to.be(true); - expect(hint.hasClass('kbnUiAceKeyboardHint-isInactive')).to.be(false); - }); - }); - - describe('ui-ace textarea', () => { - it('should not be tabable anymore', () => { - expect(element.find('textarea').attr('tabindex')).to.be('-1'); - }); - }); -}); - -describe('kbnUiAceKeyboardModeService', () => { - let element; - - beforeEach(ngMock.module('kibana')); - - beforeEach( - ngMock.inject(($compile, $rootScope, kbnUiAceKeyboardModeService) => { - const scope = $rootScope.$new(); - element = $compile(`
`)(scope); - kbnUiAceKeyboardModeService.initialize(scope, element); - }) - ); - - it('should add the hint element', () => { - expect(element.find('.kbnUiAceKeyboardHint').length).to.be(1); - }); - - describe('hint element', () => { - it('should be tabable', () => { - expect(element.find('.kbnUiAceKeyboardHint').attr('tabindex')).to.be('0'); - }); - - it('should move focus to textbox and be inactive if pressed enter on it', () => { - const textarea = element.find('textarea'); - sinon.spy(textarea[0], 'focus'); - const ev = angular.element.Event('keydown'); // eslint-disable-line new-cap - ev.key = keys.ENTER; - element.find('.kbnUiAceKeyboardHint').trigger(ev); - expect(textarea[0].focus.called).to.be(true); - expect( - element.find('.kbnUiAceKeyboardHint').hasClass('kbnUiAceKeyboardHint-isInactive') - ).to.be(true); - }); - - it('should be shown again, when pressing Escape in ace editor', () => { - const textarea = element.find('textarea'); - const hint = element.find('.kbnUiAceKeyboardHint'); - sinon.spy(hint[0], 'focus'); - const ev = angular.element.Event('keydown'); // eslint-disable-line new-cap - ev.key = keys.ESCAPE; - textarea.trigger(ev); - expect(hint[0].focus.called).to.be(true); - expect(hint.hasClass('kbnUiAceKeyboardHint-isInactive')).to.be(false); - }); - }); - - describe('ui-ace textarea', () => { - it('should not be tabable anymore', () => { - expect(element.find('textarea').attr('tabindex')).to.be('-1'); - }); - }); -}); diff --git a/src/legacy/ui/public/accessibility/__tests__/scrollto_activedescendant.js b/src/legacy/ui/public/accessibility/__tests__/scrollto_activedescendant.js deleted file mode 100644 index d5ccbf887e79b..0000000000000 --- a/src/legacy/ui/public/accessibility/__tests__/scrollto_activedescendant.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../scrollto_activedescendant'; - -describe('scrolltoActivedescendant directive', () => { - let $compile; - let $rootScope; - - beforeEach(ngMock.module('kibana')); - - beforeEach( - ngMock.inject((_$compile_, _$rootScope_) => { - $compile = _$compile_; - $rootScope = _$rootScope_; - }) - ); - - it('should call scrollIntoView on aria-activedescendant changes', () => { - const scope = $rootScope.$new(); - scope.ad = ''; - const element = $compile(`
- - -
`)(scope); - const child1 = element.find('#child1'); - const child2 = element.find('#child2'); - sinon.spy(child1[0], 'scrollIntoView'); - sinon.spy(child2[0], 'scrollIntoView'); - scope.ad = 'child1'; - scope.$digest(); - expect(child1[0].scrollIntoView.calledOnce).to.be.eql(true); - scope.ad = 'child2'; - scope.$digest(); - expect(child2[0].scrollIntoView.calledOnce).to.be.eql(true); - }); -}); diff --git a/src/legacy/ui/public/accessibility/_index.scss b/src/legacy/ui/public/accessibility/_index.scss deleted file mode 100644 index 95062449e4b13..0000000000000 --- a/src/legacy/ui/public/accessibility/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './kbn_ui_ace_keyboard_mode'; diff --git a/src/legacy/ui/public/accessibility/_kbn_ui_ace_keyboard_mode.scss b/src/legacy/ui/public/accessibility/_kbn_ui_ace_keyboard_mode.scss deleted file mode 100644 index 9ace600db4197..0000000000000 --- a/src/legacy/ui/public/accessibility/_kbn_ui_ace_keyboard_mode.scss +++ /dev/null @@ -1,24 +0,0 @@ -.kbnUiAceKeyboardHint { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - background: transparentize($euiColorEmptyShade, 0.3); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - opacity: 0; - - &:focus { - opacity: 1; - border: 2px solid $euiColorPrimary; - z-index: 1000; - } - - &.kbnUiAceKeyboardHint-isInactive { - display: none; - } -} diff --git a/src/legacy/ui/public/accessibility/angular_aria.js b/src/legacy/ui/public/accessibility/angular_aria.js deleted file mode 100644 index 4335eddf0d8cd..0000000000000 --- a/src/legacy/ui/public/accessibility/angular_aria.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'angular-aria'; -import { uiModules } from '../modules'; - -/** - * This module will take care of attaching appropriate aria tags related to some angular stuff, - * e.g. it will attach aria-invalid if the model state is set to invalid. - * - * You can find more infos in the official documentation: https://docs.angularjs.org/api/ngAria. - * - * Three settings are disabled: it won't automatically attach `tabindex`, `role=button` or - * handling keyboard events for `ngClick` directives. Kibana uses `kbnAccessibleClick` to handle - * those cases where you need an `ngClick` non button element to have keyboard access. - */ -uiModules.get('kibana', ['ngAria']).config(($ariaProvider) => { - $ariaProvider.config({ - bindKeydown: false, - bindRoleForClick: false, - tabindex: false, - }); -}); diff --git a/src/legacy/ui/public/accessibility/index.js b/src/legacy/ui/public/accessibility/index.js deleted file mode 100644 index 5ff2521421866..0000000000000 --- a/src/legacy/ui/public/accessibility/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import './angular_aria'; -import './kbn_accessible_click'; -import './scrollto_activedescendant'; diff --git a/src/legacy/ui/public/accessibility/kbn_accessible_click.js b/src/legacy/ui/public/accessibility/kbn_accessible_click.js deleted file mode 100644 index a57fbc6be82fd..0000000000000 --- a/src/legacy/ui/public/accessibility/kbn_accessible_click.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Interactive elements must be able to receive focus. - * - * Ideally, this means using elements that are natively keyboard accessible (, - * , or - - -`; diff --git a/src/legacy/ui/public/exit_full_screen/_index.scss b/src/legacy/ui/public/exit_full_screen/_index.scss deleted file mode 100644 index 33dff05e2a687..0000000000000 --- a/src/legacy/ui/public/exit_full_screen/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import '../../../../plugins/kibana_react/public/exit_full_screen_button/index'; diff --git a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.test.js b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.test.js deleted file mode 100644 index d4273c0fdb207..0000000000000 --- a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.test.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -jest.mock( - 'ui/chrome', - () => ({ - getKibanaVersion: () => '6.0.0', - setVisible: () => {}, - }), - { virtual: true } -); - -import React from 'react'; -import { mountWithIntl, renderWithIntl } from 'test_utils/enzyme_helpers'; -import sinon from 'sinon'; -import chrome from 'ui/chrome'; - -import { ExitFullScreenButton } from './exit_full_screen_button'; - -import { keys } from '@elastic/eui'; - -test('is rendered', () => { - const component = renderWithIntl( {}} />); - - expect(component).toMatchSnapshot(); -}); - -describe('onExitFullScreenMode', () => { - test('is called when the button is pressed', () => { - const onExitHandler = sinon.stub(); - - const component = mountWithIntl(); - - component.find('button').simulate('click'); - - sinon.assert.calledOnce(onExitHandler); - }); - - test('is called when the ESC key is pressed', () => { - const onExitHandler = sinon.stub(); - - mountWithIntl(); - - const escapeKeyEvent = new KeyboardEvent('keydown', { key: keys.ESCAPE }); - document.dispatchEvent(escapeKeyEvent); - - sinon.assert.calledOnce(onExitHandler); - }); -}); - -describe('chrome.setVisible', () => { - test('is called with false when the component is rendered', () => { - chrome.setVisible = sinon.stub(); - - const component = mountWithIntl( {}} />); - - component.find('button').simulate('click'); - - sinon.assert.calledOnce(chrome.setVisible); - sinon.assert.calledWith(chrome.setVisible, false); - }); - - test('is called with true the component is unmounted', () => { - const component = mountWithIntl( {}} />); - - chrome.setVisible = sinon.stub(); - component.unmount(); - - sinon.assert.calledOnce(chrome.setVisible); - sinon.assert.calledWith(chrome.setVisible, true); - }); -}); diff --git a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx deleted file mode 100644 index db4101010f6d6..0000000000000 --- a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { PureComponent } from 'react'; -import chrome from 'ui/chrome'; -import { ExitFullScreenButton as ExitFullScreenButtonUi } from '../../../../plugins/kibana_react/public'; - -/** - * DO NOT USE THIS COMPONENT, IT IS DEPRECATED. - * Use the one in `src/plugins/kibana_react`. - */ - -interface Props { - onExitFullScreenMode: () => void; -} - -export class ExitFullScreenButton extends PureComponent { - public UNSAFE_componentWillMount() { - chrome.setVisible(false); - } - - public componentWillUnmount() { - chrome.setVisible(true); - } - - public render() { - return ; - } -} diff --git a/src/legacy/ui/public/exit_full_screen/index.ts b/src/legacy/ui/public/exit_full_screen/index.ts deleted file mode 100644 index a965fd776e0c2..0000000000000 --- a/src/legacy/ui/public/exit_full_screen/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { ExitFullScreenButton } from './exit_full_screen_button'; diff --git a/src/legacy/ui/public/flot-charts/API.md b/src/legacy/ui/public/flot-charts/API.md deleted file mode 100644 index 699e2500f4942..0000000000000 --- a/src/legacy/ui/public/flot-charts/API.md +++ /dev/null @@ -1,1498 +0,0 @@ -# Flot Reference # - -**Table of Contents** - -[Introduction](#introduction) -| [Data Format](#data-format) -| [Plot Options](#plot-options) -| [Customizing the legend](#customizing-the-legend) -| [Customizing the axes](#customizing-the-axes) -| [Multiple axes](#multiple-axes) -| [Time series data](#time-series-data) -| [Customizing the data series](#customizing-the-data-series) -| [Customizing the grid](#customizing-the-grid) -| [Specifying gradients](#specifying-gradients) -| [Plot Methods](#plot-methods) -| [Hooks](#hooks) -| [Plugins](#plugins) -| [Version number](#version-number) - ---- - -## Introduction ## - -Consider a call to the plot function: - -```js -var plot = $.plot(placeholder, data, options) -``` - -The placeholder is a jQuery object or DOM element or jQuery expression -that the plot will be put into. This placeholder needs to have its -width and height set as explained in the [README](README.md) (go read that now if -you haven't, it's short). The plot will modify some properties of the -placeholder so it's recommended you simply pass in a div that you -don't use for anything else. Make sure you check any fancy styling -you apply to the div, e.g. background images have been reported to be a -problem on IE 7. - -The plot function can also be used as a jQuery chainable property. This form -naturally can't return the plot object directly, but you can still access it -via the 'plot' data key, like this: - -```js -var plot = $("#placeholder").plot(data, options).data("plot"); -``` - -The format of the data is documented below, as is the available -options. The plot object returned from the call has some methods you -can call. These are documented separately below. - -Note that in general Flot gives no guarantees if you change any of the -objects you pass in to the plot function or get out of it since -they're not necessarily deep-copied. - - -## Data Format ## - -The data is an array of data series: - -```js -[ series1, series2, ... ] -``` - -A series can either be raw data or an object with properties. The raw -data format is an array of points: - -```js -[ [x1, y1], [x2, y2], ... ] -``` - -E.g. - -```js -[ [1, 3], [2, 14.01], [3.5, 3.14] ] -``` - -Note that to simplify the internal logic in Flot both the x and y -values must be numbers (even if specifying time series, see below for -how to do this). This is a common problem because you might retrieve -data from the database and serialize them directly to JSON without -noticing the wrong type. If you're getting mysterious errors, double -check that you're inputting numbers and not strings. - -If a null is specified as a point or if one of the coordinates is null -or couldn't be converted to a number, the point is ignored when -drawing. As a special case, a null value for lines is interpreted as a -line segment end, i.e. the points before and after the null value are -not connected. - -Lines and points take two coordinates. For filled lines and bars, you -can specify a third coordinate which is the bottom of the filled -area/bar (defaults to 0). - -The format of a single series object is as follows: - -```js -{ - color: color or number - data: rawdata - label: string - lines: specific lines options - bars: specific bars options - points: specific points options - xaxis: number - yaxis: number - clickable: boolean - hoverable: boolean - shadowSize: number - highlightColor: color or number -} -``` - -You don't have to specify any of them except the data, the rest are -options that will get default values. Typically you'd only specify -label and data, like this: - -```js -{ - label: "y = 3", - data: [[0, 3], [10, 3]] -} -``` - -The label is used for the legend, if you don't specify one, the series -will not show up in the legend. - -If you don't specify color, the series will get a color from the -auto-generated colors. The color is either a CSS color specification -(like "rgb(255, 100, 123)") or an integer that specifies which of -auto-generated colors to select, e.g. 0 will get color no. 0, etc. - -The latter is mostly useful if you let the user add and remove series, -in which case you can hard-code the color index to prevent the colors -from jumping around between the series. - -The "xaxis" and "yaxis" options specify which axis to use. The axes -are numbered from 1 (default), so { yaxis: 2} means that the series -should be plotted against the second y axis. - -"clickable" and "hoverable" can be set to false to disable -interactivity for specific series if interactivity is turned on in -the plot, see below. - -The rest of the options are all documented below as they are the same -as the default options passed in via the options parameter in the plot -command. When you specify them for a specific data series, they will -override the default options for the plot for that data series. - -Here's a complete example of a simple data specification: - -```js -[ { label: "Foo", data: [ [10, 1], [17, -14], [30, 5] ] }, - { label: "Bar", data: [ [11, 13], [19, 11], [30, -7] ] } -] -``` - - -## Plot Options ## - -All options are completely optional. They are documented individually -below, to change them you just specify them in an object, e.g. - -```js -var options = { - series: { - lines: { show: true }, - points: { show: true } - } -}; - -$.plot(placeholder, data, options); -``` - - -## Customizing the legend ## - -```js -legend: { - show: boolean - labelFormatter: null or (fn: string, series object -> string) - labelBoxBorderColor: color - noColumns: number - position: "ne" or "nw" or "se" or "sw" - margin: number of pixels or [x margin, y margin] - backgroundColor: null or color - backgroundOpacity: number between 0 and 1 - container: null or jQuery object/DOM element/jQuery expression - sorted: null/false, true, "ascending", "descending", "reverse", or a comparator -} -``` - -The legend is generated as a table with the data series labels and -small label boxes with the color of the series. If you want to format -the labels in some way, e.g. make them to links, you can pass in a -function for "labelFormatter". Here's an example that makes them -clickable: - -```js -labelFormatter: function(label, series) { - // series is the series object for the label - return '' + label + ''; -} -``` - -To prevent a series from showing up in the legend, simply have the function -return null. - -"noColumns" is the number of columns to divide the legend table into. -"position" specifies the overall placement of the legend within the -plot (top-right, top-left, etc.) and margin the distance to the plot -edge (this can be either a number or an array of two numbers like [x, -y]). "backgroundColor" and "backgroundOpacity" specifies the -background. The default is a partly transparent auto-detected -background. - -If you want the legend to appear somewhere else in the DOM, you can -specify "container" as a jQuery object/expression to put the legend -table into. The "position" and "margin" etc. options will then be -ignored. Note that Flot will overwrite the contents of the container. - -Legend entries appear in the same order as their series by default. If "sorted" -is "reverse" then they appear in the opposite order from their series. To sort -them alphabetically, you can specify true, "ascending" or "descending", where -true and "ascending" are equivalent. - -You can also provide your own comparator function that accepts two -objects with "label" and "color" properties, and returns zero if they -are equal, a positive value if the first is greater than the second, -and a negative value if the first is less than the second. - -```js -sorted: function(a, b) { - // sort alphabetically in ascending order - return a.label == b.label ? 0 : ( - a.label > b.label ? 1 : -1 - ) -} -``` - - -## Customizing the axes ## - -```js -xaxis, yaxis: { - show: null or true/false - position: "bottom" or "top" or "left" or "right" - mode: null or "time" ("time" requires jquery.flot.time.js plugin) - timezone: null, "browser" or timezone (only makes sense for mode: "time") - - color: null or color spec - tickColor: null or color spec - font: null or font spec object - - min: null or number - max: null or number - autoscaleMargin: null or number - - transform: null or fn: number -> number - inverseTransform: null or fn: number -> number - - ticks: null or number or ticks array or (fn: axis -> ticks array) - tickSize: number or array - minTickSize: number or array - tickFormatter: (fn: number, object -> string) or string - tickDecimals: null or number - - labelWidth: null or number - labelHeight: null or number - reserveSpace: null or true - - tickLength: null or number - - alignTicksWithAxis: null or number -} -``` - -All axes have the same kind of options. The following describes how to -configure one axis, see below for what to do if you've got more than -one x axis or y axis. - -If you don't set the "show" option (i.e. it is null), visibility is -auto-detected, i.e. the axis will show up if there's data associated -with it. You can override this by setting the "show" option to true or -false. - -The "position" option specifies where the axis is placed, bottom or -top for x axes, left or right for y axes. The "mode" option determines -how the data is interpreted, the default of null means as decimal -numbers. Use "time" for time series data; see the time series data -section. The time plugin (jquery.flot.time.js) is required for time -series support. - -The "color" option determines the color of the line and ticks for the axis, and -defaults to the grid color with transparency. For more fine-grained control you -can also set the color of the ticks separately with "tickColor". - -You can customize the font and color used to draw the axis tick labels with CSS -or directly via the "font" option. When "font" is null - the default - each -tick label is given the 'flot-tick-label' class. For compatibility with Flot -0.7 and earlier the labels are also given the 'tickLabel' class, but this is -deprecated and scheduled to be removed with the release of version 1.0.0. - -To enable more granular control over styles, labels are divided between a set -of text containers, with each holding the labels for one axis. These containers -are given the classes 'flot-[x|y]-axis', and 'flot-[x|y]#-axis', where '#' is -the number of the axis when there are multiple axes. For example, the x-axis -labels for a simple plot with only a single x-axis might look like this: - -```html -
-
January 2013
- ... -
-``` - -For direct control over label styles you can also provide "font" as an object -with this format: - -```js -{ - size: 11, - lineHeight: 13, - style: "italic", - weight: "bold", - family: "sans-serif", - variant: "small-caps", - color: "#545454" -} -``` - -The size and lineHeight must be expressed in pixels; CSS units such as 'em' -or 'smaller' are not allowed. - -The options "min"/"max" are the precise minimum/maximum value on the -scale. If you don't specify either of them, a value will automatically -be chosen based on the minimum/maximum data values. Note that Flot -always examines all the data values you feed to it, even if a -restriction on another axis may make some of them invisible (this -makes interactive use more stable). - -The "autoscaleMargin" is a bit esoteric: it's the fraction of margin -that the scaling algorithm will add to avoid that the outermost points -ends up on the grid border. Note that this margin is only applied when -a min or max value is not explicitly set. If a margin is specified, -the plot will furthermore extend the axis end-point to the nearest -whole tick. The default value is "null" for the x axes and 0.02 for y -axes which seems appropriate for most cases. - -"transform" and "inverseTransform" are callbacks you can put in to -change the way the data is drawn. You can design a function to -compress or expand certain parts of the axis non-linearly, e.g. -suppress weekends or compress far away points with a logarithm or some -other means. When Flot draws the plot, each value is first put through -the transform function. Here's an example, the x axis can be turned -into a natural logarithm axis with the following code: - -```js -xaxis: { - transform: function (v) { return Math.log(v); }, - inverseTransform: function (v) { return Math.exp(v); } -} -``` - -Similarly, for reversing the y axis so the values appear in inverse -order: - -```js -yaxis: { - transform: function (v) { return -v; }, - inverseTransform: function (v) { return -v; } -} -``` - -Note that for finding extrema, Flot assumes that the transform -function does not reorder values (it should be monotone). - -The inverseTransform is simply the inverse of the transform function -(so v == inverseTransform(transform(v)) for all relevant v). It is -required for converting from canvas coordinates to data coordinates, -e.g. for a mouse interaction where a certain pixel is clicked. If you -don't use any interactive features of Flot, you may not need it. - - -The rest of the options deal with the ticks. - -If you don't specify any ticks, a tick generator algorithm will make -some for you. The algorithm has two passes. It first estimates how -many ticks would be reasonable and uses this number to compute a nice -round tick interval size. Then it generates the ticks. - -You can specify how many ticks the algorithm aims for by setting -"ticks" to a number. The algorithm always tries to generate reasonably -round tick values so even if you ask for three ticks, you might get -five if that fits better with the rounding. If you don't want any -ticks at all, set "ticks" to 0 or an empty array. - -Another option is to skip the rounding part and directly set the tick -interval size with "tickSize". If you set it to 2, you'll get ticks at -2, 4, 6, etc. Alternatively, you can specify that you just don't want -ticks at a size less than a specific tick size with "minTickSize". -Note that for time series, the format is an array like [2, "month"], -see the next section. - -If you want to completely override the tick algorithm, you can specify -an array for "ticks", either like this: - -```js -ticks: [0, 1.2, 2.4] -``` - -Or like this where the labels are also customized: - -```js -ticks: [[0, "zero"], [1.2, "one mark"], [2.4, "two marks"]] -``` - -You can mix the two if you like. - -For extra flexibility you can specify a function as the "ticks" -parameter. The function will be called with an object with the axis -min and max and should return a ticks array. Here's a simplistic tick -generator that spits out intervals of pi, suitable for use on the x -axis for trigonometric functions: - -```js -function piTickGenerator(axis) { - var res = [], i = Math.floor(axis.min / Math.PI); - do { - var v = i * Math.PI; - res.push([v, i + "\u03c0"]); - ++i; - } while (v < axis.max); - return res; -} -``` - -You can control how the ticks look like with "tickDecimals", the -number of decimals to display (default is auto-detected). - -Alternatively, for ultimate control over how ticks are formatted you can -provide a function to "tickFormatter". The function is passed two -parameters, the tick value and an axis object with information, and -should return a string. The default formatter looks like this: - -```js -function formatter(val, axis) { - return val.toFixed(axis.tickDecimals); -} -``` - -The axis object has "min" and "max" with the range of the axis, -"tickDecimals" with the number of decimals to round the value to and -"tickSize" with the size of the interval between ticks as calculated -by the automatic axis scaling algorithm (or specified by you). Here's -an example of a custom formatter: - -```js -function suffixFormatter(val, axis) { - if (val > 1000000) - return (val / 1000000).toFixed(axis.tickDecimals) + " MB"; - else if (val > 1000) - return (val / 1000).toFixed(axis.tickDecimals) + " kB"; - else - return val.toFixed(axis.tickDecimals) + " B"; -} -``` - -"labelWidth" and "labelHeight" specifies a fixed size of the tick -labels in pixels. They're useful in case you need to align several -plots. "reserveSpace" means that even if an axis isn't shown, Flot -should reserve space for it - it is useful in combination with -labelWidth and labelHeight for aligning multi-axis charts. - -"tickLength" is the length of the tick lines in pixels. By default, the -innermost axes will have ticks that extend all across the plot, while -any extra axes use small ticks. A value of null means use the default, -while a number means small ticks of that length - set it to 0 to hide -the lines completely. - -If you set "alignTicksWithAxis" to the number of another axis, e.g. -alignTicksWithAxis: 1, Flot will ensure that the autogenerated ticks -of this axis are aligned with the ticks of the other axis. This may -improve the looks, e.g. if you have one y axis to the left and one to -the right, because the grid lines will then match the ticks in both -ends. The trade-off is that the forced ticks won't necessarily be at -natural places. - - -## Multiple axes ## - -If you need more than one x axis or y axis, you need to specify for -each data series which axis they are to use, as described under the -format of the data series, e.g. { data: [...], yaxis: 2 } specifies -that a series should be plotted against the second y axis. - -To actually configure that axis, you can't use the xaxis/yaxis options -directly - instead there are two arrays in the options: - -```js -xaxes: [] -yaxes: [] -``` - -Here's an example of configuring a single x axis and two y axes (we -can leave options of the first y axis empty as the defaults are fine): - -```js -{ - xaxes: [ { position: "top" } ], - yaxes: [ { }, { position: "right", min: 20 } ] -} -``` - -The arrays get their default values from the xaxis/yaxis settings, so -say you want to have all y axes start at zero, you can simply specify -yaxis: { min: 0 } instead of adding a min parameter to all the axes. - -Generally, the various interfaces in Flot dealing with data points -either accept an xaxis/yaxis parameter to specify which axis number to -use (starting from 1), or lets you specify the coordinate directly as -x2/x3/... or x2axis/x3axis/... instead of "x" or "xaxis". - - -## Time series data ## - -Please note that it is now required to include the time plugin, -jquery.flot.time.js, for time series support. - -Time series are a bit more difficult than scalar data because -calendars don't follow a simple base 10 system. For many cases, Flot -abstracts most of this away, but it can still be a bit difficult to -get the data into Flot. So we'll first discuss the data format. - -The time series support in Flot is based on JavaScript timestamps, -i.e. everywhere a time value is expected or handed over, a JavaScript -timestamp number is used. This is a number, not a Date object. A -JavaScript timestamp is the number of milliseconds since January 1, -1970 00:00:00 UTC. This is almost the same as Unix timestamps, except it's -in milliseconds, so remember to multiply by 1000! - -You can see a timestamp like this - -```js -alert((new Date()).getTime()) -``` - -There are different schools of thought when it comes to display of -timestamps. Many will want the timestamps to be displayed according to -a certain time zone, usually the time zone in which the data has been -produced. Some want the localized experience, where the timestamps are -displayed according to the local time of the visitor. Flot supports -both. Optionally you can include a third-party library to get -additional timezone support. - -Default behavior is that Flot always displays timestamps according to -UTC. The reason being that the core JavaScript Date object does not -support other fixed time zones. Often your data is at another time -zone, so it may take a little bit of tweaking to work around this -limitation. - -The easiest way to think about it is to pretend that the data -production time zone is UTC, even if it isn't. So if you have a -datapoint at 2002-02-20 08:00, you can generate a timestamp for eight -o'clock UTC even if it really happened eight o'clock UTC+0200. - -In PHP you can get an appropriate timestamp with: - -```php -strtotime("2002-02-20 UTC") * 1000 -``` - -In Python you can get it with something like: - -```python -calendar.timegm(datetime_object.timetuple()) * 1000 -``` -In Ruby you can get it using the `#to_i` method on the -[`Time`](http://apidock.com/ruby/Time/to_i) object. If you're using the -`active_support` gem (default for Ruby on Rails applications) `#to_i` is also -available on the `DateTime` and `ActiveSupport::TimeWithZone` objects. You -simply need to multiply the result by 1000: - -```ruby -Time.now.to_i * 1000 # => 1383582043000 -# ActiveSupport examples: -DateTime.now.to_i * 1000 # => 1383582043000 -ActiveSupport::TimeZone.new('Asia/Shanghai').now.to_i * 1000 -# => 1383582043000 -``` - -In .NET you can get it with something like: - -```aspx -public static int GetJavaScriptTimestamp(System.DateTime input) -{ - System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks); - System.DateTime time = input.Subtract(span); - return (long)(time.Ticks / 10000); -} -``` - -JavaScript also has some support for parsing date strings, so it is -possible to generate the timestamps manually client-side. - -If you've already got the real UTC timestamp, it's too late to use the -pretend trick described above. But you can fix up the timestamps by -adding the time zone offset, e.g. for UTC+0200 you would add 2 hours -to the UTC timestamp you got. Then it'll look right on the plot. Most -programming environments have some means of getting the timezone -offset for a specific date (note that you need to get the offset for -each individual timestamp to account for daylight savings). - -The alternative with core JavaScript is to interpret the timestamps -according to the time zone that the visitor is in, which means that -the ticks will shift with the time zone and daylight savings of each -visitor. This behavior is enabled by setting the axis option -"timezone" to the value "browser". - -If you need more time zone functionality than this, there is still -another option. If you include the "timezone-js" library - in the page and set axis.timezone -to a value recognized by said library, Flot will use timezone-js to -interpret the timestamps according to that time zone. - -Once you've gotten the timestamps into the data and specified "time" -as the axis mode, Flot will automatically generate relevant ticks and -format them. As always, you can tweak the ticks via the "ticks" option -- just remember that the values should be timestamps (numbers), not -Date objects. - -Tick generation and formatting can also be controlled separately -through the following axis options: - -```js -minTickSize: array -timeformat: null or format string -monthNames: null or array of size 12 of strings -dayNames: null or array of size 7 of strings -twelveHourClock: boolean -``` - -Here "timeformat" is a format string to use. You might use it like -this: - -```js -xaxis: { - mode: "time", - timeformat: "%Y/%m/%d" -} -``` - -This will result in tick labels like "2000/12/24". A subset of the -standard strftime specifiers are supported (plus the nonstandard %q): - -```js -%a: weekday name (customizable) -%b: month name (customizable) -%d: day of month, zero-padded (01-31) -%e: day of month, space-padded ( 1-31) -%H: hours, 24-hour time, zero-padded (00-23) -%I: hours, 12-hour time, zero-padded (01-12) -%m: month, zero-padded (01-12) -%M: minutes, zero-padded (00-59) -%q: quarter (1-4) -%S: seconds, zero-padded (00-59) -%y: year (two digits) -%Y: year (four digits) -%p: am/pm -%P: AM/PM (uppercase version of %p) -%w: weekday as number (0-6, 0 being Sunday) -``` - -Flot 0.8 switched from %h to the standard %H hours specifier. The %h specifier -is still available, for backwards-compatibility, but is deprecated and -scheduled to be removed permanently with the release of version 1.0. - -You can customize the month names with the "monthNames" option. For -instance, for Danish you might specify: - -```js -monthNames: ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"] -``` - -Similarly you can customize the weekday names with the "dayNames" -option. An example in French: - -```js -dayNames: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"] -``` - -If you set "twelveHourClock" to true, the autogenerated timestamps -will use 12 hour AM/PM timestamps instead of 24 hour. This only -applies if you have not set "timeformat". Use the "%I" and "%p" or -"%P" options if you want to build your own format string with 12-hour -times. - -If the Date object has a strftime property (and it is a function), it -will be used instead of the built-in formatter. Thus you can include -a strftime library such as http://hacks.bluesmoon.info/strftime/ for -more powerful date/time formatting. - -If everything else fails, you can control the formatting by specifying -a custom tick formatter function as usual. Here's a simple example -which will format December 24 as 24/12: - -```js -tickFormatter: function (val, axis) { - var d = new Date(val); - return d.getUTCDate() + "/" + (d.getUTCMonth() + 1); -} -``` - -Note that for the time mode "tickSize" and "minTickSize" are a bit -special in that they are arrays on the form "[value, unit]" where unit -is one of "second", "minute", "hour", "day", "month" and "year". So -you can specify - -```js -minTickSize: [1, "month"] -``` - -to get a tick interval size of at least 1 month and correspondingly, -if axis.tickSize is [2, "day"] in the tick formatter, the ticks have -been produced with two days in-between. - - -## Customizing the data series ## - -```js -series: { - lines, points, bars: { - show: boolean - lineWidth: number - fill: boolean or number - fillColor: null or color/gradient - } - - lines, bars: { - zero: boolean - } - - points: { - radius: number - symbol: "circle" or function - } - - bars: { - barWidth: number - align: "left", "right" or "center" - horizontal: boolean - } - - lines: { - steps: boolean - } - - shadowSize: number - highlightColor: color or number -} - -colors: [ color1, color2, ... ] -``` - -The options inside "series: {}" are copied to each of the series. So -you can specify that all series should have bars by putting it in the -global options, or override it for individual series by specifying -bars in a particular the series object in the array of data. - -The most important options are "lines", "points" and "bars" that -specify whether and how lines, points and bars should be shown for -each data series. In case you don't specify anything at all, Flot will -default to showing lines (you can turn this off with -lines: { show: false }). You can specify the various types -independently of each other, and Flot will happily draw each of them -in turn (this is probably only useful for lines and points), e.g. - -```js -var options = { - series: { - lines: { show: true, fill: true, fillColor: "rgba(255, 255, 255, 0.8)" }, - points: { show: true, fill: false } - } -}; -``` - -"lineWidth" is the thickness of the line or outline in pixels. You can -set it to 0 to prevent a line or outline from being drawn; this will -also hide the shadow. - -"fill" is whether the shape should be filled. For lines, this produces -area graphs. You can use "fillColor" to specify the color of the fill. -If "fillColor" evaluates to false (default for everything except -points which are filled with white), the fill color is auto-set to the -color of the data series. You can adjust the opacity of the fill by -setting fill to a number between 0 (fully transparent) and 1 (fully -opaque). - -For bars, fillColor can be a gradient, see the gradient documentation -below. "barWidth" is the width of the bars in units of the x axis (or -the y axis if "horizontal" is true), contrary to most other measures -that are specified in pixels. For instance, for time series the unit -is milliseconds so 24 * 60 * 60 * 1000 produces bars with the width of -a day. "align" specifies whether a bar should be left-aligned -(default), right-aligned or centered on top of the value it represents. -When "horizontal" is on, the bars are drawn horizontally, i.e. from the -y axis instead of the x axis; note that the bar end points are still -defined in the same way so you'll probably want to swap the -coordinates if you've been plotting vertical bars first. - -Area and bar charts normally start from zero, regardless of the data's range. -This is because they convey information through size, and starting from a -different value would distort their meaning. In cases where the fill is purely -for decorative purposes, however, "zero" allows you to override this behavior. -It defaults to true for filled lines and bars; setting it to false tells the -series to use the same automatic scaling as an un-filled line. - -For lines, "steps" specifies whether two adjacent data points are -connected with a straight (possibly diagonal) line or with first a -horizontal and then a vertical line. Note that this transforms the -data by adding extra points. - -For points, you can specify the radius and the symbol. The only -built-in symbol type is circles, for other types you can use a plugin -or define them yourself by specifying a callback: - -```js -function cross(ctx, x, y, radius, shadow) { - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.moveTo(x - size, y - size); - ctx.lineTo(x + size, y + size); - ctx.moveTo(x - size, y + size); - ctx.lineTo(x + size, y - size); -} -``` - -The parameters are the drawing context, x and y coordinates of the -center of the point, a radius which corresponds to what the circle -would have used and whether the call is to draw a shadow (due to -limited canvas support, shadows are currently faked through extra -draws). It's good practice to ensure that the area covered by the -symbol is the same as for the circle with the given radius, this -ensures that all symbols have approximately the same visual weight. - -"shadowSize" is the default size of shadows in pixels. Set it to 0 to -remove shadows. - -"highlightColor" is the default color of the translucent overlay used -to highlight the series when the mouse hovers over it. - -The "colors" array specifies a default color theme to get colors for -the data series from. You can specify as many colors as you like, like -this: - -```js -colors: ["#d18b2c", "#dba255", "#919733"] -``` - -If there are more data series than colors, Flot will try to generate -extra colors by lightening and darkening colors in the theme. - - -## Customizing the grid ## - -```js -grid: { - show: boolean - aboveData: boolean - color: color - backgroundColor: color/gradient or null - margin: number or margin object - labelMargin: number - axisMargin: number - markings: array of markings or (fn: axes -> array of markings) - borderWidth: number or object with "top", "right", "bottom" and "left" properties with different widths - borderColor: color or null or object with "top", "right", "bottom" and "left" properties with different colors - minBorderMargin: number or null - clickable: boolean - hoverable: boolean - autoHighlight: boolean - mouseActiveRadius: number -} - -interaction: { - redrawOverlayInterval: number or -1 -} -``` - -The grid is the thing with the axes and a number of ticks. Many of the -things in the grid are configured under the individual axes, but not -all. "color" is the color of the grid itself whereas "backgroundColor" -specifies the background color inside the grid area, here null means -that the background is transparent. You can also set a gradient, see -the gradient documentation below. - -You can turn off the whole grid including tick labels by setting -"show" to false. "aboveData" determines whether the grid is drawn -above the data or below (below is default). - -"margin" is the space in pixels between the canvas edge and the grid, -which can be either a number or an object with individual margins for -each side, in the form: - -```js -margin: { - top: top margin in pixels - left: left margin in pixels - bottom: bottom margin in pixels - right: right margin in pixels -} -``` - -"labelMargin" is the space in pixels between tick labels and axis -line, and "axisMargin" is the space in pixels between axes when there -are two next to each other. - -"borderWidth" is the width of the border around the plot. Set it to 0 -to disable the border. Set it to an object with "top", "right", -"bottom" and "left" properties to use different widths. You can -also set "borderColor" if you want the border to have a different color -than the grid lines. Set it to an object with "top", "right", "bottom" -and "left" properties to use different colors. "minBorderMargin" controls -the default minimum margin around the border - it's used to make sure -that points aren't accidentally clipped by the canvas edge so by default -the value is computed from the point radius. - -"markings" is used to draw simple lines and rectangular areas in the -background of the plot. You can either specify an array of ranges on -the form { xaxis: { from, to }, yaxis: { from, to } } (with multiple -axes, you can specify coordinates for other axes instead, e.g. as -x2axis/x3axis/...) or with a function that returns such an array given -the axes for the plot in an object as the first parameter. - -You can set the color of markings by specifying "color" in the ranges -object. Here's an example array: - -```js -markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] -``` - -If you leave out one of the values, that value is assumed to go to the -border of the plot. So for example if you only specify { xaxis: { -from: 0, to: 2 } } it means an area that extends from the top to the -bottom of the plot in the x range 0-2. - -A line is drawn if from and to are the same, e.g. - -```js -markings: [ { yaxis: { from: 1, to: 1 } }, ... ] -``` - -would draw a line parallel to the x axis at y = 1. You can control the -line width with "lineWidth" in the range object. - -An example function that makes vertical stripes might look like this: - -```js -markings: function (axes) { - var markings = []; - for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2) - markings.push({ xaxis: { from: x, to: x + 1 } }); - return markings; -} -``` - -If you set "clickable" to true, the plot will listen for click events -on the plot area and fire a "plotclick" event on the placeholder with -a position and a nearby data item object as parameters. The coordinates -are available both in the unit of the axes (not in pixels) and in -global screen coordinates. - -Likewise, if you set "hoverable" to true, the plot will listen for -mouse move events on the plot area and fire a "plothover" event with -the same parameters as the "plotclick" event. If "autoHighlight" is -true (the default), nearby data items are highlighted automatically. -If needed, you can disable highlighting and control it yourself with -the highlight/unhighlight plot methods described elsewhere. - -You can use "plotclick" and "plothover" events like this: - -```js -$.plot($("#placeholder"), [ d ], { grid: { clickable: true } }); - -$("#placeholder").bind("plotclick", function (event, pos, item) { - alert("You clicked at " + pos.x + ", " + pos.y); - // axis coordinates for other axes, if present, are in pos.x2, pos.x3, ... - // if you need global screen coordinates, they are pos.pageX, pos.pageY - - if (item) { - highlight(item.series, item.datapoint); - alert("You clicked a point!"); - } -}); -``` - -The item object in this example is either null or a nearby object on the form: - -```js -item: { - datapoint: the point, e.g. [0, 2] - dataIndex: the index of the point in the data array - series: the series object - seriesIndex: the index of the series - pageX, pageY: the global screen coordinates of the point -} -``` - -For instance, if you have specified the data like this - -```js -$.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...); -``` - -and the mouse is near the point (7, 3), "datapoint" is [7, 3], -"dataIndex" will be 1, "series" is a normalized series object with -among other things the "Foo" label in series.label and the color in -series.color, and "seriesIndex" is 0. Note that plugins and options -that transform the data can shift the indexes from what you specified -in the original data array. - -If you use the above events to update some other information and want -to clear out that info in case the mouse goes away, you'll probably -also need to listen to "mouseout" events on the placeholder div. - -"mouseActiveRadius" specifies how far the mouse can be from an item -and still activate it. If there are two or more points within this -radius, Flot chooses the closest item. For bars, the top-most bar -(from the latest specified data series) is chosen. - -If you want to disable interactivity for a specific data series, you -can set "hoverable" and "clickable" to false in the options for that -series, like this: - -```js -{ data: [...], label: "Foo", clickable: false } -``` - -"redrawOverlayInterval" specifies the maximum time to delay a redraw -of interactive things (this works as a rate limiting device). The -default is capped to 60 frames per second. You can set it to -1 to -disable the rate limiting. - - -## Specifying gradients ## - -A gradient is specified like this: - -```js -{ colors: [ color1, color2, ... ] } -``` - -For instance, you might specify a background on the grid going from -black to gray like this: - -```js -grid: { - backgroundColor: { colors: ["#000", "#999"] } -} -``` - -For the series you can specify the gradient as an object that -specifies the scaling of the brightness and the opacity of the series -color, e.g. - -```js -{ colors: [{ opacity: 0.8 }, { brightness: 0.6, opacity: 0.8 } ] } -``` - -where the first color simply has its alpha scaled, whereas the second -is also darkened. For instance, for bars the following makes the bars -gradually disappear, without outline: - -```js -bars: { - show: true, - lineWidth: 0, - fill: true, - fillColor: { colors: [ { opacity: 0.8 }, { opacity: 0.1 } ] } -} -``` - -Flot currently only supports vertical gradients drawn from top to -bottom because that's what works with IE. - - -## Plot Methods ## - -The Plot object returned from the plot function has some methods you -can call: - - - highlight(series, datapoint) - - Highlight a specific datapoint in the data series. You can either - specify the actual objects, e.g. if you got them from a - "plotclick" event, or you can specify the indices, e.g. - highlight(1, 3) to highlight the fourth point in the second series - (remember, zero-based indexing). - - - unhighlight(series, datapoint) or unhighlight() - - Remove the highlighting of the point, same parameters as - highlight. - - If you call unhighlight with no parameters, e.g. as - plot.unhighlight(), all current highlights are removed. - - - setData(data) - - You can use this to reset the data used. Note that axis scaling, - ticks, legend etc. will not be recomputed (use setupGrid() to do - that). You'll probably want to call draw() afterwards. - - You can use this function to speed up redrawing a small plot if - you know that the axes won't change. Put in the new data with - setData(newdata), call draw(), and you're good to go. Note that - for large datasets, almost all the time is consumed in draw() - plotting the data so in this case don't bother. - - - setupGrid() - - Recalculate and set axis scaling, ticks, legend etc. - - Note that because of the drawing model of the canvas, this - function will immediately redraw (actually reinsert in the DOM) - the labels and the legend, but not the actual tick lines because - they're drawn on the canvas. You need to call draw() to get the - canvas redrawn. - - - draw() - - Redraws the plot canvas. - - - triggerRedrawOverlay() - - Schedules an update of an overlay canvas used for drawing - interactive things like a selection and point highlights. This - is mostly useful for writing plugins. The redraw doesn't happen - immediately, instead a timer is set to catch multiple successive - redraws (e.g. from a mousemove). You can get to the overlay by - setting up a drawOverlay hook. - - - width()/height() - - Gets the width and height of the plotting area inside the grid. - This is smaller than the canvas or placeholder dimensions as some - extra space is needed (e.g. for labels). - - - offset() - - Returns the offset of the plotting area inside the grid relative - to the document, useful for instance for calculating mouse - positions (event.pageX/Y minus this offset is the pixel position - inside the plot). - - - pointOffset({ x: xpos, y: ypos }) - - Returns the calculated offset of the data point at (x, y) in data - space within the placeholder div. If you are working with multiple - axes, you can specify the x and y axis references, e.g. - - ```js - o = pointOffset({ x: xpos, y: ypos, xaxis: 2, yaxis: 3 }) - // o.left and o.top now contains the offset within the div - ```` - - - resize() - - Tells Flot to resize the drawing canvas to the size of the - placeholder. You need to run setupGrid() and draw() afterwards as - canvas resizing is a destructive operation. This is used - internally by the resize plugin. - - - shutdown() - - Cleans up any event handlers Flot has currently registered. This - is used internally. - -There are also some members that let you peek inside the internal -workings of Flot which is useful in some cases. Note that if you change -something in the objects returned, you're changing the objects used by -Flot to keep track of its state, so be careful. - - - getData() - - Returns an array of the data series currently used in normalized - form with missing settings filled in according to the global - options. So for instance to find out what color Flot has assigned - to the data series, you could do this: - - ```js - var series = plot.getData(); - for (var i = 0; i < series.length; ++i) - alert(series[i].color); - ``` - - A notable other interesting field besides color is datapoints - which has a field "points" with the normalized data points in a - flat array (the field "pointsize" is the increment in the flat - array to get to the next point so for a dataset consisting only of - (x,y) pairs it would be 2). - - - getAxes() - - Gets an object with the axes. The axes are returned as the - attributes of the object, so for instance getAxes().xaxis is the - x axis. - - Various things are stuffed inside an axis object, e.g. you could - use getAxes().xaxis.ticks to find out what the ticks are for the - xaxis. Two other useful attributes are p2c and c2p, functions for - transforming from data point space to the canvas plot space and - back. Both returns values that are offset with the plot offset. - Check the Flot source code for the complete set of attributes (or - output an axis with console.log() and inspect it). - - With multiple axes, the extra axes are returned as x2axis, x3axis, - etc., e.g. getAxes().y2axis is the second y axis. You can check - y2axis.used to see whether the axis is associated with any data - points and y2axis.show to see if it is currently shown. - - - getPlaceholder() - - Returns placeholder that the plot was put into. This can be useful - for plugins for adding DOM elements or firing events. - - - getCanvas() - - Returns the canvas used for drawing in case you need to hack on it - yourself. You'll probably need to get the plot offset too. - - - getPlotOffset() - - Gets the offset that the grid has within the canvas as an object - with distances from the canvas edges as "left", "right", "top", - "bottom". I.e., if you draw a circle on the canvas with the center - placed at (left, top), its center will be at the top-most, left - corner of the grid. - - - getOptions() - - Gets the options for the plot, normalized, with default values - filled in. You get a reference to actual values used by Flot, so - if you modify the values in here, Flot will use the new values. - If you change something, you probably have to call draw() or - setupGrid() or triggerRedrawOverlay() to see the change. - - -## Hooks ## - -In addition to the public methods, the Plot object also has some hooks -that can be used to modify the plotting process. You can install a -callback function at various points in the process, the function then -gets access to the internal data structures in Flot. - -Here's an overview of the phases Flot goes through: - - 1. Plugin initialization, parsing options - - 2. Constructing the canvases used for drawing - - 3. Set data: parsing data specification, calculating colors, - copying raw data points into internal format, - normalizing them, finding max/min for axis auto-scaling - - 4. Grid setup: calculating axis spacing, ticks, inserting tick - labels, the legend - - 5. Draw: drawing the grid, drawing each of the series in turn - - 6. Setting up event handling for interactive features - - 7. Responding to events, if any - - 8. Shutdown: this mostly happens in case a plot is overwritten - -Each hook is simply a function which is put in the appropriate array. -You can add them through the "hooks" option, and they are also available -after the plot is constructed as the "hooks" attribute on the returned -plot object, e.g. - -```js - // define a simple draw hook - function hellohook(plot, canvascontext) { alert("hello!"); }; - - // pass it in, in an array since we might want to specify several - var plot = $.plot(placeholder, data, { hooks: { draw: [hellohook] } }); - - // we can now find it again in plot.hooks.draw[0] unless a plugin - // has added other hooks -``` - -The available hooks are described below. All hook callbacks get the -plot object as first parameter. You can find some examples of defined -hooks in the plugins bundled with Flot. - - - processOptions [phase 1] - - ```function(plot, options)``` - - Called after Flot has parsed and merged options. Useful in the - instance where customizations beyond simple merging of default - values is needed. A plugin might use it to detect that it has been - enabled and then turn on or off other options. - - - - processRawData [phase 3] - - ```function(plot, series, data, datapoints)``` - - Called before Flot copies and normalizes the raw data for the given - series. If the function fills in datapoints.points with normalized - points and sets datapoints.pointsize to the size of the points, - Flot will skip the copying/normalization step for this series. - - In any case, you might be interested in setting datapoints.format, - an array of objects for specifying how a point is normalized and - how it interferes with axis scaling. It accepts the following options: - - ```js - { - x, y: boolean, - number: boolean, - required: boolean, - defaultValue: value, - autoscale: boolean - } - ``` - - "x" and "y" specify whether the value is plotted against the x or y axis, - and is currently used only to calculate axis min-max ranges. The default - format array, for example, looks like this: - - ```js - [ - { x: true, number: true, required: true }, - { y: true, number: true, required: true } - ] - ``` - - This indicates that a point, i.e. [0, 25], consists of two values, with the - first being plotted on the x axis and the second on the y axis. - - If "number" is true, then the value must be numeric, and is set to null if - it cannot be converted to a number. - - "defaultValue" provides a fallback in case the original value is null. This - is for instance handy for bars, where one can omit the third coordinate - (the bottom of the bar), which then defaults to zero. - - If "required" is true, then the value must exist (be non-null) for the - point as a whole to be valid. If no value is provided, then the entire - point is cleared out with nulls, turning it into a gap in the series. - - "autoscale" determines whether the value is considered when calculating an - automatic min-max range for the axes that the value is plotted against. - - - processDatapoints [phase 3] - - ```function(plot, series, datapoints)``` - - Called after normalization of the given series but before finding - min/max of the data points. This hook is useful for implementing data - transformations. "datapoints" contains the normalized data points in - a flat array as datapoints.points with the size of a single point - given in datapoints.pointsize. Here's a simple transform that - multiplies all y coordinates by 2: - - ```js - function multiply(plot, series, datapoints) { - var points = datapoints.points, ps = datapoints.pointsize; - for (var i = 0; i < points.length; i += ps) - points[i + 1] *= 2; - } - ``` - - Note that you must leave datapoints in a good condition as Flot - doesn't check it or do any normalization on it afterwards. - - - processOffset [phase 4] - - ```function(plot, offset)``` - - Called after Flot has initialized the plot's offset, but before it - draws any axes or plot elements. This hook is useful for customizing - the margins between the grid and the edge of the canvas. "offset" is - an object with attributes "top", "bottom", "left" and "right", - corresponding to the margins on the four sides of the plot. - - - drawBackground [phase 5] - - ```function(plot, canvascontext)``` - - Called before all other drawing operations. Used to draw backgrounds - or other custom elements before the plot or axes have been drawn. - - - drawSeries [phase 5] - - ```function(plot, canvascontext, series)``` - - Hook for custom drawing of a single series. Called just before the - standard drawing routine has been called in the loop that draws - each series. - - - draw [phase 5] - - ```function(plot, canvascontext)``` - - Hook for drawing on the canvas. Called after the grid is drawn - (unless it's disabled or grid.aboveData is set) and the series have - been plotted (in case any points, lines or bars have been turned - on). For examples of how to draw things, look at the source code. - - - bindEvents [phase 6] - - ```function(plot, eventHolder)``` - - Called after Flot has setup its event handlers. Should set any - necessary event handlers on eventHolder, a jQuery object with the - canvas, e.g. - - ```js - function (plot, eventHolder) { - eventHolder.mousedown(function (e) { - alert("You pressed the mouse at " + e.pageX + " " + e.pageY); - }); - } - ``` - - Interesting events include click, mousemove, mouseup/down. You can - use all jQuery events. Usually, the event handlers will update the - state by drawing something (add a drawOverlay hook and call - triggerRedrawOverlay) or firing an externally visible event for - user code. See the crosshair plugin for an example. - - Currently, eventHolder actually contains both the static canvas - used for the plot itself and the overlay canvas used for - interactive features because some versions of IE get the stacking - order wrong. The hook only gets one event, though (either for the - overlay or for the static canvas). - - Note that custom plot events generated by Flot are not generated on - eventHolder, but on the div placeholder supplied as the first - argument to the plot call. You can get that with - plot.getPlaceholder() - that's probably also the one you should use - if you need to fire a custom event. - - - drawOverlay [phase 7] - - ```function (plot, canvascontext)``` - - The drawOverlay hook is used for interactive things that need a - canvas to draw on. The model currently used by Flot works the way - that an extra overlay canvas is positioned on top of the static - canvas. This overlay is cleared and then completely redrawn - whenever something interesting happens. This hook is called when - the overlay canvas is to be redrawn. - - "canvascontext" is the 2D context of the overlay canvas. You can - use this to draw things. You'll most likely need some of the - metrics computed by Flot, e.g. plot.width()/plot.height(). See the - crosshair plugin for an example. - - - shutdown [phase 8] - - ```function (plot, eventHolder)``` - - Run when plot.shutdown() is called, which usually only happens in - case a plot is overwritten by a new plot. If you're writing a - plugin that adds extra DOM elements or event handlers, you should - add a callback to clean up after you. Take a look at the section in - the [PLUGINS](PLUGINS.md) document for more info. - - -## Plugins ## - -Plugins extend the functionality of Flot. To use a plugin, simply -include its JavaScript file after Flot in the HTML page. - -If you're worried about download size/latency, you can concatenate all -the plugins you use, and Flot itself for that matter, into one big file -(make sure you get the order right), then optionally run it through a -JavaScript minifier such as YUI Compressor. - -Here's a brief explanation of how the plugin plumbings work: - -Each plugin registers itself in the global array $.plot.plugins. When -you make a new plot object with $.plot, Flot goes through this array -calling the "init" function of each plugin and merging default options -from the "option" attribute of the plugin. The init function gets a -reference to the plot object created and uses this to register hooks -and add new public methods if needed. - -See the [PLUGINS](PLUGINS.md) document for details on how to write a plugin. As the -above description hints, it's actually pretty easy. - - -## Version number ## - -The version number of Flot is available in ```$.plot.version```. diff --git a/src/legacy/ui/public/flot-charts/index.js b/src/legacy/ui/public/flot-charts/index.js deleted file mode 100644 index f98aca30d2dec..0000000000000 --- a/src/legacy/ui/public/flot-charts/index.js +++ /dev/null @@ -1,42 +0,0 @@ -/* @notice - * - * This product includes code that is based on flot-charts, which was available - * under a "MIT" license. - * - * The MIT License (MIT) - * - * Copyright (c) 2007-2014 IOLA and Ole Laursen - * - * 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. - */ - -import $ from 'jquery'; -if (window) window.jQuery = $; -require('ui/flot-charts/jquery.flot'); -require('ui/flot-charts/jquery.flot.time'); -require('ui/flot-charts/jquery.flot.canvas'); -require('ui/flot-charts/jquery.flot.symbol'); -require('ui/flot-charts/jquery.flot.crosshair'); -require('ui/flot-charts/jquery.flot.selection'); -require('ui/flot-charts/jquery.flot.pie'); -require('ui/flot-charts/jquery.flot.stack'); -require('ui/flot-charts/jquery.flot.threshold'); -require('ui/flot-charts/jquery.flot.fillbetween'); -require('ui/flot-charts/jquery.flot.log'); -module.exports = $; diff --git a/src/legacy/ui/public/flot-charts/jquery.colorhelpers.js b/src/legacy/ui/public/flot-charts/jquery.colorhelpers.js deleted file mode 100644 index b2f6dc4e433a3..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.colorhelpers.js +++ /dev/null @@ -1,180 +0,0 @@ -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ - -(function($) { - $.color = {}; - - // construct color object with some convenient chainable helpers - $.color.make = function (r, g, b, a) { - var o = {}; - o.r = r || 0; - o.g = g || 0; - o.b = b || 0; - o.a = a != null ? a : 1; - - o.add = function (c, d) { - for (var i = 0; i < c.length; ++i) - o[c.charAt(i)] += d; - return o.normalize(); - }; - - o.scale = function (c, f) { - for (var i = 0; i < c.length; ++i) - o[c.charAt(i)] *= f; - return o.normalize(); - }; - - o.toString = function () { - if (o.a >= 1.0) { - return "rgb("+[o.r, o.g, o.b].join(",")+")"; - } else { - return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")"; - } - }; - - o.normalize = function () { - function clamp(min, value, max) { - return value < min ? min: (value > max ? max: value); - } - - o.r = clamp(0, parseInt(o.r), 255); - o.g = clamp(0, parseInt(o.g), 255); - o.b = clamp(0, parseInt(o.b), 255); - o.a = clamp(0, o.a, 1); - return o; - }; - - o.clone = function () { - return $.color.make(o.r, o.b, o.g, o.a); - }; - - return o.normalize(); - } - - // extract CSS color property from element, going up in the DOM - // if it's "transparent" - $.color.extract = function (elem, css) { - var c; - - do { - c = elem.css(css).toLowerCase(); - // keep going until we find an element that has color, or - // we hit the body or root (have no parent) - if (c != '' && c != 'transparent') - break; - elem = elem.parent(); - } while (elem.length && !$.nodeName(elem.get(0), "body")); - - // catch Safari's way of signalling transparent - if (c == "rgba(0, 0, 0, 0)") - c = "transparent"; - - return $.color.parse(c); - } - - // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"), - // returns color object, if parsing failed, you get black (0, 0, - // 0) out - $.color.parse = function (str) { - var res, m = $.color.make; - - // Look for rgb(num,num,num) - if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) - return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10)); - - // Look for rgba(num,num,num,num) - if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) - return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4])); - - // Look for rgb(num%,num%,num%) - if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) - return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55); - - // Look for rgba(num%,num%,num%,num) - if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) - return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4])); - - // Look for #a0b1c2 - if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) - return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); - - // Look for #fff - if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) - return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16)); - - // Otherwise, we're most likely dealing with a named color - var name = $.trim(str).toLowerCase(); - if (name == "transparent") - return m(255, 255, 255, 0); - else { - // default to black - res = lookupColors[name] || [0, 0, 0]; - return m(res[0], res[1], res[2]); - } - } - - var lookupColors = { - aqua:[0,255,255], - azure:[240,255,255], - beige:[245,245,220], - black:[0,0,0], - blue:[0,0,255], - brown:[165,42,42], - cyan:[0,255,255], - darkblue:[0,0,139], - darkcyan:[0,139,139], - darkgrey:[169,169,169], - darkgreen:[0,100,0], - darkkhaki:[189,183,107], - darkmagenta:[139,0,139], - darkolivegreen:[85,107,47], - darkorange:[255,140,0], - darkorchid:[153,50,204], - darkred:[139,0,0], - darksalmon:[233,150,122], - darkviolet:[148,0,211], - fuchsia:[255,0,255], - gold:[255,215,0], - green:[0,128,0], - indigo:[75,0,130], - khaki:[240,230,140], - lightblue:[173,216,230], - lightcyan:[224,255,255], - lightgreen:[144,238,144], - lightgrey:[211,211,211], - lightpink:[255,182,193], - lightyellow:[255,255,224], - lime:[0,255,0], - magenta:[255,0,255], - maroon:[128,0,0], - navy:[0,0,128], - olive:[128,128,0], - orange:[255,165,0], - pink:[255,192,203], - purple:[128,0,128], - violet:[128,0,128], - red:[255,0,0], - silver:[192,192,192], - white:[255,255,255], - yellow:[255,255,0] - }; -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.canvas.js b/src/legacy/ui/public/flot-charts/jquery.flot.canvas.js deleted file mode 100644 index 29328d5812127..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.canvas.js +++ /dev/null @@ -1,345 +0,0 @@ -/* Flot plugin for drawing all elements of a plot on the canvas. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -Flot normally produces certain elements, like axis labels and the legend, using -HTML elements. This permits greater interactivity and customization, and often -looks better, due to cross-browser canvas text inconsistencies and limitations. - -It can also be desirable to render the plot entirely in canvas, particularly -if the goal is to save it as an image, or if Flot is being used in a context -where the HTML DOM does not exist, as is the case within Node.js. This plugin -switches out Flot's standard drawing operations for canvas-only replacements. - -Currently the plugin supports only axis labels, but it will eventually allow -every element of the plot to be rendered directly to canvas. - -The plugin supports these options: - -{ - canvas: boolean -} - -The "canvas" option controls whether full canvas drawing is enabled, making it -possible to toggle on and off. This is useful when a plot uses HTML text in the -browser, but needs to redraw with canvas text when exporting as an image. - -*/ - -(function($) { - - var options = { - canvas: true - }; - - var render, getTextInfo, addText; - - // Cache the prototype hasOwnProperty for faster access - - var hasOwnProperty = Object.prototype.hasOwnProperty; - - function init(plot, classes) { - - var Canvas = classes.Canvas; - - // We only want to replace the functions once; the second time around - // we would just get our new function back. This whole replacing of - // prototype functions is a disaster, and needs to be changed ASAP. - - if (render == null) { - getTextInfo = Canvas.prototype.getTextInfo, - addText = Canvas.prototype.addText, - render = Canvas.prototype.render; - } - - // Finishes rendering the canvas, including overlaid text - - Canvas.prototype.render = function() { - - if (!plot.getOptions().canvas) { - return render.call(this); - } - - var context = this.context, - cache = this._textCache; - - // For each text layer, render elements marked as active - - context.save(); - context.textBaseline = "middle"; - - for (var layerKey in cache) { - if (hasOwnProperty.call(cache, layerKey)) { - var layerCache = cache[layerKey]; - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey], - updateStyles = true; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - - var info = styleCache[key], - positions = info.positions, - lines = info.lines; - - // Since every element at this level of the cache have the - // same font and fill styles, we can just change them once - // using the values from the first element. - - if (updateStyles) { - context.fillStyle = info.font.color; - context.font = info.font.definition; - updateStyles = false; - } - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - for (var j = 0, line; line = position.lines[j]; j++) { - context.fillText(lines[j].text, line[0], line[1]); - } - } else { - positions.splice(i--, 1); - } - } - - if (positions.length == 0) { - delete styleCache[key]; - } - } - } - } - } - } - } - - context.restore(); - }; - - // Creates (if necessary) and returns a text info object. - // - // When the canvas option is set, the object looks like this: - // - // { - // width: Width of the text's bounding box. - // height: Height of the text's bounding box. - // positions: Array of positions at which this text is drawn. - // lines: [{ - // height: Height of this line. - // widths: Width of this line. - // text: Text on this line. - // }], - // font: { - // definition: Canvas font property string. - // color: Color of the text. - // }, - // } - // - // The positions array contains objects that look like this: - // - // { - // active: Flag indicating whether the text should be visible. - // lines: Array of [x, y] coordinates at which to draw the line. - // x: X coordinate at which to draw the text. - // y: Y coordinate at which to draw the text. - // } - - Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - - if (!plot.getOptions().canvas) { - return getTextInfo.call(this, layer, text, font, angle, width); - } - - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number - - text = "" + text; - - // If the font is a font-spec object, generate a CSS definition - - if (typeof font === "object") { - textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; - } else { - textStyle = font; - } - - // Retrieve (or create) the cache for the text's layer and styles - - layerCache = this._textCache[layer]; - - if (layerCache == null) { - layerCache = this._textCache[layer] = {}; - } - - styleCache = layerCache[textStyle]; - - if (styleCache == null) { - styleCache = layerCache[textStyle] = {}; - } - - info = styleCache[text]; - - if (info == null) { - - var context = this.context; - - // If the font was provided as CSS, create a div with those - // classes and examine it to generate a canvas font spec. - - if (typeof font !== "object") { - - var element = $("
 
") - .css("position", "absolute") - .addClass(typeof font === "string" ? font : null) - .appendTo(this.getTextLayer(layer)); - - font = { - lineHeight: element.height(), - style: element.css("font-style"), - variant: element.css("font-variant"), - weight: element.css("font-weight"), - family: element.css("font-family"), - color: element.css("color") - }; - - // Setting line-height to 1, without units, sets it equal - // to the font-size, even if the font-size is abstract, - // like 'smaller'. This enables us to read the real size - // via the element's height, working around browsers that - // return the literal 'smaller' value. - - font.size = element.css("line-height", 1).height(); - - element.remove(); - } - - textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; - - // Create a new info object, initializing the dimensions to - // zero so we can count them up line-by-line. - - info = styleCache[text] = { - width: 0, - height: 0, - positions: [], - lines: [], - font: { - definition: textStyle, - color: font.color - } - }; - - context.save(); - context.font = textStyle; - - // Canvas can't handle multi-line strings; break on various - // newlines, including HTML brs, to build a list of lines. - // Note that we could split directly on regexps, but IE < 9 is - // broken; revisit when we drop IE 7/8 support. - - var lines = (text + "").replace(/
|\r\n|\r/g, "\n").split("\n"); - - for (var i = 0; i < lines.length; ++i) { - - var lineText = lines[i], - measured = context.measureText(lineText); - - info.width = Math.max(measured.width, info.width); - info.height += font.lineHeight; - - info.lines.push({ - text: lineText, - width: measured.width, - height: font.lineHeight - }); - } - - context.restore(); - } - - return info; - }; - - // Adds a text string to the canvas text overlay. - - Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { - - if (!plot.getOptions().canvas) { - return addText.call(this, layer, x, y, text, font, angle, width, halign, valign); - } - - var info = this.getTextInfo(layer, text, font, angle, width), - positions = info.positions, - lines = info.lines; - - // Text is drawn with baseline 'middle', which we need to account - // for by adding half a line's height to the y position. - - y += info.height / lines.length / 2; - - // Tweak the initial y-position to match vertical alignment - - if (valign == "middle") { - y = Math.round(y - info.height / 2); - } else if (valign == "bottom") { - y = Math.round(y - info.height); - } else { - y = Math.round(y); - } - - // FIXME: LEGACY BROWSER FIX - // AFFECTS: Opera < 12.00 - - // Offset the y coordinate, since Opera is off pretty - // consistently compared to the other browsers. - - if (!!(window.opera && window.opera.version().split(".")[0] < 12)) { - y -= 2; - } - - // Determine whether this text already exists at this position. - // If so, mark it for inclusion in the next render pass. - - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = true; - return; - } - } - - // If the text doesn't exist at this position, create a new entry - - position = { - active: true, - lines: [], - x: x, - y: y - }; - - positions.push(position); - - // Fill in the x & y positions of each line, adjusting them - // individually for horizontal alignment. - - for (var i = 0, line; line = lines[i]; i++) { - if (halign == "center") { - position.lines.push([Math.round(x - line.width / 2), y]); - } else if (halign == "right") { - position.lines.push([Math.round(x - line.width), y]); - } else { - position.lines.push([Math.round(x), y]); - } - y += line.height; - } - }; - } - - $.plot.plugins.push({ - init: init, - options: options, - name: "canvas", - version: "1.0" - }); - -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.categories.js b/src/legacy/ui/public/flot-charts/jquery.flot.categories.js deleted file mode 100644 index 2f9b257971499..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.categories.js +++ /dev/null @@ -1,190 +0,0 @@ -/* Flot plugin for plotting textual data or categories. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin -allows you to plot such a dataset directly. - -To enable it, you must specify mode: "categories" on the axis with the textual -labels, e.g. - - $.plot("#placeholder", data, { xaxis: { mode: "categories" } }); - -By default, the labels are ordered as they are met in the data series. If you -need a different ordering, you can specify "categories" on the axis options -and list the categories there: - - xaxis: { - mode: "categories", - categories: ["February", "March", "April"] - } - -If you need to customize the distances between the categories, you can specify -"categories" as an object mapping labels to values - - xaxis: { - mode: "categories", - categories: { "February": 1, "March": 3, "April": 4 } - } - -If you don't specify all categories, the remaining categories will be numbered -from the max value plus 1 (with a spacing of 1 between each). - -Internally, the plugin works by transforming the input data through an auto- -generated mapping where the first category becomes 0, the second 1, etc. -Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this -is visible in hover and click events that return numbers rather than the -category labels). The plugin also overrides the tick generator to spit out the -categories as ticks instead of the values. - -If you need to map a value back to its label, the mapping is always accessible -as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories. - -*/ - -(function ($) { - var options = { - xaxis: { - categories: null - }, - yaxis: { - categories: null - } - }; - - function processRawData(plot, series, data, datapoints) { - // if categories are enabled, we need to disable - // auto-transformation to numbers so the strings are intact - // for later processing - - var xCategories = series.xaxis.options.mode == "categories", - yCategories = series.yaxis.options.mode == "categories"; - - if (!(xCategories || yCategories)) - return; - - var format = datapoints.format; - - if (!format) { - // FIXME: auto-detection should really not be defined here - var s = series; - format = []; - format.push({ x: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - - if (s.bars.show || (s.lines.show && s.lines.fill)) { - var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); - format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); - if (s.bars.horizontal) { - delete format[format.length - 1].y; - format[format.length - 1].x = true; - } - } - - datapoints.format = format; - } - - for (var m = 0; m < format.length; ++m) { - if (format[m].x && xCategories) - format[m].number = false; - - if (format[m].y && yCategories) - format[m].number = false; - } - } - - function getNextIndex(categories) { - var index = -1; - - for (var v in categories) - if (categories[v] > index) - index = categories[v]; - - return index + 1; - } - - function categoriesTickGenerator(axis) { - var res = []; - for (var label in axis.categories) { - var v = axis.categories[label]; - if (v >= axis.min && v <= axis.max) - res.push([v, label]); - } - - res.sort(function (a, b) { return a[0] - b[0]; }); - - return res; - } - - function setupCategoriesForAxis(series, axis, datapoints) { - if (series[axis].options.mode != "categories") - return; - - if (!series[axis].categories) { - // parse options - var c = {}, o = series[axis].options.categories || {}; - if ($.isArray(o)) { - for (var i = 0; i < o.length; ++i) - c[o[i]] = i; - } - else { - for (var v in o) - c[v] = o[v]; - } - - series[axis].categories = c; - } - - // fix ticks - if (!series[axis].options.ticks) - series[axis].options.ticks = categoriesTickGenerator; - - transformPointsOnAxis(datapoints, axis, series[axis].categories); - } - - function transformPointsOnAxis(datapoints, axis, categories) { - // go through the points, transforming them - var points = datapoints.points, - ps = datapoints.pointsize, - format = datapoints.format, - formatColumn = axis.charAt(0), - index = getNextIndex(categories); - - for (var i = 0; i < points.length; i += ps) { - if (points[i] == null) - continue; - - for (var m = 0; m < ps; ++m) { - var val = points[i + m]; - - if (val == null || !format[m][formatColumn]) - continue; - - if (!(val in categories)) { - categories[val] = index; - ++index; - } - - points[i + m] = categories[val]; - } - } - } - - function processDatapoints(plot, series, datapoints) { - setupCategoriesForAxis(series, "xaxis", datapoints); - setupCategoriesForAxis(series, "yaxis", datapoints); - } - - function init(plot) { - plot.hooks.processRawData.push(processRawData); - plot.hooks.processDatapoints.push(processDatapoints); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'categories', - version: '1.0' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.crosshair.js b/src/legacy/ui/public/flot-charts/jquery.flot.crosshair.js deleted file mode 100644 index 5111695e3d12c..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.crosshair.js +++ /dev/null @@ -1,176 +0,0 @@ -/* Flot plugin for showing crosshairs when the mouse hovers over the plot. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin supports these options: - - crosshair: { - mode: null or "x" or "y" or "xy" - color: color - lineWidth: number - } - -Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical -crosshair that lets you trace the values on the x axis, "y" enables a -horizontal crosshair and "xy" enables them both. "color" is the color of the -crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of -the drawn lines (default is 1). - -The plugin also adds four public methods: - - - setCrosshair( pos ) - - Set the position of the crosshair. Note that this is cleared if the user - moves the mouse. "pos" is in coordinates of the plot and should be on the - form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple - axes), which is coincidentally the same format as what you get from a - "plothover" event. If "pos" is null, the crosshair is cleared. - - - clearCrosshair() - - Clear the crosshair. - - - lockCrosshair(pos) - - Cause the crosshair to lock to the current location, no longer updating if - the user moves the mouse. Optionally supply a position (passed on to - setCrosshair()) to move it to. - - Example usage: - - var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; - $("#graph").bind( "plothover", function ( evt, position, item ) { - if ( item ) { - // Lock the crosshair to the data point being hovered - myFlot.lockCrosshair({ - x: item.datapoint[ 0 ], - y: item.datapoint[ 1 ] - }); - } else { - // Return normal crosshair operation - myFlot.unlockCrosshair(); - } - }); - - - unlockCrosshair() - - Free the crosshair to move again after locking it. -*/ - -(function ($) { - var options = { - crosshair: { - mode: null, // one of null, "x", "y" or "xy", - color: "rgba(170, 0, 0, 0.80)", - lineWidth: 1 - } - }; - - function init(plot) { - // position of crosshair in pixels - var crosshair = { x: -1, y: -1, locked: false }; - - plot.setCrosshair = function setCrosshair(pos) { - if (!pos) - crosshair.x = -1; - else { - var o = plot.p2c(pos); - crosshair.x = Math.max(0, Math.min(o.left, plot.width())); - crosshair.y = Math.max(0, Math.min(o.top, plot.height())); - } - - plot.triggerRedrawOverlay(); - }; - - plot.clearCrosshair = plot.setCrosshair; // passes null for pos - - plot.lockCrosshair = function lockCrosshair(pos) { - if (pos) - plot.setCrosshair(pos); - crosshair.locked = true; - }; - - plot.unlockCrosshair = function unlockCrosshair() { - crosshair.locked = false; - }; - - function onMouseOut(e) { - if (crosshair.locked) - return; - - if (crosshair.x != -1) { - crosshair.x = -1; - plot.triggerRedrawOverlay(); - } - } - - function onMouseMove(e) { - if (crosshair.locked) - return; - - if (plot.getSelection && plot.getSelection()) { - crosshair.x = -1; // hide the crosshair while selecting - return; - } - - var offset = plot.offset(); - crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); - crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); - plot.triggerRedrawOverlay(); - } - - plot.hooks.bindEvents.push(function (plot, eventHolder) { - if (!plot.getOptions().crosshair.mode) - return; - - eventHolder.mouseout(onMouseOut); - eventHolder.mousemove(onMouseMove); - }); - - plot.hooks.drawOverlay.push(function (plot, ctx) { - var c = plot.getOptions().crosshair; - if (!c.mode) - return; - - var plotOffset = plot.getPlotOffset(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - if (crosshair.x != -1) { - var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; - - ctx.strokeStyle = c.color; - ctx.lineWidth = c.lineWidth; - ctx.lineJoin = "round"; - - ctx.beginPath(); - if (c.mode.indexOf("x") != -1) { - var drawX = Math.floor(crosshair.x) + adj; - ctx.moveTo(drawX, 0); - ctx.lineTo(drawX, plot.height()); - } - if (c.mode.indexOf("y") != -1) { - var drawY = Math.floor(crosshair.y) + adj; - ctx.moveTo(0, drawY); - ctx.lineTo(plot.width(), drawY); - } - ctx.stroke(); - } - ctx.restore(); - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mouseout", onMouseOut); - eventHolder.unbind("mousemove", onMouseMove); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'crosshair', - version: '1.0' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.errorbars.js b/src/legacy/ui/public/flot-charts/jquery.flot.errorbars.js deleted file mode 100644 index 655036e0db846..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.errorbars.js +++ /dev/null @@ -1,353 +0,0 @@ -/* Flot plugin for plotting error bars. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -Error bars are used to show standard deviation and other statistical -properties in a plot. - -* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com - -This plugin allows you to plot error-bars over points. Set "errorbars" inside -the points series to the axis name over which there will be error values in -your data array (*even* if you do not intend to plot them later, by setting -"show: null" on xerr/yerr). - -The plugin supports these options: - - series: { - points: { - errorbars: "x" or "y" or "xy", - xerr: { - show: null/false or true, - asymmetric: null/false or true, - upperCap: null or "-" or function, - lowerCap: null or "-" or function, - color: null or color, - radius: null or number - }, - yerr: { same options as xerr } - } - } - -Each data point array is expected to be of the type: - - "x" [ x, y, xerr ] - "y" [ x, y, yerr ] - "xy" [ x, y, xerr, yerr ] - -Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and -equivalently for yerr. E.g., a datapoint for the "xy" case with symmetric -error-bars on X and asymmetric on Y would be: - - [ x, y, xerr, yerr_lower, yerr_upper ] - -By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will -draw a small cap perpendicular to the error bar. They can also be set to a -user-defined drawing function, with (ctx, x, y, radius) as parameters, as e.g.: - - function drawSemiCircle( ctx, x, y, radius ) { - ctx.beginPath(); - ctx.arc( x, y, radius, 0, Math.PI, false ); - ctx.moveTo( x - radius, y ); - ctx.lineTo( x + radius, y ); - ctx.stroke(); - } - -Color and radius both default to the same ones of the points series if not -set. The independent radius parameter on xerr/yerr is useful for the case when -we may want to add error-bars to a line, without showing the interconnecting -points (with radius: 0), and still showing end caps on the error-bars. -shadowSize and lineWidth are derived as well from the points series. - -*/ - -(function ($) { - var options = { - series: { - points: { - errorbars: null, //should be 'x', 'y' or 'xy' - xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}, - yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null} - } - } - }; - - function processRawData(plot, series, data, datapoints){ - if (!series.points.errorbars) - return; - - // x,y values - var format = [ - { x: true, number: true, required: true }, - { y: true, number: true, required: true } - ]; - - var errors = series.points.errorbars; - // error bars - first X then Y - if (errors == 'x' || errors == 'xy') { - // lower / upper error - if (series.points.xerr.asymmetric) { - format.push({ x: true, number: true, required: true }); - format.push({ x: true, number: true, required: true }); - } else - format.push({ x: true, number: true, required: true }); - } - if (errors == 'y' || errors == 'xy') { - // lower / upper error - if (series.points.yerr.asymmetric) { - format.push({ y: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - } else - format.push({ y: true, number: true, required: true }); - } - datapoints.format = format; - } - - function parseErrors(series, i){ - - var points = series.datapoints.points; - - // read errors from points array - var exl = null, - exu = null, - eyl = null, - eyu = null; - var xerr = series.points.xerr, - yerr = series.points.yerr; - - var eb = series.points.errorbars; - // error bars - first X - if (eb == 'x' || eb == 'xy') { - if (xerr.asymmetric) { - exl = points[i + 2]; - exu = points[i + 3]; - if (eb == 'xy') - if (yerr.asymmetric){ - eyl = points[i + 4]; - eyu = points[i + 5]; - } else eyl = points[i + 4]; - } else { - exl = points[i + 2]; - if (eb == 'xy') - if (yerr.asymmetric) { - eyl = points[i + 3]; - eyu = points[i + 4]; - } else eyl = points[i + 3]; - } - // only Y - } else if (eb == 'y') - if (yerr.asymmetric) { - eyl = points[i + 2]; - eyu = points[i + 3]; - } else eyl = points[i + 2]; - - // symmetric errors? - if (exu == null) exu = exl; - if (eyu == null) eyu = eyl; - - var errRanges = [exl, exu, eyl, eyu]; - // nullify if not showing - if (!xerr.show){ - errRanges[0] = null; - errRanges[1] = null; - } - if (!yerr.show){ - errRanges[2] = null; - errRanges[3] = null; - } - return errRanges; - } - - function drawSeriesErrors(plot, ctx, s){ - - var points = s.datapoints.points, - ps = s.datapoints.pointsize, - ax = [s.xaxis, s.yaxis], - radius = s.points.radius, - err = [s.points.xerr, s.points.yerr]; - - //sanity check, in case some inverted axis hack is applied to flot - var invertX = false; - if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) { - invertX = true; - var tmp = err[0].lowerCap; - err[0].lowerCap = err[0].upperCap; - err[0].upperCap = tmp; - } - - var invertY = false; - if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) { - invertY = true; - var tmp = err[1].lowerCap; - err[1].lowerCap = err[1].upperCap; - err[1].upperCap = tmp; - } - - for (var i = 0; i < s.datapoints.points.length; i += ps) { - - //parse - var errRanges = parseErrors(s, i); - - //cycle xerr & yerr - for (var e = 0; e < err.length; e++){ - - var minmax = [ax[e].min, ax[e].max]; - - //draw this error? - if (errRanges[e * err.length]){ - - //data coordinates - var x = points[i], - y = points[i + 1]; - - //errorbar ranges - var upper = [x, y][e] + errRanges[e * err.length + 1], - lower = [x, y][e] - errRanges[e * err.length]; - - //points outside of the canvas - if (err[e].err == 'x') - if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) - continue; - if (err[e].err == 'y') - if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) - continue; - - // prevent errorbars getting out of the canvas - var drawUpper = true, - drawLower = true; - - if (upper > minmax[1]) { - drawUpper = false; - upper = minmax[1]; - } - if (lower < minmax[0]) { - drawLower = false; - lower = minmax[0]; - } - - //sanity check, in case some inverted axis hack is applied to flot - if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) { - //swap coordinates - var tmp = lower; - lower = upper; - upper = tmp; - tmp = drawLower; - drawLower = drawUpper; - drawUpper = tmp; - tmp = minmax[0]; - minmax[0] = minmax[1]; - minmax[1] = tmp; - } - - // convert to pixels - x = ax[0].p2c(x), - y = ax[1].p2c(y), - upper = ax[e].p2c(upper); - lower = ax[e].p2c(lower); - minmax[0] = ax[e].p2c(minmax[0]); - minmax[1] = ax[e].p2c(minmax[1]); - - //same style as points by default - var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth, - sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize; - - //shadow as for points - if (lw > 0 && sw > 0) { - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax); - } - - ctx.strokeStyle = err[e].color? err[e].color: s.color; - ctx.lineWidth = lw; - //draw it - drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax); - } - } - } - } - - function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){ - - //shadow offset - y += offset; - upper += offset; - lower += offset; - - // error bar - avoid plotting over circles - if (err.err == 'x'){ - if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]); - else drawUpper = false; - if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] ); - else drawLower = false; - } - else { - if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] ); - else drawUpper = false; - if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] ); - else drawLower = false; - } - - //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps - //this is a way to get errorbars on lines without visible connecting dots - radius = err.radius != null? err.radius: radius; - - // upper cap - if (drawUpper) { - if (err.upperCap == '-'){ - if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] ); - else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] ); - } else if ($.isFunction(err.upperCap)){ - if (err.err=='x') err.upperCap(ctx, upper, y, radius); - else err.upperCap(ctx, x, upper, radius); - } - } - // lower cap - if (drawLower) { - if (err.lowerCap == '-'){ - if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] ); - else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] ); - } else if ($.isFunction(err.lowerCap)){ - if (err.err=='x') err.lowerCap(ctx, lower, y, radius); - else err.lowerCap(ctx, x, lower, radius); - } - } - } - - function drawPath(ctx, pts){ - ctx.beginPath(); - ctx.moveTo(pts[0][0], pts[0][1]); - for (var p=1; p < pts.length; p++) - ctx.lineTo(pts[p][0], pts[p][1]); - ctx.stroke(); - } - - function draw(plot, ctx){ - var plotOffset = plot.getPlotOffset(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - $.each(plot.getData(), function (i, s) { - if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) - drawSeriesErrors(plot, ctx, s); - }); - ctx.restore(); - } - - function init(plot) { - plot.hooks.processRawData.push(processRawData); - plot.hooks.draw.push(draw); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'errorbars', - version: '1.0' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.fillbetween.js b/src/legacy/ui/public/flot-charts/jquery.flot.fillbetween.js deleted file mode 100644 index 18b15d26db8c9..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.fillbetween.js +++ /dev/null @@ -1,226 +0,0 @@ -/* Flot plugin for computing bottoms for filled line and bar charts. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The case: you've got two series that you want to fill the area between. In Flot -terms, you need to use one as the fill bottom of the other. You can specify the -bottom of each data point as the third coordinate manually, or you can use this -plugin to compute it for you. - -In order to name the other series, you need to give it an id, like this: - - var dataset = [ - { data: [ ... ], id: "foo" } , // use default bottom - { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom - ]; - - $.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }}); - -As a convenience, if the id given is a number that doesn't appear as an id in -the series, it is interpreted as the index in the array instead (so fillBetween: -0 can also mean the first series). - -Internally, the plugin modifies the datapoints in each series. For line series, -extra data points might be inserted through interpolation. Note that at points -where the bottom line is not defined (due to a null point or start/end of line), -the current line will show a gap too. The algorithm comes from the -jquery.flot.stack.js plugin, possibly some code could be shared. - -*/ - -(function ( $ ) { - - var options = { - series: { - fillBetween: null // or number - } - }; - - function init( plot ) { - - function findBottomSeries( s, allseries ) { - - var i; - - for ( i = 0; i < allseries.length; ++i ) { - if ( allseries[ i ].id === s.fillBetween ) { - return allseries[ i ]; - } - } - - if ( typeof s.fillBetween === "number" ) { - if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) { - return null; - } - return allseries[ s.fillBetween ]; - } - - return null; - } - - function computeFillBottoms( plot, s, datapoints ) { - - if ( s.fillBetween == null ) { - return; - } - - var other = findBottomSeries( s, plot.getData() ); - - if ( !other ) { - return; - } - - var ps = datapoints.pointsize, - points = datapoints.points, - otherps = other.datapoints.pointsize, - otherpoints = other.datapoints.points, - newpoints = [], - px, py, intery, qx, qy, bottom, - withlines = s.lines.show, - withbottom = ps > 2 && datapoints.format[2].y, - withsteps = withlines && s.lines.steps, - fromgap = true, - i = 0, - j = 0, - l, m; - - while ( true ) { - - if ( i >= points.length ) { - break; - } - - l = newpoints.length; - - if ( points[ i ] == null ) { - - // copy gaps - - for ( m = 0; m < ps; ++m ) { - newpoints.push( points[ i + m ] ); - } - - i += ps; - - } else if ( j >= otherpoints.length ) { - - // for lines, we can't use the rest of the points - - if ( !withlines ) { - for ( m = 0; m < ps; ++m ) { - newpoints.push( points[ i + m ] ); - } - } - - i += ps; - - } else if ( otherpoints[ j ] == null ) { - - // oops, got a gap - - for ( m = 0; m < ps; ++m ) { - newpoints.push( null ); - } - - fromgap = true; - j += otherps; - - } else { - - // cases where we actually got two points - - px = points[ i ]; - py = points[ i + 1 ]; - qx = otherpoints[ j ]; - qy = otherpoints[ j + 1 ]; - bottom = 0; - - if ( px === qx ) { - - for ( m = 0; m < ps; ++m ) { - newpoints.push( points[ i + m ] ); - } - - //newpoints[ l + 1 ] += qy; - bottom = qy; - - i += ps; - j += otherps; - - } else if ( px > qx ) { - - // we got past point below, might need to - // insert interpolated extra point - - if ( withlines && i > 0 && points[ i - ps ] != null ) { - intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px ); - newpoints.push( qx ); - newpoints.push( intery ); - for ( m = 2; m < ps; ++m ) { - newpoints.push( points[ i + m ] ); - } - bottom = qy; - } - - j += otherps; - - } else { // px < qx - - // if we come from a gap, we just skip this point - - if ( fromgap && withlines ) { - i += ps; - continue; - } - - for ( m = 0; m < ps; ++m ) { - newpoints.push( points[ i + m ] ); - } - - // we might be able to interpolate a point below, - // this can give us a better y - - if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) { - bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx ); - } - - //newpoints[l + 1] += bottom; - - i += ps; - } - - fromgap = false; - - if ( l !== newpoints.length && withbottom ) { - newpoints[ l + 2 ] = bottom; - } - } - - // maintain the line steps invariant - - if ( withsteps && l !== newpoints.length && l > 0 && - newpoints[ l ] !== null && - newpoints[ l ] !== newpoints[ l - ps ] && - newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) { - for (m = 0; m < ps; ++m) { - newpoints[ l + ps + m ] = newpoints[ l + m ]; - } - newpoints[ l + 1 ] = newpoints[ l - ps + 1 ]; - } - } - - datapoints.points = newpoints; - } - - plot.hooks.processDatapoints.push( computeFillBottoms ); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: "fillbetween", - version: "1.0" - }); - -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.image.js b/src/legacy/ui/public/flot-charts/jquery.flot.image.js deleted file mode 100644 index 178f0e69069ef..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.image.js +++ /dev/null @@ -1,241 +0,0 @@ -/* Flot plugin for plotting images. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and -(x2, y2) are where you intend the two opposite corners of the image to end up -in the plot. Image must be a fully loaded JavaScript image (you can make one -with new Image()). If the image is not complete, it's skipped when plotting. - -There are two helpers included for retrieving images. The easiest work the way -that you put in URLs instead of images in the data, like this: - - [ "myimage.png", 0, 0, 10, 10 ] - -Then call $.plot.image.loadData( data, options, callback ) where data and -options are the same as you pass in to $.plot. This loads the images, replaces -the URLs in the data with the corresponding images and calls "callback" when -all images are loaded (or failed loading). In the callback, you can then call -$.plot with the data set. See the included example. - -A more low-level helper, $.plot.image.load(urls, callback) is also included. -Given a list of URLs, it calls callback with an object mapping from URL to -Image object when all images are loaded or have failed loading. - -The plugin supports these options: - - series: { - images: { - show: boolean - anchor: "corner" or "center" - alpha: [ 0, 1 ] - } - } - -They can be specified for a specific series: - - $.plot( $("#placeholder"), [{ - data: [ ... ], - images: { ... } - ]) - -Note that because the data format is different from usual data points, you -can't use images with anything else in a specific data series. - -Setting "anchor" to "center" causes the pixels in the image to be anchored at -the corner pixel centers inside of at the pixel corners, effectively letting -half a pixel stick out to each side in the plot. - -A possible future direction could be support for tiling for large images (like -Google Maps). - -*/ - -(function ($) { - var options = { - series: { - images: { - show: false, - alpha: 1, - anchor: "corner" // or "center" - } - } - }; - - $.plot.image = {}; - - $.plot.image.loadDataImages = function (series, options, callback) { - var urls = [], points = []; - - var defaultShow = options.series.images.show; - - $.each(series, function (i, s) { - if (!(defaultShow || s.images.show)) - return; - - if (s.data) - s = s.data; - - $.each(s, function (i, p) { - if (typeof p[0] == "string") { - urls.push(p[0]); - points.push(p); - } - }); - }); - - $.plot.image.load(urls, function (loadedImages) { - $.each(points, function (i, p) { - var url = p[0]; - if (loadedImages[url]) - p[0] = loadedImages[url]; - }); - - callback(); - }); - } - - $.plot.image.load = function (urls, callback) { - var missing = urls.length, loaded = {}; - if (missing == 0) - callback({}); - - $.each(urls, function (i, url) { - var handler = function () { - --missing; - - loaded[url] = this; - - if (missing == 0) - callback(loaded); - }; - - $('').load(handler).error(handler).attr('src', url); - }); - }; - - function drawSeries(plot, ctx, series) { - var plotOffset = plot.getPlotOffset(); - - if (!series.images || !series.images.show) - return; - - var points = series.datapoints.points, - ps = series.datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var img = points[i], - x1 = points[i + 1], y1 = points[i + 2], - x2 = points[i + 3], y2 = points[i + 4], - xaxis = series.xaxis, yaxis = series.yaxis, - tmp; - - // actually we should check img.complete, but it - // appears to be a somewhat unreliable indicator in - // IE6 (false even after load event) - if (!img || img.width <= 0 || img.height <= 0) - continue; - - if (x1 > x2) { - tmp = x2; - x2 = x1; - x1 = tmp; - } - if (y1 > y2) { - tmp = y2; - y2 = y1; - y1 = tmp; - } - - // if the anchor is at the center of the pixel, expand the - // image by 1/2 pixel in each direction - if (series.images.anchor == "center") { - tmp = 0.5 * (x2-x1) / (img.width - 1); - x1 -= tmp; - x2 += tmp; - tmp = 0.5 * (y2-y1) / (img.height - 1); - y1 -= tmp; - y2 += tmp; - } - - // clip - if (x1 == x2 || y1 == y2 || - x1 >= xaxis.max || x2 <= xaxis.min || - y1 >= yaxis.max || y2 <= yaxis.min) - continue; - - var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; - if (x1 < xaxis.min) { - sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); - x1 = xaxis.min; - } - - if (x2 > xaxis.max) { - sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); - x2 = xaxis.max; - } - - if (y1 < yaxis.min) { - sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); - y1 = yaxis.min; - } - - if (y2 > yaxis.max) { - sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); - y2 = yaxis.max; - } - - x1 = xaxis.p2c(x1); - x2 = xaxis.p2c(x2); - y1 = yaxis.p2c(y1); - y2 = yaxis.p2c(y2); - - // the transformation may have swapped us - if (x1 > x2) { - tmp = x2; - x2 = x1; - x1 = tmp; - } - if (y1 > y2) { - tmp = y2; - y2 = y1; - y1 = tmp; - } - - tmp = ctx.globalAlpha; - ctx.globalAlpha *= series.images.alpha; - ctx.drawImage(img, - sx1, sy1, sx2 - sx1, sy2 - sy1, - x1 + plotOffset.left, y1 + plotOffset.top, - x2 - x1, y2 - y1); - ctx.globalAlpha = tmp; - } - } - - function processRawData(plot, series, data, datapoints) { - if (!series.images.show) - return; - - // format is Image, x1, y1, x2, y2 (opposite corners) - datapoints.format = [ - { required: true }, - { x: true, number: true, required: true }, - { y: true, number: true, required: true }, - { x: true, number: true, required: true }, - { y: true, number: true, required: true } - ]; - } - - function init(plot) { - plot.hooks.processRawData.push(processRawData); - plot.hooks.drawSeries.push(drawSeries); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'image', - version: '1.1' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.js b/src/legacy/ui/public/flot-charts/jquery.flot.js deleted file mode 100644 index 43db1cc3d93db..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.js +++ /dev/null @@ -1,3168 +0,0 @@ -/* JavaScript plotting library for jQuery, version 0.8.3. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -*/ - -// first an inline dependency, jquery.colorhelpers.js, we inline it here -// for convenience - -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ -(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); - -// the actual Flot code -(function($) { - - // Cache the prototype hasOwnProperty for faster access - - var hasOwnProperty = Object.prototype.hasOwnProperty; - - // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM - // operation produces the same effect as detach, i.e. removing the element - // without touching its jQuery data. - - // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+. - - if (!$.fn.detach) { - $.fn.detach = function() { - return this.each(function() { - if (this.parentNode) { - this.parentNode.removeChild( this ); - } - }); - }; - } - - /////////////////////////////////////////////////////////////////////////// - // The Canvas object is a wrapper around an HTML5 tag. - // - // @constructor - // @param {string} cls List of classes to apply to the canvas. - // @param {element} container Element onto which to append the canvas. - // - // Requiring a container is a little iffy, but unfortunately canvas - // operations don't work unless the canvas is attached to the DOM. - - function Canvas(cls, container) { - - var element = container.children("." + cls)[0]; - - if (element == null) { - - element = document.createElement("canvas"); - element.className = cls; - - $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) - .appendTo(container); - - // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas - - if (!element.getContext) { - if (window.G_vmlCanvasManager) { - element = window.G_vmlCanvasManager.initElement(element); - } else { - throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); - } - } - } - - this.element = element; - - var context = this.context = element.getContext("2d"); - - // Determine the screen's ratio of physical to device-independent - // pixels. This is the ratio between the canvas width that the browser - // advertises and the number of pixels actually present in that space. - - // The iPhone 4, for example, has a device-independent width of 320px, - // but its screen is actually 640px wide. It therefore has a pixel - // ratio of 2, while most normal devices have a ratio of 1. - - var devicePixelRatio = window.devicePixelRatio || 1, - backingStoreRatio = - context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || 1; - - this.pixelRatio = devicePixelRatio / backingStoreRatio; - - // Size the canvas to match the internal dimensions of its container - - this.resize(container.width(), container.height()); - - // Collection of HTML div layers for text overlaid onto the canvas - - this.textContainer = null; - this.text = {}; - - // Cache of text fragments and metrics, so we can avoid expensively - // re-calculating them when the plot is re-rendered in a loop. - - this._textCache = {}; - } - - // Resizes the canvas to the given dimensions. - // - // @param {number} width New width of the canvas, in pixels. - // @param {number} width New height of the canvas, in pixels. - - Canvas.prototype.resize = function(width, height) { - - if (width <= 0 || height <= 0) { - throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); - } - - var element = this.element, - context = this.context, - pixelRatio = this.pixelRatio; - - // Resize the canvas, increasing its density based on the display's - // pixel ratio; basically giving it more pixels without increasing the - // size of its element, to take advantage of the fact that retina - // displays have that many more pixels in the same advertised space. - - // Resizing should reset the state (excanvas seems to be buggy though) - - if (this.width != width) { - element.width = width * pixelRatio; - element.style.width = width + "px"; - this.width = width; - } - - if (this.height != height) { - element.height = height * pixelRatio; - element.style.height = height + "px"; - this.height = height; - } - - // Save the context, so we can reset in case we get replotted. The - // restore ensure that we're really back at the initial state, and - // should be safe even if we haven't saved the initial state yet. - - context.restore(); - context.save(); - - // Scale the coordinate space to match the display density; so even though we - // may have twice as many pixels, we still want lines and other drawing to - // appear at the same size; the extra pixels will just make them crisper. - - context.scale(pixelRatio, pixelRatio); - }; - - // Clears the entire canvas area, not including any overlaid HTML text - - Canvas.prototype.clear = function() { - this.context.clearRect(0, 0, this.width, this.height); - }; - - // Finishes rendering the canvas, including managing the text overlay. - - Canvas.prototype.render = function() { - - var cache = this._textCache; - - // For each text layer, add elements marked as active that haven't - // already been rendered, and remove those that are no longer active. - - for (var layerKey in cache) { - if (hasOwnProperty.call(cache, layerKey)) { - - var layer = this.getTextLayer(layerKey), - layerCache = cache[layerKey]; - - layer.hide(); - - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - - var positions = styleCache[key].positions; - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - if (!position.rendered) { - layer.append(position.element); - position.rendered = true; - } - } else { - positions.splice(i--, 1); - if (position.rendered) { - position.element.detach(); - } - } - } - - if (positions.length == 0) { - delete styleCache[key]; - } - } - } - } - } - - layer.show(); - } - } - }; - - // Creates (if necessary) and returns the text overlay container. - // - // @param {string} classes String of space-separated CSS classes used to - // uniquely identify the text layer. - // @return {object} The jQuery-wrapped text-layer div. - - Canvas.prototype.getTextLayer = function(classes) { - - var layer = this.text[classes]; - - // Create the text layer if it doesn't exist - - if (layer == null) { - - // Create the text layer container, if it doesn't exist - - if (this.textContainer == null) { - this.textContainer = $("
") - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0, - 'font-size': "smaller", - color: "#545454" - }) - .insertAfter(this.element); - } - - layer = this.text[classes] = $("
") - .addClass(classes) - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0 - }) - .appendTo(this.textContainer); - } - - return layer; - }; - - // Creates (if necessary) and returns a text info object. - // - // The object looks like this: - // - // { - // width: Width of the text's wrapper div. - // height: Height of the text's wrapper div. - // element: The jQuery-wrapped HTML div containing the text. - // positions: Array of positions at which this text is drawn. - // } - // - // The positions array contains objects that look like this: - // - // { - // active: Flag indicating whether the text should be visible. - // rendered: Flag indicating whether the text is currently visible. - // element: The jQuery-wrapped HTML div containing the text. - // x: X coordinate at which to draw the text. - // y: Y coordinate at which to draw the text. - // } - // - // Each position after the first receives a clone of the original element. - // - // The idea is that that the width, height, and general 'identity' of the - // text is constant no matter where it is placed; the placements are a - // secondary property. - // - // Canvas maintains a cache of recently-used text info objects; getTextInfo - // either returns the cached element or creates a new entry. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {string} text Text string to retrieve info for. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @return {object} a text info object. - - Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number or such - - text = "" + text; - - // If the font is a font-spec object, generate a CSS font definition - - if (typeof font === "object") { - textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; - } else { - textStyle = font; - } - - // Retrieve (or create) the cache for the text's layer and styles - - layerCache = this._textCache[layer]; - - if (layerCache == null) { - layerCache = this._textCache[layer] = {}; - } - - styleCache = layerCache[textStyle]; - - if (styleCache == null) { - styleCache = layerCache[textStyle] = {}; - } - - info = styleCache[text]; - - // If we can't find a matching element in our cache, create a new one - - if (info == null) { - - var element = $("
").html(text) - .css({ - position: "absolute", - 'max-width': width, - top: -9999 - }) - .appendTo(this.getTextLayer(layer)); - - if (typeof font === "object") { - element.css({ - font: textStyle, - color: font.color - }); - } else if (typeof font === "string") { - element.addClass(font); - } - - info = styleCache[text] = { - width: element.outerWidth(true), - height: element.outerHeight(true), - element: element, - positions: [] - }; - - element.detach(); - } - - return info; - }; - - // Adds a text string to the canvas text overlay. - // - // The text isn't drawn immediately; it is marked as rendering, which will - // result in its addition to the canvas on the next render pass. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number} x X coordinate at which to draw the text. - // @param {number} y Y coordinate at which to draw the text. - // @param {string} text Text string to draw. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @param {string=} halign Horizontal alignment of the text; either "left", - // "center" or "right". - // @param {string=} valign Vertical alignment of the text; either "top", - // "middle" or "bottom". - - Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { - - var info = this.getTextInfo(layer, text, font, angle, width), - positions = info.positions; - - // Tweak the div's position to match the text's alignment - - if (halign == "center") { - x -= info.width / 2; - } else if (halign == "right") { - x -= info.width; - } - - if (valign == "middle") { - y -= info.height / 2; - } else if (valign == "bottom") { - y -= info.height; - } - - // Determine whether this text already exists at this position. - // If so, mark it for inclusion in the next render pass. - - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = true; - return; - } - } - - // If the text doesn't exist at this position, create a new entry - - // For the very first position we'll re-use the original element, - // while for subsequent ones we'll clone it. - - position = { - active: true, - rendered: false, - element: positions.length ? info.element.clone() : info.element, - x: x, - y: y - }; - - positions.push(position); - - // Move the element to its final position within the container - - position.element.css({ - top: Math.round(y), - left: Math.round(x), - 'text-align': halign // In case the text wraps - }); - }; - - // Removes one or more text strings from the canvas text overlay. - // - // If no parameters are given, all text within the layer is removed. - // - // Note that the text is not immediately removed; it is simply marked as - // inactive, which will result in its removal on the next render pass. - // This avoids the performance penalty for 'clear and redraw' behavior, - // where we potentially get rid of all text on a layer, but will likely - // add back most or all of it later, as when redrawing axes, for example. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number=} x X coordinate of the text. - // @param {number=} y Y coordinate of the text. - // @param {string=} text Text string to remove. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which the text is rotated, in degrees. - // Angle is currently unused, it will be implemented in the future. - - Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { - if (text == null) { - var layerCache = this._textCache[layer]; - if (layerCache != null) { - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - var positions = styleCache[key].positions; - for (var i = 0, position; position = positions[i]; i++) { - position.active = false; - } - } - } - } - } - } - } else { - var positions = this.getTextInfo(layer, text, font, angle).positions; - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = false; - } - } - } - }; - - /////////////////////////////////////////////////////////////////////////// - // The top-level container for the entire plot. - - function Plot(placeholder, data_, options_, plugins) { - // data is on the form: - // [ series1, series2 ... ] - // where series is either just the data as [ [x1, y1], [x2, y2], ... ] - // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } - - var series = [], - options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of columns in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85, // set to 0 to avoid background - sorted: null // default to no legend sorting - }, - xaxis: { - show: null, // null = auto-detect, true = always, false = never - position: "bottom", // or "top" - mode: null, // null or "time" - font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } - color: null, // base color, labels, ticks - tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" - transform: null, // null or f: number -> number to transform axis - inverseTransform: null, // if transform is set, this should be the inverse function - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - reserveSpace: null, // whether to reserve space even if axis isn't shown - tickLength: null, // size in pixels of ticks, or "full" for whole line - alignTicksWithAxis: null, // axis number or null for no sync - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null // number or [number, "unit"] - }, - yaxis: { - autoscaleMargin: 0.02, - position: "left" // or "right" - }, - xaxes: [], - yaxes: [], - series: { - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff", - symbol: "circle" // or callback - }, - lines: { - // we don't put in show: false so we can see - // whether lines were actively disabled - lineWidth: 2, // in pixels - fill: false, - fillColor: null, - steps: false - // Omit 'zero', so we can later default its value to - // match that of the 'fill' option. - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left", // "left", "right", or "center" - horizontal: false, - zero: true - }, - shadowSize: 3, - highlightColor: null - }, - grid: { - show: true, - aboveData: false, - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - borderColor: null, // set if different from the grid color - tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" - margin: 0, // distance from the canvas edge to the grid - labelMargin: 5, // in pixels - axisMargin: 8, // in pixels - borderWidth: 2, // in pixels - minBorderMargin: null, // in pixels, null means taken from points radius - markings: null, // array of ranges or fn: axes -> array of ranges - markingsColor: "#f4f4f4", - markingsLineWidth: 2, - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - interaction: { - redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow - }, - hooks: {} - }, - surface = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - xaxes = [], yaxes = [], - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - plotWidth = 0, plotHeight = 0, - hooks = { - processOptions: [], - processRawData: [], - processDatapoints: [], - processOffset: [], - drawBackground: [], - drawSeries: [], - draw: [], - bindEvents: [], - drawOverlay: [], - shutdown: [] - }, - plot = this; - - // public functions - plot.setData = setData; - plot.setupGrid = setupGrid; - plot.draw = draw; - plot.getPlaceholder = function() { return placeholder; }; - plot.getCanvas = function() { return surface.element; }; - plot.getPlotOffset = function() { return plotOffset; }; - plot.width = function () { return plotWidth; }; - plot.height = function () { return plotHeight; }; - plot.offset = function () { - var o = eventHolder.offset(); - o.left += plotOffset.left; - o.top += plotOffset.top; - return o; - }; - plot.getData = function () { return series; }; - plot.getAxes = function () { - var res = {}, i; - $.each(xaxes.concat(yaxes), function (_, axis) { - if (axis) - res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; - }); - return res; - }; - plot.getXAxes = function () { return xaxes; }; - plot.getYAxes = function () { return yaxes; }; - plot.c2p = canvasToAxisCoords; - plot.p2c = axisToCanvasCoords; - plot.getOptions = function () { return options; }; - plot.highlight = highlight; - plot.unhighlight = unhighlight; - plot.triggerRedrawOverlay = triggerRedrawOverlay; - plot.pointOffset = function(point) { - return { - left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), - top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) - }; - }; - plot.shutdown = shutdown; - plot.destroy = function () { - shutdown(); - placeholder.removeData("plot").empty(); - - series = []; - options = null; - surface = null; - overlay = null; - eventHolder = null; - ctx = null; - octx = null; - xaxes = []; - yaxes = []; - hooks = null; - highlights = []; - plot = null; - }; - plot.resize = function () { - var width = placeholder.width(), - height = placeholder.height(); - surface.resize(width, height); - overlay.resize(width, height); - }; - - // public attributes - plot.hooks = hooks; - - // initialize - initPlugins(plot); - parseOptions(options_); - setupCanvases(); - setData(data_); - setupGrid(); - draw(); - bindEvents(); - - - function executeHooks(hook, args) { - args = [plot].concat(args); - for (var i = 0; i < hook.length; ++i) - hook[i].apply(this, args); - } - - function initPlugins() { - - // References to key classes, allowing plugins to modify them - - var classes = { - Canvas: Canvas - }; - - for (var i = 0; i < plugins.length; ++i) { - var p = plugins[i]; - p.init(plot, classes); - if (p.options) - $.extend(true, options, p.options); - } - } - - function parseOptions(opts) { - - $.extend(true, options, opts); - - // $.extend merges arrays, rather than replacing them. When less - // colors are provided than the size of the default palette, we - // end up with those colors plus the remaining defaults, which is - // not expected behavior; avoid it by replacing them here. - - if (opts && opts.colors) { - options.colors = opts.colors; - } - - if (options.xaxis.color == null) - options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - if (options.yaxis.color == null) - options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility - options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; - if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility - options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; - - if (options.grid.borderColor == null) - options.grid.borderColor = options.grid.color; - if (options.grid.tickColor == null) - options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - // Fill in defaults for axis options, including any unspecified - // font-spec fields, if a font-spec was provided. - - // If no x/y axis options were provided, create one of each anyway, - // since the rest of the code assumes that they exist. - - var i, axisOptions, axisCount, - fontSize = placeholder.css("font-size"), - fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, - fontDefaults = { - style: placeholder.css("font-style"), - size: Math.round(0.8 * fontSizeDefault), - variant: placeholder.css("font-variant"), - weight: placeholder.css("font-weight"), - family: placeholder.css("font-family") - }; - - axisCount = options.xaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.xaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.xaxis, axisOptions); - options.xaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - axisCount = options.yaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.yaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.yaxis, axisOptions); - options.yaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - // backwards compatibility, to be removed in future - if (options.xaxis.noTicks && options.xaxis.ticks == null) - options.xaxis.ticks = options.xaxis.noTicks; - if (options.yaxis.noTicks && options.yaxis.ticks == null) - options.yaxis.ticks = options.yaxis.noTicks; - if (options.x2axis) { - options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); - options.xaxes[1].position = "top"; - // Override the inherit to allow the axis to auto-scale - if (options.x2axis.min == null) { - options.xaxes[1].min = null; - } - if (options.x2axis.max == null) { - options.xaxes[1].max = null; - } - } - if (options.y2axis) { - options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); - options.yaxes[1].position = "right"; - // Override the inherit to allow the axis to auto-scale - if (options.y2axis.min == null) { - options.yaxes[1].min = null; - } - if (options.y2axis.max == null) { - options.yaxes[1].max = null; - } - } - if (options.grid.coloredAreas) - options.grid.markings = options.grid.coloredAreas; - if (options.grid.coloredAreasColor) - options.grid.markingsColor = options.grid.coloredAreasColor; - if (options.lines) - $.extend(true, options.series.lines, options.lines); - if (options.points) - $.extend(true, options.series.points, options.points); - if (options.bars) - $.extend(true, options.series.bars, options.bars); - if (options.shadowSize != null) - options.series.shadowSize = options.shadowSize; - if (options.highlightColor != null) - options.series.highlightColor = options.highlightColor; - - // save options on axes for future reference - for (i = 0; i < options.xaxes.length; ++i) - getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; - for (i = 0; i < options.yaxes.length; ++i) - getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; - - // add hooks from options - for (var n in hooks) - if (options.hooks[n] && options.hooks[n].length) - hooks[n] = hooks[n].concat(options.hooks[n]); - - executeHooks(hooks.processOptions, [options]); - } - - function setData(d) { - series = parseData(d); - fillInSeriesOptions(); - processData(); - } - - function parseData(d) { - var res = []; - for (var i = 0; i < d.length; ++i) { - var s = $.extend(true, {}, options.series); - - if (d[i].data != null) { - s.data = d[i].data; // move the data instead of deep-copy - delete d[i].data; - - $.extend(true, s, d[i]); - - d[i].data = s.data; - } - else - s.data = d[i]; - res.push(s); - } - - return res; - } - - function axisNumber(obj, coord) { - var a = obj[coord + "axis"]; - if (typeof a == "object") // if we got a real axis, extract number - a = a.n; - if (typeof a != "number") - a = 1; // default to first axis - return a; - } - - function allAxes() { - // return flat array without annoying null entries - return $.grep(xaxes.concat(yaxes), function (a) { return a; }); - } - - function canvasToAxisCoords(pos) { - // return an object with x/y corresponding to all used axes - var res = {}, i, axis; - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) - res["x" + axis.n] = axis.c2p(pos.left); - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) - res["y" + axis.n] = axis.c2p(pos.top); - } - - if (res.x1 !== undefined) - res.x = res.x1; - if (res.y1 !== undefined) - res.y = res.y1; - - return res; - } - - function axisToCanvasCoords(pos) { - // get canvas coords from the first pair of x/y found in pos - var res = {}, i, axis, key; - - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) { - key = "x" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "x"; - - if (pos[key] != null) { - res.left = axis.p2c(pos[key]); - break; - } - } - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) { - key = "y" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "y"; - - if (pos[key] != null) { - res.top = axis.p2c(pos[key]); - break; - } - } - } - - return res; - } - - function getOrCreateAxis(axes, number) { - if (!axes[number - 1]) - axes[number - 1] = { - n: number, // save the number for future reference - direction: axes == xaxes ? "x" : "y", - options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) - }; - - return axes[number - 1]; - } - - function fillInSeriesOptions() { - - var neededColors = series.length, maxIndex = -1, i; - - // Subtract the number of series that already have fixed colors or - // color indexes from the number that we still need to generate. - - for (i = 0; i < series.length; ++i) { - var sc = series[i].color; - if (sc != null) { - neededColors--; - if (typeof sc == "number" && sc > maxIndex) { - maxIndex = sc; - } - } - } - - // If any of the series have fixed color indexes, then we need to - // generate at least as many colors as the highest index. - - if (neededColors <= maxIndex) { - neededColors = maxIndex + 1; - } - - // Generate all the colors, using first the option colors and then - // variations on those colors once they're exhausted. - - var c, colors = [], colorPool = options.colors, - colorPoolSize = colorPool.length, variation = 0; - - for (i = 0; i < neededColors; i++) { - - c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); - - // Each time we exhaust the colors in the pool we adjust - // a scaling factor used to produce more variations on - // those colors. The factor alternates negative/positive - // to produce lighter/darker colors. - - // Reset the variation after every few cycles, or else - // it will end up producing only white or black colors. - - if (i % colorPoolSize == 0 && i) { - if (variation >= 0) { - if (variation < 0.5) { - variation = -variation - 0.2; - } else variation = 0; - } else variation = -variation; - } - - colors[i] = c.scale('rgb', 1 + variation); - } - - // Finalize the series options, filling in their colors - - var colori = 0, s; - for (i = 0; i < series.length; ++i) { - s = series[i]; - - // assign colors - if (s.color == null) { - s.color = colors[colori].toString(); - ++colori; - } - else if (typeof s.color == "number") - s.color = colors[s.color].toString(); - - // turn on lines automatically in case nothing is set - if (s.lines.show == null) { - var v, show = true; - for (v in s) - if (s[v] && s[v].show) { - show = false; - break; - } - if (show) - s.lines.show = true; - } - - // If nothing was provided for lines.zero, default it to match - // lines.fill, since areas by default should extend to zero. - - if (s.lines.zero == null) { - s.lines.zero = !!s.lines.fill; - } - - // setup axes - s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); - s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); - } - } - - function processData() { - var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY, - fakeInfinity = Number.MAX_VALUE, - i, j, k, m, length, - s, points, ps, x, y, axis, val, f, p, - data, format; - - function updateAxis(axis, min, max) { - if (min < axis.datamin && min != -fakeInfinity) - axis.datamin = min; - if (max > axis.datamax && max != fakeInfinity) - axis.datamax = max; - } - - $.each(allAxes(), function (_, axis) { - // init axis - axis.datamin = topSentry; - axis.datamax = bottomSentry; - axis.used = false; - }); - - for (i = 0; i < series.length; ++i) { - s = series[i]; - s.datapoints = { points: [] }; - - executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); - } - - // first pass: clean and copy data - for (i = 0; i < series.length; ++i) { - s = series[i]; - - data = s.data; - format = s.datapoints.format; - - if (!format) { - format = []; - // find out how to copy - format.push({ x: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - - if (s.bars.show || (s.lines.show && s.lines.fill)) { - var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); - format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); - if (s.bars.horizontal) { - delete format[format.length - 1].y; - format[format.length - 1].x = true; - } - } - - s.datapoints.format = format; - } - - if (s.datapoints.pointsize != null) - continue; // already filled in - - s.datapoints.pointsize = format.length; - - ps = s.datapoints.pointsize; - points = s.datapoints.points; - - var insertSteps = s.lines.show && s.lines.steps; - s.xaxis.used = s.yaxis.used = true; - - for (j = k = 0; j < data.length; ++j, k += ps) { - p = data[j]; - - var nullify = p == null; - if (!nullify) { - for (m = 0; m < ps; ++m) { - val = p[m]; - f = format[m]; - - if (f) { - if (f.number && val != null) { - val = +val; // convert to number - if (isNaN(val)) - val = null; - else if (val == Infinity) - val = fakeInfinity; - else if (val == -Infinity) - val = -fakeInfinity; - } - - if (val == null) { - if (f.required) - nullify = true; - - if (f.defaultValue != null) - val = f.defaultValue; - } - } - - points[k + m] = val; - } - } - - if (nullify) { - for (m = 0; m < ps; ++m) { - val = points[k + m]; - if (val != null) { - f = format[m]; - // extract min/max info - if (f.autoscale !== false) { - if (f.x) { - updateAxis(s.xaxis, val, val); - } - if (f.y) { - updateAxis(s.yaxis, val, val); - } - } - } - points[k + m] = null; - } - } - else { - // a little bit of line specific stuff that - // perhaps shouldn't be here, but lacking - // better means... - if (insertSteps && k > 0 - && points[k - ps] != null - && points[k - ps] != points[k] - && points[k - ps + 1] != points[k + 1]) { - // copy the point to make room for a middle point - for (m = 0; m < ps; ++m) - points[k + ps + m] = points[k + m]; - - // middle point has same y - points[k + 1] = points[k - ps + 1]; - - // we've added a point, better reflect that - k += ps; - } - } - } - } - - // give the hooks a chance to run - for (i = 0; i < series.length; ++i) { - s = series[i]; - - executeHooks(hooks.processDatapoints, [ s, s.datapoints]); - } - - // second pass: find datamax/datamin for auto-scaling - for (i = 0; i < series.length; ++i) { - s = series[i]; - points = s.datapoints.points; - ps = s.datapoints.pointsize; - format = s.datapoints.format; - - var xmin = topSentry, ymin = topSentry, - xmax = bottomSentry, ymax = bottomSentry; - - for (j = 0; j < points.length; j += ps) { - if (points[j] == null) - continue; - - for (m = 0; m < ps; ++m) { - val = points[j + m]; - f = format[m]; - if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) - continue; - - if (f.x) { - if (val < xmin) - xmin = val; - if (val > xmax) - xmax = val; - } - if (f.y) { - if (val < ymin) - ymin = val; - if (val > ymax) - ymax = val; - } - } - } - - if (s.bars.show) { - // make sure we got room for the bar on the dancing floor - var delta; - - switch (s.bars.align) { - case "left": - delta = 0; - break; - case "right": - delta = -s.bars.barWidth; - break; - default: - delta = -s.bars.barWidth / 2; - } - - if (s.bars.horizontal) { - ymin += delta; - ymax += delta + s.bars.barWidth; - } - else { - xmin += delta; - xmax += delta + s.bars.barWidth; - } - } - - updateAxis(s.xaxis, xmin, xmax); - updateAxis(s.yaxis, ymin, ymax); - } - - $.each(allAxes(), function (_, axis) { - if (axis.datamin == topSentry) - axis.datamin = null; - if (axis.datamax == bottomSentry) - axis.datamax = null; - }); - } - - function setupCanvases() { - - // Make sure the placeholder is clear of everything except canvases - // from a previous plot in this container that we'll try to re-use. - - placeholder.css("padding", 0) // padding messes up the positioning - .children().filter(function(){ - return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); - }).remove(); - - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - - surface = new Canvas("flot-base", placeholder); - overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features - - ctx = surface.context; - octx = overlay.context; - - // define which element we're listening for events on - eventHolder = $(overlay.element).unbind(); - - // If we're re-using a plot object, shut down the old one - - var existing = placeholder.data("plot"); - - if (existing) { - existing.shutdown(); - overlay.clear(); - } - - // save in case we get replotted - placeholder.data("plot", plot); - } - - function bindEvents() { - // bind events - if (options.grid.hoverable) { - eventHolder.mousemove(onMouseMove); - - // Use bind, rather than .mouseleave, because we officially - // still support jQuery 1.2.6, which doesn't define a shortcut - // for mouseenter or mouseleave. This was a bug/oversight that - // was fixed somewhere around 1.3.x. We can return to using - // .mouseleave when we drop support for 1.2.6. - - eventHolder.bind("mouseleave", onMouseLeave); - } - - if (options.grid.clickable) - eventHolder.click(onClick); - - executeHooks(hooks.bindEvents, [eventHolder]); - } - - function shutdown() { - if (redrawTimeout) - clearTimeout(redrawTimeout); - - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mouseleave", onMouseLeave); - eventHolder.unbind("click", onClick); - - executeHooks(hooks.shutdown, [eventHolder]); - } - - function setTransformationHelpers(axis) { - // set helper functions on the axis, assumes plot area - // has been computed already - - function identity(x) { return x; } - - var s, m, t = axis.options.transform || identity, - it = axis.options.inverseTransform; - - // precompute how much the axis is scaling a point - // in canvas space - if (axis.direction == "x") { - s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); - m = Math.min(t(axis.max), t(axis.min)); - } - else { - s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); - s = -s; - m = Math.max(t(axis.max), t(axis.min)); - } - - // data point to canvas coordinate - if (t == identity) // slight optimization - axis.p2c = function (p) { return (p - m) * s; }; - else - axis.p2c = function (p) { return (t(p) - m) * s; }; - // canvas coordinate to data point - if (!it) - axis.c2p = function (c) { return m + c / s; }; - else - axis.c2p = function (c) { return it(m + c / s); }; - } - - function measureTickLabels(axis) { - - var opts = axis.options, - ticks = axis.ticks || [], - labelWidth = opts.labelWidth || 0, - labelHeight = opts.labelHeight || 0, - maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = opts.font || "flot-tick-label tickLabel"; - - for (var i = 0; i < ticks.length; ++i) { - - var t = ticks[i]; - - if (!t.label) - continue; - - var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - - labelWidth = Math.max(labelWidth, info.width); - labelHeight = Math.max(labelHeight, info.height); - } - - axis.labelWidth = opts.labelWidth || labelWidth; - axis.labelHeight = opts.labelHeight || labelHeight; - } - - function allocateAxisBoxFirstPhase(axis) { - // find the bounding box of the axis by looking at label - // widths/heights and ticks, make room by diminishing the - // plotOffset; this first phase only looks at one - // dimension per axis, the other dimension depends on the - // other axes so will have to wait - - var lw = axis.labelWidth, - lh = axis.labelHeight, - pos = axis.options.position, - isXAxis = axis.direction === "x", - tickLength = axis.options.tickLength, - axisMargin = options.grid.axisMargin, - padding = options.grid.labelMargin, - innermost = true, - outermost = true, - first = true, - found = false; - - // Determine the axis's position in its direction and on its side - - $.each(isXAxis ? xaxes : yaxes, function(i, a) { - if (a && (a.show || a.reserveSpace)) { - if (a === axis) { - found = true; - } else if (a.options.position === pos) { - if (found) { - outermost = false; - } else { - innermost = false; - } - } - if (!found) { - first = false; - } - } - }); - - // The outermost axis on each side has no margin - - if (outermost) { - axisMargin = 0; - } - - // The ticks for the first axis in each direction stretch across - - if (tickLength == null) { - tickLength = first ? "full" : 5; - } - - if (!isNaN(+tickLength)) - padding += +tickLength; - - if (isXAxis) { - lh += padding; - - if (pos == "bottom") { - plotOffset.bottom += lh + axisMargin; - axis.box = { top: surface.height - plotOffset.bottom, height: lh }; - } - else { - axis.box = { top: plotOffset.top + axisMargin, height: lh }; - plotOffset.top += lh + axisMargin; - } - } - else { - lw += padding; - - if (pos == "left") { - axis.box = { left: plotOffset.left + axisMargin, width: lw }; - plotOffset.left += lw + axisMargin; - } - else { - plotOffset.right += lw + axisMargin; - axis.box = { left: surface.width - plotOffset.right, width: lw }; - } - } - - // save for future reference - axis.position = pos; - axis.tickLength = tickLength; - axis.box.padding = padding; - axis.innermost = innermost; - } - - function allocateAxisBoxSecondPhase(axis) { - // now that all axis boxes have been placed in one - // dimension, we can set the remaining dimension coordinates - if (axis.direction == "x") { - axis.box.left = plotOffset.left - axis.labelWidth / 2; - axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; - } - else { - axis.box.top = plotOffset.top - axis.labelHeight / 2; - axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; - } - } - - function adjustLayoutForThingsStickingOut() { - // possibly adjust plot offset to ensure everything stays - // inside the canvas and isn't clipped off - - var minMargin = options.grid.minBorderMargin, - axis, i; - - // check stuff from the plot (FIXME: this should just read - // a value from the series, otherwise it's impossible to - // customize) - if (minMargin == null) { - minMargin = 0; - for (i = 0; i < series.length; ++i) - minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); - } - - var margins = { - left: minMargin, - right: minMargin, - top: minMargin, - bottom: minMargin - }; - - // check axis labels, note we don't check the actual - // labels but instead use the overall width/height to not - // jump as much around with replots - $.each(allAxes(), function (_, axis) { - if (axis.reserveSpace && axis.ticks && axis.ticks.length) { - if (axis.direction === "x") { - margins.left = Math.max(margins.left, axis.labelWidth / 2); - margins.right = Math.max(margins.right, axis.labelWidth / 2); - } else { - margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); - margins.top = Math.max(margins.top, axis.labelHeight / 2); - } - } - }); - - plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); - plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); - plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); - plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); - } - - function setupGrid() { - var i, axes = allAxes(), showGrid = options.grid.show; - - // Initialize the plot's offset from the edge of the canvas - - for (var a in plotOffset) { - var margin = options.grid.margin || 0; - plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; - } - - executeHooks(hooks.processOffset, [plotOffset]); - - // If the grid is visible, add its border width to the offset - - for (var a in plotOffset) { - if(typeof(options.grid.borderWidth) == "object") { - plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; - } - else { - plotOffset[a] += showGrid ? options.grid.borderWidth : 0; - } - } - - $.each(axes, function (_, axis) { - var axisOpts = axis.options; - axis.show = axisOpts.show == null ? axis.used : axisOpts.show; - axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace; - setRange(axis); - }); - - if (showGrid) { - - var allocatedAxes = $.grep(axes, function (axis) { - return axis.show || axis.reserveSpace; - }); - - $.each(allocatedAxes, function (_, axis) { - // make the ticks - setupTickGeneration(axis); - setTicks(axis); - snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis - measureTickLabels(axis); - }); - - // with all dimensions calculated, we can compute the - // axis bounding boxes, start from the outside - // (reverse order) - for (i = allocatedAxes.length - 1; i >= 0; --i) - allocateAxisBoxFirstPhase(allocatedAxes[i]); - - // make sure we've got enough space for things that - // might stick out - adjustLayoutForThingsStickingOut(); - - $.each(allocatedAxes, function (_, axis) { - allocateAxisBoxSecondPhase(axis); - }); - } - - plotWidth = surface.width - plotOffset.left - plotOffset.right; - plotHeight = surface.height - plotOffset.bottom - plotOffset.top; - - // now we got the proper plot dimensions, we can compute the scaling - $.each(axes, function (_, axis) { - setTransformationHelpers(axis); - }); - - if (showGrid) { - drawAxisLabels(); - } - - insertLegend(); - } - - function setRange(axis) { - var opts = axis.options, - min = +(opts.min != null ? opts.min : axis.datamin), - max = +(opts.max != null ? opts.max : axis.datamax), - delta = max - min; - - if (delta == 0.0) { - // degenerate case - var widen = max == 0 ? 1 : 0.01; - - if (opts.min == null) - min -= widen; - // always widen max if we couldn't widen min to ensure we - // don't fall into min == max which doesn't work - if (opts.max == null || opts.min != null) - max += widen; - } - else { - // consider autoscaling - var margin = opts.autoscaleMargin; - if (margin != null) { - if (opts.min == null) { - min -= delta * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin != null && axis.datamin >= 0) - min = 0; - } - if (opts.max == null) { - max += delta * margin; - if (max > 0 && axis.datamax != null && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - } - - function setupTickGeneration(axis) { - var opts = axis.options; - - // estimate number of ticks - var noTicks; - if (typeof opts.ticks == "number" && opts.ticks > 0) - noTicks = opts.ticks; - else - // heuristic based on the model a*sqrt(x) fitted to - // some data points that seemed reasonable - noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); - - var delta = (axis.max - axis.min) / noTicks, - dec = -Math.floor(Math.log(delta) / Math.LN10), - maxDec = opts.tickDecimals; - - if (maxDec != null && dec > maxDec) { - dec = maxDec; - } - - var magn = Math.pow(10, -dec), - norm = delta / magn, // norm is between 1.0 and 10.0 - size; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - - if (opts.minTickSize != null && size < opts.minTickSize) { - size = opts.minTickSize; - } - - axis.delta = delta; - axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); - axis.tickSize = opts.tickSize || size; - - // Time mode was moved to a plug-in in 0.8, and since so many people use it - // we'll add an especially friendly reminder to make sure they included it. - - if (opts.mode == "time" && !axis.tickGenerator) { - throw new Error("Time mode requires the flot.time plugin."); - } - - // Flot supports base-10 axes; any other mode else is handled by a plug-in, - // like flot.time.js. - - if (!axis.tickGenerator) { - - axis.tickGenerator = function (axis) { - - var ticks = [], - start = floorInBase(axis.min, axis.tickSize), - i = 0, - v = Number.NaN, - prev; - - do { - prev = v; - v = start + i * axis.tickSize; - ticks.push(v); - ++i; - } while (v < axis.max && v != prev); - return ticks; - }; - - axis.tickFormatter = function (value, axis) { - - var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; - var formatted = "" + Math.round(value * factor) / factor; - - // If tickDecimals was specified, ensure that we have exactly that - // much precision; otherwise default to the value's own precision. - - if (axis.tickDecimals != null) { - var decimal = formatted.indexOf("."); - var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; - if (precision < axis.tickDecimals) { - return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); - } - } - - return formatted; - }; - } - - if ($.isFunction(opts.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; - - if (opts.alignTicksWithAxis != null) { - var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; - if (otherAxis && otherAxis.used && otherAxis != axis) { - // consider snapping min/max to outermost nice ticks - var niceTicks = axis.tickGenerator(axis); - if (niceTicks.length > 0) { - if (opts.min == null) - axis.min = Math.min(axis.min, niceTicks[0]); - if (opts.max == null && niceTicks.length > 1) - axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); - } - - axis.tickGenerator = function (axis) { - // copy ticks, scaled to this axis - var ticks = [], v, i; - for (i = 0; i < otherAxis.ticks.length; ++i) { - v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); - v = axis.min + v * (axis.max - axis.min); - ticks.push(v); - } - return ticks; - }; - - // we might need an extra decimal since forced - // ticks don't necessarily fit naturally - if (!axis.mode && opts.tickDecimals == null) { - var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), - ts = axis.tickGenerator(axis); - - // only proceed if the tick interval rounded - // with an extra decimal doesn't give us a - // zero at end - if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) - axis.tickDecimals = extraDec; - } - } - } - } - - function setTicks(axis) { - var oticks = axis.options.ticks, ticks = []; - if (oticks == null || (typeof oticks == "number" && oticks > 0)) - ticks = axis.tickGenerator(axis); - else if (oticks) { - if ($.isFunction(oticks)) - // generate the ticks - ticks = oticks(axis); - else - ticks = oticks; - } - - // clean up/labelify the supplied ticks, copy them over - var i, v; - axis.ticks = []; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = +t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = +t; - if (label == null) - label = axis.tickFormatter(v, axis); - if (!isNaN(v)) - axis.ticks.push({ v: v, label: label }); - } - } - - function snapRangeToTicks(axis, ticks) { - if (axis.options.autoscaleMargin && ticks.length > 0) { - // snap to ticks - if (axis.options.min == null) - axis.min = Math.min(axis.min, ticks[0].v); - if (axis.options.max == null && ticks.length > 1) - axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); - } - } - - function draw() { - - surface.clear(); - - executeHooks(hooks.drawBackground, [ctx]); - - var grid = options.grid; - - // draw background, if any - if (grid.show && grid.backgroundColor) - drawBackground(); - - if (grid.show && !grid.aboveData) { - drawGrid(); - } - - for (var i = 0; i < series.length; ++i) { - executeHooks(hooks.drawSeries, [ctx, series[i]]); - drawSeries(series[i]); - } - - executeHooks(hooks.draw, [ctx]); - - if (grid.show && grid.aboveData) { - drawGrid(); - } - - surface.render(); - - // A draw implies that either the axes or data have changed, so we - // should probably update the overlay highlights as well. - - triggerRedrawOverlay(); - } - - function extractRange(ranges, coord) { - var axis, from, to, key, axes = allAxes(); - - for (var i = 0; i < axes.length; ++i) { - axis = axes[i]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? xaxes[0] : yaxes[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function drawBackground() { - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); - ctx.fillRect(0, 0, plotWidth, plotHeight); - ctx.restore(); - } - - function drawGrid() { - var i, axes, bw, bc; - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // draw markings - var markings = options.grid.markings; - if (markings) { - if ($.isFunction(markings)) { - axes = plot.getAxes(); - // xmin etc. is backwards compatibility, to be - // removed in the future - axes.xmin = axes.xaxis.min; - axes.xmax = axes.xaxis.max; - axes.ymin = axes.yaxis.min; - axes.ymax = axes.yaxis.max; - - markings = markings(axes); - } - - for (i = 0; i < markings.length; ++i) { - var m = markings[i], - xrange = extractRange(m, "x"), - yrange = extractRange(m, "y"); - - // fill in missing - if (xrange.from == null) - xrange.from = xrange.axis.min; - if (xrange.to == null) - xrange.to = xrange.axis.max; - if (yrange.from == null) - yrange.from = yrange.axis.min; - if (yrange.to == null) - yrange.to = yrange.axis.max; - - // clip - if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || - yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) - continue; - - xrange.from = Math.max(xrange.from, xrange.axis.min); - xrange.to = Math.min(xrange.to, xrange.axis.max); - yrange.from = Math.max(yrange.from, yrange.axis.min); - yrange.to = Math.min(yrange.to, yrange.axis.max); - - var xequal = xrange.from === xrange.to, - yequal = yrange.from === yrange.to; - - if (xequal && yequal) { - continue; - } - - // then draw - xrange.from = Math.floor(xrange.axis.p2c(xrange.from)); - xrange.to = Math.floor(xrange.axis.p2c(xrange.to)); - yrange.from = Math.floor(yrange.axis.p2c(yrange.from)); - yrange.to = Math.floor(yrange.axis.p2c(yrange.to)); - - if (xequal || yequal) { - var lineWidth = m.lineWidth || options.grid.markingsLineWidth, - subPixel = lineWidth % 2 ? 0.5 : 0; - ctx.beginPath(); - ctx.strokeStyle = m.color || options.grid.markingsColor; - ctx.lineWidth = lineWidth; - if (xequal) { - ctx.moveTo(xrange.to + subPixel, yrange.from); - ctx.lineTo(xrange.to + subPixel, yrange.to); - } else { - ctx.moveTo(xrange.from, yrange.to + subPixel); - ctx.lineTo(xrange.to, yrange.to + subPixel); - } - ctx.stroke(); - } else { - ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(xrange.from, yrange.to, - xrange.to - xrange.from, - yrange.from - yrange.to); - } - } - } - - // draw the ticks - axes = allAxes(); - bw = options.grid.borderWidth; - - for (var j = 0; j < axes.length; ++j) { - var axis = axes[j], box = axis.box, - t = axis.tickLength, x, y, xoff, yoff; - if (!axis.show || axis.ticks.length == 0) - continue; - - ctx.lineWidth = 1; - - // find the edges - if (axis.direction == "x") { - x = 0; - if (t == "full") - y = (axis.position == "top" ? 0 : plotHeight); - else - y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); - } - else { - y = 0; - if (t == "full") - x = (axis.position == "left" ? 0 : plotWidth); - else - x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); - } - - // draw tick bar - if (!axis.innermost) { - ctx.strokeStyle = axis.options.color; - ctx.beginPath(); - xoff = yoff = 0; - if (axis.direction == "x") - xoff = plotWidth + 1; - else - yoff = plotHeight + 1; - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") { - y = Math.floor(y) + 0.5; - } else { - x = Math.floor(x) + 0.5; - } - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - ctx.stroke(); - } - - // draw ticks - - ctx.strokeStyle = axis.options.tickColor; - - ctx.beginPath(); - for (i = 0; i < axis.ticks.length; ++i) { - var v = axis.ticks[i].v; - - xoff = yoff = 0; - - if (isNaN(v) || v < axis.min || v > axis.max - // skip those lying on the axes if we got a border - || (t == "full" - && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) - && (v == axis.min || v == axis.max))) - continue; - - if (axis.direction == "x") { - x = axis.p2c(v); - yoff = t == "full" ? -plotHeight : t; - - if (axis.position == "top") - yoff = -yoff; - } - else { - y = axis.p2c(v); - xoff = t == "full" ? -plotWidth : t; - - if (axis.position == "left") - xoff = -xoff; - } - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") - x = Math.floor(x) + 0.5; - else - y = Math.floor(y) + 0.5; - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - } - - ctx.stroke(); - } - - - // draw border - if (bw) { - // If either borderWidth or borderColor is an object, then draw the border - // line by line instead of as one rectangle - bc = options.grid.borderColor; - if(typeof bw == "object" || typeof bc == "object") { - if (typeof bw !== "object") { - bw = {top: bw, right: bw, bottom: bw, left: bw}; - } - if (typeof bc !== "object") { - bc = {top: bc, right: bc, bottom: bc, left: bc}; - } - - if (bw.top > 0) { - ctx.strokeStyle = bc.top; - ctx.lineWidth = bw.top; - ctx.beginPath(); - ctx.moveTo(0 - bw.left, 0 - bw.top/2); - ctx.lineTo(plotWidth, 0 - bw.top/2); - ctx.stroke(); - } - - if (bw.right > 0) { - ctx.strokeStyle = bc.right; - ctx.lineWidth = bw.right; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); - ctx.lineTo(plotWidth + bw.right / 2, plotHeight); - ctx.stroke(); - } - - if (bw.bottom > 0) { - ctx.strokeStyle = bc.bottom; - ctx.lineWidth = bw.bottom; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); - ctx.lineTo(0, plotHeight + bw.bottom / 2); - ctx.stroke(); - } - - if (bw.left > 0) { - ctx.strokeStyle = bc.left; - ctx.lineWidth = bw.left; - ctx.beginPath(); - ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); - ctx.lineTo(0- bw.left/2, 0); - ctx.stroke(); - } - } - else { - ctx.lineWidth = bw; - ctx.strokeStyle = options.grid.borderColor; - ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); - } - } - - ctx.restore(); - } - - function drawAxisLabels() { - - $.each(allAxes(), function (_, axis) { - var box = axis.box, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = axis.options.font || "flot-tick-label tickLabel", - tick, x, y, halign, valign; - - // Remove text before checking for axis.show and ticks.length; - // otherwise plugins, like flot-tickrotor, that draw their own - // tick labels will end up with both theirs and the defaults. - - surface.removeText(layer); - - if (!axis.show || axis.ticks.length == 0) - return; - - for (var i = 0; i < axis.ticks.length; ++i) { - - tick = axis.ticks[i]; - if (!tick.label || tick.v < axis.min || tick.v > axis.max) - continue; - - if (axis.direction == "x") { - halign = "center"; - x = plotOffset.left + axis.p2c(tick.v); - if (axis.position == "bottom") { - y = box.top + box.padding; - } else { - y = box.top + box.height - box.padding; - valign = "bottom"; - } - } else { - valign = "middle"; - y = plotOffset.top + axis.p2c(tick.v); - if (axis.position == "left") { - x = box.left + box.width - box.padding; - halign = "right"; - } else { - x = box.left + box.padding; - } - } - - surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); - } - }); - } - - function drawSeries(series) { - if (series.lines.show) - drawSeriesLines(series); - if (series.bars.show) - drawSeriesBars(series); - if (series.points.show) - drawSeriesPoints(series); - } - - function drawSeriesLines(series) { - function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - prevx = null, prevy = null; - - ctx.beginPath(); - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1], - x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); - } - ctx.stroke(); - } - - function plotLineArea(datapoints, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - bottom = Math.min(Math.max(0, axisy.min), axisy.max), - i = 0, top, areaOpen = false, - ypos = 1, segmentStart = 0, segmentEnd = 0; - - // we process each segment in two turns, first forward - // direction to sketch out top, then once we hit the - // end we go backwards to sketch the bottom - while (true) { - if (ps > 0 && i > points.length + ps) - break; - - i += ps; // ps is negative if going backwards - - var x1 = points[i - ps], - y1 = points[i - ps + ypos], - x2 = points[i], y2 = points[i + ypos]; - - if (areaOpen) { - if (ps > 0 && x1 != null && x2 == null) { - // at turning point - segmentEnd = i; - ps = -ps; - ypos = 2; - continue; - } - - if (ps < 0 && i == segmentStart + ps) { - // done with the reverse sweep - ctx.fill(); - areaOpen = false; - ps = -ps; - ypos = 1; - i = segmentStart = segmentEnd + ps; - continue; - } - } - - if (x1 == null || x2 == null) - continue; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be a flat maxed out rectangle first, then a - // triangular cutout or reverse; to find these - // keep track of the current x values - var x1old = x1, x2old = x2; - - // clip the y values, without shortcutting, we - // go through all cases in turn - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); - // it goes to (x1, y1), but we fill that below - } - - // fill triangular section, this sometimes result - // in redundant points if (x1, y1) hasn't changed - // from previous line to, but we just ignore that - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); - } - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - var lw = series.lines.lineWidth, - sw = series.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (lw > 0 && sw > 0) { - // draw shadow as a thick and thin line with transparency - ctx.lineWidth = sw; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - // position shadow at angle from the mid of line - var angle = Math.PI/18; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); - ctx.lineWidth = sw/2; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); - if (fillStyle) { - ctx.fillStyle = fillStyle; - plotLineArea(series.datapoints, series.xaxis, series.yaxis); - } - - if (lw > 0) - plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawSeriesPoints(series) { - function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var x = points[i], y = points[i + 1]; - if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - - ctx.beginPath(); - x = axisx.p2c(x); - y = axisy.p2c(y) + offset; - if (symbol == "circle") - ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); - else - symbol(ctx, x, y, radius, shadow); - ctx.closePath(); - - if (fillStyle) { - ctx.fillStyle = fillStyle; - ctx.fill(); - } - ctx.stroke(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var lw = series.points.lineWidth, - sw = series.shadowSize, - radius = series.points.radius, - symbol = series.points.symbol; - - // If the user sets the line width to 0, we change it to a very - // small value. A line width of 0 seems to force the default of 1. - // Doing the conditional here allows the shadow setting to still be - // optional even with a lineWidth of 0. - - if( lw == 0 ) - lw = 0.0001; - - if (lw > 0 && sw > 0) { - // draw shadow in two steps - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPoints(series.datapoints, radius, null, w + w/2, true, - series.xaxis, series.yaxis, symbol); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPoints(series.datapoints, radius, null, w/2, true, - series.xaxis, series.yaxis, symbol); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - plotPoints(series.datapoints, radius, - getFillStyle(series.points, series.color), 0, false, - series.xaxis, series.yaxis, symbol); - ctx.restore(); - } - - function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { - var left, right, bottom, top, - drawLeft, drawRight, drawTop, drawBottom, - tmp; - - // in horizontal mode, we start the bar from the left - // instead of from the bottom so it appears to be - // horizontal rather than vertical - if (horizontal) { - drawBottom = drawRight = drawTop = true; - drawLeft = false; - left = b; - right = x; - top = y + barLeft; - bottom = y + barRight; - - // account for negative bars - if (right < left) { - tmp = right; - right = left; - left = tmp; - drawLeft = true; - drawRight = false; - } - } - else { - drawLeft = drawRight = drawTop = true; - drawBottom = false; - left = x + barLeft; - right = x + barRight; - bottom = b; - top = y; - - // account for negative bars - if (top < bottom) { - tmp = top; - top = bottom; - bottom = tmp; - drawBottom = true; - drawTop = false; - } - } - - // clip - if (right < axisx.min || left > axisx.max || - top < axisy.min || bottom > axisy.max) - return; - - if (left < axisx.min) { - left = axisx.min; - drawLeft = false; - } - - if (right > axisx.max) { - right = axisx.max; - drawRight = false; - } - - if (bottom < axisy.min) { - bottom = axisy.min; - drawBottom = false; - } - - if (top > axisy.max) { - top = axisy.max; - drawTop = false; - } - - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - - // fill the bar - if (fillStyleCallback) { - c.fillStyle = fillStyleCallback(bottom, top); - c.fillRect(left, top, right - left, bottom - top) - } - - // draw outline - if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { - c.beginPath(); - - // FIXME: inline moveTo is buggy with excanvas - c.moveTo(left, bottom); - if (drawLeft) - c.lineTo(left, top); - else - c.moveTo(left, top); - if (drawTop) - c.lineTo(right, top); - else - c.moveTo(right, top); - if (drawRight) - c.lineTo(right, bottom); - else - c.moveTo(right, bottom); - if (drawBottom) - c.lineTo(left, bottom); - else - c.moveTo(left, bottom); - c.stroke(); - } - } - - function drawSeriesBars(series) { - function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - if (points[i] == null) - continue; - drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // FIXME: figure out a way to add shadows (for instance along the right edge) - ctx.lineWidth = series.bars.lineWidth; - ctx.strokeStyle = series.color; - - var barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; - plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); - ctx.restore(); - } - - function getFillStyle(filloptions, seriesColor, bottom, top) { - var fill = filloptions.fill; - if (!fill) - return null; - - if (filloptions.fillColor) - return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - - var c = $.color.parse(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - return c.toString(); - } - - function insertLegend() { - - if (options.legend.container != null) { - $(options.legend.container).html(""); - } else { - placeholder.find(".legend").remove(); - } - - if (!options.legend.show) { - return; - } - - var fragments = [], entries = [], rowStarted = false, - lf = options.legend.labelFormatter, s, label; - - // Build a list of legend entries, with each having a label and a color - - for (var i = 0; i < series.length; ++i) { - s = series[i]; - if (s.label) { - label = lf ? lf(s.label, s) : s.label; - if (label) { - entries.push({ - label: label, - color: s.color - }); - } - } - } - - // Sort the legend using either the default or a custom comparator - - if (options.legend.sorted) { - if ($.isFunction(options.legend.sorted)) { - entries.sort(options.legend.sorted); - } else if (options.legend.sorted == "reverse") { - entries.reverse(); - } else { - var ascending = options.legend.sorted != "descending"; - entries.sort(function(a, b) { - return a.label == b.label ? 0 : ( - (a.label < b.label) != ascending ? 1 : -1 // Logical XOR - ); - }); - } - } - - // Generate markup for the list of entries, in their final order - - for (var i = 0; i < entries.length; ++i) { - - var entry = entries[i]; - - if (i % options.legend.noColumns == 0) { - if (rowStarted) - fragments.push(''); - fragments.push(''); - rowStarted = true; - } - - fragments.push( - '
' + - '' + entry.label + '' - ); - } - - if (rowStarted) - fragments.push(''); - - if (fragments.length == 0) - return; - - var table = '' + fragments.join("") + '
'; - if (options.legend.container != null) - $(options.legend.container).html(table); - else { - var pos = "", - p = options.legend.position, - m = options.legend.margin; - if (m[0] == null) - m = [m, m]; - if (p.charAt(0) == "n") - pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; - else if (p.charAt(0) == "s") - pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; - if (p.charAt(1) == "e") - pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; - else if (p.charAt(1) == "w") - pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; - var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); - if (options.legend.backgroundOpacity != 0.0) { - // put in the transparent background - // separately to avoid blended labels and - // label boxes - var c = options.legend.backgroundColor; - if (c == null) { - c = options.grid.backgroundColor; - if (c && typeof c == "string") - c = $.color.parse(c); - else - c = $.color.extract(legend, 'background-color'); - c.a = 1; - c = c.toString(); - } - var div = legend.children(); - $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - } - } - } - - - // interactive features - - var highlights = [], - redrawTimeout = null; - - // returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY, seriesFilter) { - var maxDistance = options.grid.mouseActiveRadius, - smallestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false, i, j, ps; - - for (i = series.length - 1; i >= 0; --i) { - if (!seriesFilter(series[i])) - continue; - - var s = series[i], - axisx = s.xaxis, - axisy = s.yaxis, - points = s.datapoints.points, - mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster - my = axisy.c2p(mouseY), - maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale; - - ps = s.datapoints.pointsize; - // with inverse transforms, we can't use the maxx/maxy - // optimization, sadly - if (axisx.options.inverseTransform) - maxx = Number.MAX_VALUE; - if (axisy.options.inverseTransform) - maxy = Number.MAX_VALUE; - - if (s.lines.show || s.points.show) { - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1]; - if (x == null) - continue; - - // For points and lines, the cursor must be within a - // certain distance to the data point - if (x - mx > maxx || x - mx < -maxx || - y - my > maxy || y - my < -maxy) - continue; - - // We have to calculate distances in pixels, not in - // data units, because the scales of the axes may be different - var dx = Math.abs(axisx.p2c(x) - mouseX), - dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; // we save the sqrt - - // use <= to ensure last point takes precedence - // (last generally means on top of) - if (dist < smallestDistance) { - smallestDistance = dist; - item = [i, j / ps]; - } - } - } - - if (s.bars.show && !item) { // no other point can be nearby - - var barLeft, barRight; - - switch (s.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -s.bars.barWidth; - break; - default: - barLeft = -s.bars.barWidth / 2; - } - - barRight = barLeft + s.bars.barWidth; - - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1], b = points[j + 2]; - if (x == null) - continue; - - // for a bar graph, the cursor must be inside the bar - if (series[i].bars.horizontal ? - (mx <= Math.max(b, x) && mx >= Math.min(b, x) && - my >= y + barLeft && my <= y + barRight) : - (mx >= x + barLeft && mx <= x + barRight && - my >= Math.min(b, y) && my <= Math.max(b, y))) - item = [i, j / ps]; - } - } - } - - if (item) { - i = item[0]; - j = item[1]; - ps = series[i].datapoints.pointsize; - - return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - - return null; - } - - function onMouseMove(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return s["hoverable"] != false; }); - } - - function onMouseLeave(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return false; }); - } - - function onClick(e) { - triggerClickHoverEvent("plotclick", e, - function (s) { return s["clickable"] != false; }); - } - - // trigger click or hover event (they send the same parameters - // so we share their code) - function triggerClickHoverEvent(eventname, event, seriesFilter) { - var offset = eventHolder.offset(), - canvasX = event.pageX - offset.left - plotOffset.left, - canvasY = event.pageY - offset.top - plotOffset.top, - pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); - - pos.pageX = event.pageX; - pos.pageY = event.pageY; - - var item = findNearbyItem(canvasX, canvasY, seriesFilter); - - if (item) { - // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); - } - - if (options.grid.autoHighlight) { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto == eventname && - !(item && h.series == item.series && - h.point[0] == item.datapoint[0] && - h.point[1] == item.datapoint[1])) - unhighlight(h.series, h.point); - } - - if (item) - highlight(item.series, item.datapoint, eventname); - } - - placeholder.trigger(eventname, [ pos, item ]); - } - - function triggerRedrawOverlay() { - var t = options.interaction.redrawOverlayInterval; - if (t == -1) { // skip event queue - drawOverlay(); - return; - } - - if (!redrawTimeout) - redrawTimeout = setTimeout(drawOverlay, t); - } - - function drawOverlay() { - redrawTimeout = null; - - // draw highlights - octx.save(); - overlay.clear(); - octx.translate(plotOffset.left, plotOffset.top); - - var i, hi; - for (i = 0; i < highlights.length; ++i) { - hi = highlights[i]; - - if (hi.series.bars.show) - drawBarHighlight(hi.series, hi.point); - else - drawPointHighlight(hi.series, hi.point); - } - octx.restore(); - - executeHooks(hooks.drawOverlay, [octx]); - } - - function highlight(s, point, auto) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i == -1) { - highlights.push({ series: s, point: point, auto: auto }); - - triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s, point) { - if (s == null && point == null) { - highlights = []; - triggerRedrawOverlay(); - return; - } - - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i != -1) { - highlights.splice(i, 1); - - triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s, p) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s && h.point[0] == p[0] - && h.point[1] == p[1]) - return i; - } - return -1; - } - - function drawPointHighlight(series, point) { - var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis, - highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); - - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - return; - - var pointRadius = series.points.radius + series.points.lineWidth / 2; - octx.lineWidth = pointRadius; - octx.strokeStyle = highlightColor; - var radius = 1.5 * pointRadius; - x = axisx.p2c(x); - y = axisy.p2c(y); - - octx.beginPath(); - if (series.points.symbol == "circle") - octx.arc(x, y, radius, 0, 2 * Math.PI, false); - else - series.points.symbol(octx, x, y, radius, false); - octx.closePath(); - octx.stroke(); - } - - function drawBarHighlight(series, point) { - var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), - fillStyle = highlightColor, - barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = highlightColor; - - drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, - function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); - } - - function getColorOrGradient(spec, bottom, top, defaultColor) { - if (typeof spec == "string") - return spec; - else { - // assume this is a gradient spec; IE currently only - // supports a simple vertical gradient properly, so that's - // what we support too - var gradient = ctx.createLinearGradient(0, top, 0, bottom); - - for (var i = 0, l = spec.colors.length; i < l; ++i) { - var c = spec.colors[i]; - if (typeof c != "string") { - var co = $.color.parse(defaultColor); - if (c.brightness != null) - co = co.scale('rgb', c.brightness); - if (c.opacity != null) - co.a *= c.opacity; - c = co.toString(); - } - gradient.addColorStop(i / (l - 1), c); - } - - return gradient; - } - } - } - - // Add the plot function to the top level of the jQuery object - - $.plot = function(placeholder, data, options) { - //var t0 = new Date(); - var plot = new Plot($(placeholder), data, options, $.plot.plugins); - //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); - return plot; - }; - - $.plot.version = "0.8.3"; - - $.plot.plugins = []; - - // Also add the plot function as a chainable property - - $.fn.plot = function(data, options) { - return this.each(function() { - $.plot(this, data, options); - }); - }; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.log.js b/src/legacy/ui/public/flot-charts/jquery.flot.log.js deleted file mode 100644 index e32bf5cf7e817..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.log.js +++ /dev/null @@ -1,163 +0,0 @@ -/* @notice - * - * Pretty handling of logarithmic axes. - * Copyright (c) 2007-2014 IOLA and Ole Laursen. - * Licensed under the MIT license. - * Created by Arne de Laat - * Set axis.mode to "log" and make the axis logarithmic using transform: - * axis: { - * mode: 'log', - * transform: function(v) {v <= 0 ? Math.log(v) / Math.LN10 : null}, - * inverseTransform: function(v) {Math.pow(10, v)} - * } - * The transform filters negative and zero values, because those are - * invalid on logarithmic scales. - * This plugin tries to create good looking logarithmic ticks, using - * unicode superscript characters. If all data to be plotted is between two - * powers of ten then the default flot tick generator and renderer are - * used. Logarithmic ticks are places at powers of ten and at half those - * values if there are not to many ticks already (e.g. [1, 5, 10, 50, 100]). - * For details, see https://github.com/flot/flot/pull/1328 -*/ - -(function($) { - - function log10(value) { - /* Get the Log10 of the value - */ - return Math.log(value) / Math.LN10; - } - - function floorAsLog10(value) { - /* Get power of the first power of 10 below the value - */ - return Math.floor(log10(value)); - } - - function ceilAsLog10(value) { - /* Get power of the first power of 10 above the value - */ - return Math.ceil(log10(value)); - } - - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - - function getUnicodePower(power) { - var superscripts = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"], - result = "", - str_power = "" + power; - for (var i = 0; i < str_power.length; i++) { - if (str_power[i] === "+") { - } - else if (str_power[i] === "-") { - result += "⁻"; - } - else { - result += superscripts[str_power[i]]; - } - } - return result; - } - - function init(plot) { - plot.hooks.processOptions.push(function (plot) { - $.each(plot.getAxes(), function(axisName, axis) { - - var opts = axis.options; - - if (opts.mode === "log") { - - axis.tickGenerator = function (axis) { - - var ticks = [], - end = ceilAsLog10(axis.max), - start = floorAsLog10(axis.min), - tick = Number.NaN, - i = 0; - - if (axis.min === null || axis.min <= 0) { - // Bad minimum, make ticks from 1 (10**0) to max - start = 0; - axis.min = 0.6; - } - - if (end <= start) { - // Start less than end?! - ticks = [1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, - 1e7, 1e8, 1e9]; - } - else if (log10(axis.max) - log10(axis.datamin) < 1) { - // Default flot generator incase no powers of 10 - // are between start and end - var prev; - start = floorInBase(axis.min, axis.tickSize); - do { - prev = tick; - tick = start + i * axis.tickSize; - ticks.push(tick); - ++i; - } while (tick < axis.max && tick !== prev); - } - else { - // Make ticks at each power of ten - for (; i <= (end - start); i++) { - tick = Math.pow(10, start + i); - ticks.push(tick); - } - - var length = ticks.length; - - // If not to many ticks also put a tick between - // the powers of ten - if (end - start < 6) { - for (var j = 1; j < length * 2 - 1; j += 2) { - tick = ticks[j - 1] * 5; - ticks.splice(j, 0, tick); - } - } - } - return ticks; - }; - - axis.tickFormatter = function (value, axis) { - var formatted; - if (log10(axis.max) - log10(axis.datamin) < 1) { - // Default flot formatter - var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; - formatted = "" + Math.round(value * factor) / factor; - if (axis.tickDecimals !== null) { - var decimal = formatted.indexOf("."); - var precision = decimal === -1 ? 0 : formatted.length - decimal - 1; - if (precision < axis.tickDecimals) { - return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); - } - } - } - else { - var multiplier = "", - exponential = parseFloat(value).toExponential(0), - power = getUnicodePower(exponential.slice(2)); - if (exponential[0] !== "1") { - multiplier = exponential[0] + "x"; - } - formatted = multiplier + "10" + power; - } - return formatted; - }; - } - }); - }); - } - - $.plot.plugins.push({ - init: init, - name: "log", - version: "0.9" - }); - -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.navigate.js b/src/legacy/ui/public/flot-charts/jquery.flot.navigate.js deleted file mode 100644 index 13fb7f17d04b2..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.navigate.js +++ /dev/null @@ -1,346 +0,0 @@ -/* Flot plugin for adding the ability to pan and zoom the plot. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The default behaviour is double click and scrollwheel up/down to zoom in, drag -to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and -plot.pan( offset ) so you easily can add custom controls. It also fires -"plotpan" and "plotzoom" events, useful for synchronizing plots. - -The plugin supports these options: - - zoom: { - interactive: false - trigger: "dblclick" // or "click" for single click - amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out) - } - - pan: { - interactive: false - cursor: "move" // CSS mouse cursor value used when dragging, e.g. "pointer" - frameRate: 20 - } - - xaxis, yaxis, x2axis, y2axis: { - zoomRange: null // or [ number, number ] (min range, max range) or false - panRange: null // or [ number, number ] (min, max) or false - } - -"interactive" enables the built-in drag/click behaviour. If you enable -interactive for pan, then you'll have a basic plot that supports moving -around; the same for zoom. - -"amount" specifies the default amount to zoom in (so 1.5 = 150%) relative to -the current viewport. - -"cursor" is a standard CSS mouse cursor string used for visual feedback to the -user when dragging. - -"frameRate" specifies the maximum number of times per second the plot will -update itself while the user is panning around on it (set to null to disable -intermediate pans, the plot will then not update until the mouse button is -released). - -"zoomRange" is the interval in which zooming can happen, e.g. with zoomRange: -[1, 100] the zoom will never scale the axis so that the difference between min -and max is smaller than 1 or larger than 100. You can set either end to null -to ignore, e.g. [1, null]. If you set zoomRange to false, zooming on that axis -will be disabled. - -"panRange" confines the panning to stay within a range, e.g. with panRange: -[-10, 20] panning stops at -10 in one end and at 20 in the other. Either can -be null, e.g. [-10, null]. If you set panRange to false, panning on that axis -will be disabled. - -Example API usage: - - plot = $.plot(...); - - // zoom default amount in on the pixel ( 10, 20 ) - plot.zoom({ center: { left: 10, top: 20 } }); - - // zoom out again - plot.zoomOut({ center: { left: 10, top: 20 } }); - - // zoom 200% in on the pixel (10, 20) - plot.zoom({ amount: 2, center: { left: 10, top: 20 } }); - - // pan 100 pixels to the left and 20 down - plot.pan({ left: -100, top: 20 }) - -Here, "center" specifies where the center of the zooming should happen. Note -that this is defined in pixel space, not the space of the data points (you can -use the p2c helpers on the axes in Flot to help you convert between these). - -"amount" is the amount to zoom the viewport relative to the current range, so -1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You -can set the default in the options. - -*/ - -// First two dependencies, jquery.event.drag.js and -// jquery.mousewheel.js, we put them inline here to save people the -// effort of downloading them. - -/* -jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com) -Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt -*/ -(function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY) max) { - // make sure min < max - var tmp = min; - min = max; - max = tmp; - } - - //Check that we are in panRange - if (pr) { - if (pr[0] != null && min < pr[0]) { - min = pr[0]; - } - if (pr[1] != null && max > pr[1]) { - max = pr[1]; - } - } - - var range = max - min; - if (zr && - ((zr[0] != null && range < zr[0] && amount >1) || - (zr[1] != null && range > zr[1] && amount <1))) - return; - - opts.min = min; - opts.max = max; - }); - - plot.setupGrid(); - plot.draw(); - - if (!args.preventEvent) - plot.getPlaceholder().trigger("plotzoom", [ plot, args ]); - }; - - plot.pan = function (args) { - var delta = { - x: +args.left, - y: +args.top - }; - - if (isNaN(delta.x)) - delta.x = 0; - if (isNaN(delta.y)) - delta.y = 0; - - $.each(plot.getAxes(), function (_, axis) { - var opts = axis.options, - min, max, d = delta[axis.direction]; - - min = axis.c2p(axis.p2c(axis.min) + d), - max = axis.c2p(axis.p2c(axis.max) + d); - - var pr = opts.panRange; - if (pr === false) // no panning on this axis - return; - - if (pr) { - // check whether we hit the wall - if (pr[0] != null && pr[0] > min) { - d = pr[0] - min; - min += d; - max += d; - } - - if (pr[1] != null && pr[1] < max) { - d = pr[1] - max; - min += d; - max += d; - } - } - - opts.min = min; - opts.max = max; - }); - - plot.setupGrid(); - plot.draw(); - - if (!args.preventEvent) - plot.getPlaceholder().trigger("plotpan", [ plot, args ]); - }; - - function shutdown(plot, eventHolder) { - eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); - eventHolder.unbind("mousewheel", onMouseWheel); - eventHolder.unbind("dragstart", onDragStart); - eventHolder.unbind("drag", onDrag); - eventHolder.unbind("dragend", onDragEnd); - if (panTimeout) - clearTimeout(panTimeout); - } - - plot.hooks.bindEvents.push(bindEvents); - plot.hooks.shutdown.push(shutdown); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'navigate', - version: '1.3' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.pie.js b/src/legacy/ui/public/flot-charts/jquery.flot.pie.js deleted file mode 100644 index 06f900bdc950f..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.pie.js +++ /dev/null @@ -1,824 +0,0 @@ -/* Flot plugin for rendering pie charts. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin assumes that each series has a single data value, and that each -value is a positive integer or zero. Negative numbers don't make sense for a -pie chart, and have unpredictable results. The values do NOT need to be -passed in as percentages; the plugin will calculate the total and per-slice -percentages internally. - -* Created by Brian Medendorp - -* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars - -The plugin supports these options: - - series: { - pie: { - show: true/false - radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' - innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect - startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result - tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) - offset: { - top: integer value to move the pie up or down - left: integer value to move the pie left or right, or 'auto' - }, - stroke: { - color: any hexadecimal color value (other formats may or may not work, so best to stick with something like '#FFF') - width: integer pixel width of the stroke - }, - label: { - show: true/false, or 'auto' - formatter: a user-defined function that modifies the text/style of the label text - radius: 0-1 for percentage of fullsize, or a specified pixel length - background: { - color: any hexadecimal color value (other formats may or may not work, so best to stick with something like '#000') - opacity: 0-1 - }, - threshold: 0-1 for the percentage value at which to hide labels (if they're too small) - }, - combine: { - threshold: 0-1 for the percentage value at which to combine slices (if they're too small) - color: any hexadecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined - label: any text value of what the combined slice should be labeled - } - highlight: { - opacity: 0-1 - } - } - } - -More detail and specific examples can be found in the included HTML file. - -*/ - -import { i18n } from '@kbn/i18n'; - -(function($) { - // Maximum redraw attempts when fitting labels within the plot - - var REDRAW_ATTEMPTS = 10; - - // Factor by which to shrink the pie when fitting labels within the plot - - var REDRAW_SHRINK = 0.95; - - function init(plot) { - - var canvas = null, - target = null, - options = null, - maxRadius = null, - centerLeft = null, - centerTop = null, - processed = false, - ctx = null; - - // interactive variables - - var highlights = []; - - // add hook to determine if pie plugin in enabled, and then perform necessary operations - - plot.hooks.processOptions.push(function(plot, options) { - if (options.series.pie.show) { - - options.grid.show = false; - - // set labels.show - - if (options.series.pie.label.show == "auto") { - if (options.legend.show) { - options.series.pie.label.show = false; - } else { - options.series.pie.label.show = true; - } - } - - // set radius - - if (options.series.pie.radius == "auto") { - if (options.series.pie.label.show) { - options.series.pie.radius = 3/4; - } else { - options.series.pie.radius = 1; - } - } - - // ensure sane tilt - - if (options.series.pie.tilt > 1) { - options.series.pie.tilt = 1; - } else if (options.series.pie.tilt < 0) { - options.series.pie.tilt = 0; - } - } - }); - - plot.hooks.bindEvents.push(function(plot, eventHolder) { - var options = plot.getOptions(); - if (options.series.pie.show) { - if (options.grid.hoverable) { - eventHolder.unbind("mousemove").mousemove(onMouseMove); - } - if (options.grid.clickable) { - eventHolder.unbind("click").click(onClick); - } - } - }); - - plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { - var options = plot.getOptions(); - if (options.series.pie.show) { - processDatapoints(plot, series, data, datapoints); - } - }); - - plot.hooks.drawOverlay.push(function(plot, octx) { - var options = plot.getOptions(); - if (options.series.pie.show) { - drawOverlay(plot, octx); - } - }); - - plot.hooks.draw.push(function(plot, newCtx) { - var options = plot.getOptions(); - if (options.series.pie.show) { - draw(plot, newCtx); - } - }); - - function processDatapoints(plot, series, datapoints) { - if (!processed) { - processed = true; - canvas = plot.getCanvas(); - target = $(canvas).parent(); - options = plot.getOptions(); - plot.setData(combine(plot.getData())); - } - } - - function combine(data) { - - var total = 0, - combined = 0, - numCombined = 0, - color = options.series.pie.combine.color, - newdata = []; - - // Fix up the raw data from Flot, ensuring the data is numeric - - for (var i = 0; i < data.length; ++i) { - - var value = data[i].data; - - // If the data is an array, we'll assume that it's a standard - // Flot x-y pair, and are concerned only with the second value. - - // Note how we use the original array, rather than creating a - // new one; this is more efficient and preserves any extra data - // that the user may have stored in higher indexes. - - if ($.isArray(value) && value.length == 1) { - value = value[0]; - } - - if ($.isArray(value)) { - // Equivalent to $.isNumeric() but compatible with jQuery < 1.7 - if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { - value[1] = +value[1]; - } else { - value[1] = 0; - } - } else if (!isNaN(parseFloat(value)) && isFinite(value)) { - value = [1, +value]; - } else { - value = [1, 0]; - } - - data[i].data = [value]; - } - - // Sum up all the slices, so we can calculate percentages for each - - for (var i = 0; i < data.length; ++i) { - total += data[i].data[0][1]; - } - - // Count the number of slices with percentages below the combine - // threshold; if it turns out to be just one, we won't combine. - - for (var i = 0; i < data.length; ++i) { - var value = data[i].data[0][1]; - if (value / total <= options.series.pie.combine.threshold) { - combined += value; - numCombined++; - if (!color) { - color = data[i].color; - } - } - } - - for (var i = 0; i < data.length; ++i) { - var value = data[i].data[0][1]; - if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { - newdata.push( - $.extend(data[i], { /* extend to allow keeping all other original data values - and using them e.g. in labelFormatter. */ - data: [[1, value]], - color: data[i].color, - label: data[i].label, - angle: value * Math.PI * 2 / total, - percent: value / (total / 100) - }) - ); - } - } - - if (numCombined > 1) { - newdata.push({ - data: [[1, combined]], - color: color, - label: options.series.pie.combine.label, - angle: combined * Math.PI * 2 / total, - percent: combined / (total / 100) - }); - } - - return newdata; - } - - function draw(plot, newCtx) { - - if (!target) { - return; // if no series were passed - } - - var canvasWidth = plot.getPlaceholder().width(), - canvasHeight = plot.getPlaceholder().height(), - legendWidth = target.children().filter(".legend").children().width() || 0; - - ctx = newCtx; - - // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE! - - // When combining smaller slices into an 'other' slice, we need to - // add a new series. Since Flot gives plugins no way to modify the - // list of series, the pie plugin uses a hack where the first call - // to processDatapoints results in a call to setData with the new - // list of series, then subsequent processDatapoints do nothing. - - // The plugin-global 'processed' flag is used to control this hack; - // it starts out false, and is set to true after the first call to - // processDatapoints. - - // Unfortunately this turns future setData calls into no-ops; they - // call processDatapoints, the flag is true, and nothing happens. - - // To fix this we'll set the flag back to false here in draw, when - // all series have been processed, so the next sequence of calls to - // processDatapoints once again starts out with a slice-combine. - // This is really a hack; in 0.9 we need to give plugins a proper - // way to modify series before any processing begins. - - processed = false; - - // calculate maximum radius and center point - - maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; - centerTop = canvasHeight / 2 + options.series.pie.offset.top; - centerLeft = canvasWidth / 2; - - if (options.series.pie.offset.left == "auto") { - if (options.legend.position.match("w")) { - centerLeft += legendWidth / 2; - } else { - centerLeft -= legendWidth / 2; - } - if (centerLeft < maxRadius) { - centerLeft = maxRadius; - } else if (centerLeft > canvasWidth - maxRadius) { - centerLeft = canvasWidth - maxRadius; - } - } else { - centerLeft += options.series.pie.offset.left; - } - - var slices = plot.getData(), - attempts = 0; - - // Keep shrinking the pie's radius until drawPie returns true, - // indicating that all the labels fit, or we try too many times. - - do { - if (attempts > 0) { - maxRadius *= REDRAW_SHRINK; - } - attempts += 1; - clear(); - if (options.series.pie.tilt <= 0.8) { - drawShadow(); - } - } while (!drawPie() && attempts < REDRAW_ATTEMPTS) - - if (attempts >= REDRAW_ATTEMPTS) { - clear(); - const errorMessage = i18n.translate('common.ui.flotCharts.pie.unableToDrawLabelsInsideCanvasErrorMessage', { - defaultMessage: 'Could not draw pie with labels contained inside canvas', - }); - target.prepend(`
${errorMessage}
`); - } - - if (plot.setSeries && plot.insertLegend) { - plot.setSeries(slices); - plot.insertLegend(); - } - - // we're actually done at this point, just defining internal functions at this point - - function clear() { - ctx.clearRect(0, 0, canvasWidth, canvasHeight); - target.children().filter(".pieLabel, .pieLabelBackground").remove(); - } - - function drawShadow() { - - var shadowLeft = options.series.pie.shadow.left; - var shadowTop = options.series.pie.shadow.top; - var edge = 10; - var alpha = options.series.pie.shadow.alpha; - var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; - - if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { - return; // shadow would be outside canvas, so don't draw it - } - - ctx.save(); - ctx.translate(shadowLeft,shadowTop); - ctx.globalAlpha = alpha; - ctx.fillStyle = "#000"; - - // center and rotate to starting position - - ctx.translate(centerLeft,centerTop); - ctx.scale(1, options.series.pie.tilt); - - //radius -= edge; - - for (var i = 1; i <= edge; i++) { - ctx.beginPath(); - ctx.arc(0, 0, radius, 0, Math.PI * 2, false); - ctx.fill(); - radius -= i; - } - - ctx.restore(); - } - - function drawPie() { - - var startAngle = Math.PI * options.series.pie.startAngle; - var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; - - // center and rotate to starting position - - ctx.save(); - ctx.translate(centerLeft,centerTop); - ctx.scale(1, options.series.pie.tilt); - //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera - - // draw slices - - ctx.save(); - var currentAngle = startAngle; - for (var i = 0; i < slices.length; ++i) { - slices[i].startAngle = currentAngle; - drawSlice(slices[i].angle, slices[i].color, true); - } - ctx.restore(); - - // draw slice outlines - - if (options.series.pie.stroke.width > 0) { - ctx.save(); - ctx.lineWidth = options.series.pie.stroke.width; - currentAngle = startAngle; - for (var i = 0; i < slices.length; ++i) { - drawSlice(slices[i].angle, options.series.pie.stroke.color, false); - } - ctx.restore(); - } - - // draw donut hole - - drawDonutHole(ctx); - - ctx.restore(); - - // Draw the labels, returning true if they fit within the plot - - if (options.series.pie.label.show) { - return drawLabels(); - } else return true; - - function drawSlice(angle, color, fill) { - - if (angle <= 0 || isNaN(angle)) { - return; - } - - if (fill) { - ctx.fillStyle = color; - } else { - ctx.strokeStyle = color; - ctx.lineJoin = "round"; - } - - ctx.beginPath(); - if (Math.abs(angle - Math.PI * 2) > 0.000000001) { - ctx.moveTo(0, 0); // Center of the pie - } - - //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera - ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); - ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); - ctx.closePath(); - //ctx.rotate(angle); // This doesn't work properly in Opera - currentAngle += angle; - - if (fill) { - ctx.fill(); - } else { - ctx.stroke(); - } - } - - function drawLabels() { - - var currentAngle = startAngle; - var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; - - for (var i = 0; i < slices.length; ++i) { - if (slices[i].percent >= options.series.pie.label.threshold * 100) { - if (!drawLabel(slices[i], currentAngle, i)) { - return false; - } - } - currentAngle += slices[i].angle; - } - - return true; - - function drawLabel(slice, startAngle, index) { - - if (slice.data[0][1] == 0) { - return true; - } - - // format label text - - var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; - - if (lf) { - text = lf(slice.label, slice); - } else { - text = slice.label; - } - - if (plf) { - text = plf(text, slice); - } - - var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; - var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); - var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; - - var html = "" + text + ""; - target.append(html); - - var label = target.children("#pieLabel" + index); - var labelTop = (y - label.height() / 2); - var labelLeft = (x - label.width() / 2); - - label.css("top", labelTop); - label.css("left", labelLeft); - - // check to make sure that the label is not outside the canvas - - if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { - return false; - } - - if (options.series.pie.label.background.opacity != 0) { - - // put in the transparent background separately to avoid blended labels and label boxes - - var c = options.series.pie.label.background.color; - - if (c == null) { - c = slice.color; - } - - var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; - $("
") - .css("opacity", options.series.pie.label.background.opacity) - .insertBefore(label); - } - - return true; - } // end individual label function - } // end drawLabels function - } // end drawPie function - } // end draw function - - // Placed here because it needs to be accessed from multiple locations - - function drawDonutHole(layer) { - if (options.series.pie.innerRadius > 0) { - - // subtract the center - - layer.save(); - var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; - layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color - layer.beginPath(); - layer.fillStyle = options.series.pie.stroke.color; - layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); - layer.fill(); - layer.closePath(); - layer.restore(); - - // add inner stroke - - layer.save(); - layer.beginPath(); - layer.strokeStyle = options.series.pie.stroke.color; - layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); - layer.stroke(); - layer.closePath(); - layer.restore(); - - // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. - } - } - - //-- Additional Interactive related functions -- - - function isPointInPoly(poly, pt) { - for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) - ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) - && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) - && (c = !c); - return c; - } - - function findNearbySlice(mouseX, mouseY) { - - var slices = plot.getData(), - options = plot.getOptions(), - radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius, - x, y; - - for (var i = 0; i < slices.length; ++i) { - - var s = slices[i]; - - if (s.pie.show) { - - ctx.save(); - ctx.beginPath(); - ctx.moveTo(0, 0); // Center of the pie - //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. - ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); - ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); - ctx.closePath(); - x = mouseX - centerLeft; - y = mouseY - centerTop; - - if (ctx.isPointInPath) { - if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { - ctx.restore(); - return { - datapoint: [s.percent, s.data], - dataIndex: 0, - series: s, - seriesIndex: i - }; - } - } else { - - // excanvas for IE doesn;t support isPointInPath, this is a workaround. - - var p1X = radius * Math.cos(s.startAngle), - p1Y = radius * Math.sin(s.startAngle), - p2X = radius * Math.cos(s.startAngle + s.angle / 4), - p2Y = radius * Math.sin(s.startAngle + s.angle / 4), - p3X = radius * Math.cos(s.startAngle + s.angle / 2), - p3Y = radius * Math.sin(s.startAngle + s.angle / 2), - p4X = radius * Math.cos(s.startAngle + s.angle / 1.5), - p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5), - p5X = radius * Math.cos(s.startAngle + s.angle), - p5Y = radius * Math.sin(s.startAngle + s.angle), - arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]], - arrPoint = [x, y]; - - // TODO: perhaps do some mathematical trickery here with the Y-coordinate to compensate for pie tilt? - - if (isPointInPoly(arrPoly, arrPoint)) { - ctx.restore(); - return { - datapoint: [s.percent, s.data], - dataIndex: 0, - series: s, - seriesIndex: i - }; - } - } - - ctx.restore(); - } - } - - return null; - } - - function onMouseMove(e) { - triggerClickHoverEvent("plothover", e); - } - - function onClick(e) { - triggerClickHoverEvent("plotclick", e); - } - - // trigger click or hover event (they send the same parameters so we share their code) - - function triggerClickHoverEvent(eventname, e) { - - var offset = plot.offset(); - var canvasX = parseInt(e.pageX - offset.left); - var canvasY = parseInt(e.pageY - offset.top); - var item = findNearbySlice(canvasX, canvasY); - - if (options.grid.autoHighlight) { - - // clear auto-highlights - - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto == eventname && !(item && h.series == item.series)) { - unhighlight(h.series); - } - } - } - - // highlight the slice - - if (item) { - highlight(item.series, eventname); - } - - // trigger any hover bind events - - var pos = { pageX: e.pageX, pageY: e.pageY }; - target.trigger(eventname, [pos, item]); - } - - function highlight(s, auto) { - //if (typeof s == "number") { - // s = series[s]; - //} - - var i = indexOfHighlight(s); - - if (i == -1) { - highlights.push({ series: s, auto: auto }); - plot.triggerRedrawOverlay(); - } else if (!auto) { - highlights[i].auto = false; - } - } - - function unhighlight(s) { - if (s == null) { - highlights = []; - plot.triggerRedrawOverlay(); - } - - //if (typeof s == "number") { - // s = series[s]; - //} - - var i = indexOfHighlight(s); - - if (i != -1) { - highlights.splice(i, 1); - plot.triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s) - return i; - } - return -1; - } - - function drawOverlay(plot, octx) { - - var options = plot.getOptions(); - - var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; - - octx.save(); - octx.translate(centerLeft, centerTop); - octx.scale(1, options.series.pie.tilt); - - for (var i = 0; i < highlights.length; ++i) { - drawHighlight(highlights[i].series); - } - - drawDonutHole(octx); - - octx.restore(); - - function drawHighlight(series) { - - if (series.angle <= 0 || isNaN(series.angle)) { - return; - } - - //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); - octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor - octx.beginPath(); - if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { - octx.moveTo(0, 0); // Center of the pie - } - octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); - octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false); - octx.closePath(); - octx.fill(); - } - } - } // end init (plugin body) - - // define pie specific options and their default values - - var options = { - series: { - pie: { - show: false, - radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) - innerRadius: 0, /* for donut */ - startAngle: 3/2, - tilt: 1, - shadow: { - left: 5, // shadow left offset - top: 15, // shadow top offset - alpha: 0.02 // shadow alpha - }, - offset: { - top: 0, - left: "auto" - }, - stroke: { - color: "#fff", - width: 1 - }, - label: { - show: "auto", - formatter: function(label, slice) { - return "
" + label + "
" + Math.round(slice.percent) + "%
"; - }, // formatter function - radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) - background: { - color: null, - opacity: 0 - }, - threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) - }, - combine: { - threshold: -1, // percentage at which to combine little slices into one larger slice - color: null, // color to give the new slice (auto-generated if null) - label: "Other" // label to give the new slice - }, - highlight: { - //color: "#fff", // will add this functionality once parseColor is available - opacity: 0.5 - } - } - } - }; - - $.plot.plugins.push({ - init: init, - options: options, - name: "pie", - version: "1.1" - }); - -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.resize.js b/src/legacy/ui/public/flot-charts/jquery.flot.resize.js deleted file mode 100644 index 8a626dda0addb..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.resize.js +++ /dev/null @@ -1,59 +0,0 @@ -/* Flot plugin for automatically redrawing plots as the placeholder resizes. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -It works by listening for changes on the placeholder div (through the jQuery -resize event plugin) - if the size changes, it will redraw the plot. - -There are no options. If you need to disable the plugin for some plots, you -can just fix the size of their placeholders. - -*/ - -/* Inline dependency: - * jQuery resize event - v1.1 - 3/14/2010 - * http://benalman.com/projects/jquery-resize-plugin/ - * - * Copyright (c) 2010 "Cowboy" Ben Alman - * Dual licensed under the MIT and GPL licenses. - * http://benalman.com/about/license/ - */ -(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this); - -(function ($) { - var options = { }; // no options - - function init(plot) { - function onResize() { - var placeholder = plot.getPlaceholder(); - - // somebody might have hidden us and we can't plot - // when we don't have the dimensions - if (placeholder.width() == 0 || placeholder.height() == 0) - return; - - plot.resize(); - plot.setupGrid(); - plot.draw(); - } - - function bindEvents(plot, eventHolder) { - plot.getPlaceholder().resize(onResize); - } - - function shutdown(plot, eventHolder) { - plot.getPlaceholder().unbind("resize", onResize); - } - - plot.hooks.bindEvents.push(bindEvents); - plot.hooks.shutdown.push(shutdown); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'resize', - version: '1.0' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.selection.js b/src/legacy/ui/public/flot-charts/jquery.flot.selection.js deleted file mode 100644 index c8707b30f4e6f..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.selection.js +++ /dev/null @@ -1,360 +0,0 @@ -/* Flot plugin for selecting regions of a plot. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin supports these options: - -selection: { - mode: null or "x" or "y" or "xy", - color: color, - shape: "round" or "miter" or "bevel", - minSize: number of pixels -} - -Selection support is enabled by setting the mode to one of "x", "y" or "xy". -In "x" mode, the user will only be able to specify the x range, similarly for -"y" mode. For "xy", the selection becomes a rectangle where both ranges can be -specified. "color" is color of the selection (if you need to change the color -later on, you can get to it with plot.getOptions().selection.color). "shape" -is the shape of the corners of the selection. - -"minSize" is the minimum size a selection can be in pixels. This value can -be customized to determine the smallest size a selection can be and still -have the selection rectangle be displayed. When customizing this value, the -fact that it refers to pixels, not axis units must be taken into account. -Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 -minute, setting "minSize" to 1 will not make the minimum selection size 1 -minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent -"plotunselected" events from being fired when the user clicks the mouse without -dragging. - -When selection support is enabled, a "plotselected" event will be emitted on -the DOM element you passed into the plot function. The event handler gets a -parameter with the ranges selected on the axes, like this: - - placeholder.bind( "plotselected", function( event, ranges ) { - alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) - // similar for yaxis - with multiple axes, the extra ones are in - // x2axis, x3axis, ... - }); - -The "plotselected" event is only fired when the user has finished making the -selection. A "plotselecting" event is fired during the process with the same -parameters as the "plotselected" event, in case you want to know what's -happening while it's happening, - -A "plotunselected" event with no arguments is emitted when the user clicks the -mouse to remove the selection. As stated above, setting "minSize" to 0 will -destroy this behavior. - -The plugin also adds the following methods to the plot object: - -- setSelection( ranges, preventEvent ) - - Set the selection rectangle. The passed in ranges is on the same form as - returned in the "plotselected" event. If the selection mode is "x", you - should put in either an xaxis range, if the mode is "y" you need to put in - an yaxis range and both xaxis and yaxis if the selection mode is "xy", like - this: - - setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); - - setSelection will trigger the "plotselected" event when called. If you don't - want that to happen, e.g. if you're inside a "plotselected" handler, pass - true as the second parameter. If you are using multiple axes, you can - specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of - xaxis, the plugin picks the first one it sees. - -- clearSelection( preventEvent ) - - Clear the selection rectangle. Pass in true to avoid getting a - "plotunselected" event. - -- getSelection() - - Returns the current selection in the same format as the "plotselected" - event. If there's currently no selection, the function returns null. - -*/ - -(function ($) { - function init(plot) { - var selection = { - first: { x: -1, y: -1}, second: { x: -1, y: -1}, - show: false, - active: false - }; - - // FIXME: The drag handling implemented here should be - // abstracted out, there's some similar code from a library in - // the navigation plugin, this should be massaged a bit to fit - // the Flot cases here better and reused. Doing this would - // make this plugin much slimmer. - var savedhandlers = {}; - - var mouseUpHandler = null; - - function onMouseMove(e) { - if (selection.active) { - updateSelection(e); - - plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); - } - } - - function onMouseDown(e) { - if (e.which != 1) // only accept left-click - return; - - // cancel out any text selections - document.body.focus(); - - // prevent text selection and drag in old-school browsers - if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { - savedhandlers.onselectstart = document.onselectstart; - document.onselectstart = function () { return false; }; - } - if (document.ondrag !== undefined && savedhandlers.ondrag == null) { - savedhandlers.ondrag = document.ondrag; - document.ondrag = function () { return false; }; - } - - setSelectionPos(selection.first, e); - - selection.active = true; - - // this is a bit silly, but we have to use a closure to be - // able to whack the same handler again - mouseUpHandler = function (e) { onMouseUp(e); }; - - $(document).one("mouseup", mouseUpHandler); - } - - function onMouseUp(e) { - mouseUpHandler = null; - - // revert drag stuff for old-school browsers - if (document.onselectstart !== undefined) - document.onselectstart = savedhandlers.onselectstart; - if (document.ondrag !== undefined) - document.ondrag = savedhandlers.ondrag; - - // no more dragging - selection.active = false; - updateSelection(e); - - if (selectionIsSane()) - triggerSelectedEvent(); - else { - // this counts as a clear - plot.getPlaceholder().trigger("plotunselected", [ ]); - plot.getPlaceholder().trigger("plotselecting", [ null ]); - } - - return false; - } - - function getSelection() { - if (!selectionIsSane()) - return null; - - if (!selection.show) return null; - - var r = {}, c1 = selection.first, c2 = selection.second; - $.each(plot.getAxes(), function (name, axis) { - if (axis.used) { - var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); - r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; - } - }); - return r; - } - - function triggerSelectedEvent() { - var r = getSelection(); - - plot.getPlaceholder().trigger("plotselected", [ r ]); - - // backwards-compat stuff, to be removed in future - if (r.xaxis && r.yaxis) - plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); - } - - function clamp(min, value, max) { - return value < min ? min: (value > max ? max: value); - } - - function setSelectionPos(pos, e) { - var o = plot.getOptions(); - var offset = plot.getPlaceholder().offset(); - var plotOffset = plot.getPlotOffset(); - pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); - pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); - - if (o.selection.mode == "y") - pos.x = pos == selection.first ? 0 : plot.width(); - - if (o.selection.mode == "x") - pos.y = pos == selection.first ? 0 : plot.height(); - } - - function updateSelection(pos) { - if (pos.pageX == null) - return; - - setSelectionPos(selection.second, pos); - if (selectionIsSane()) { - selection.show = true; - plot.triggerRedrawOverlay(); - } - else - clearSelection(true); - } - - function clearSelection(preventEvent) { - if (selection.show) { - selection.show = false; - plot.triggerRedrawOverlay(); - if (!preventEvent) - plot.getPlaceholder().trigger("plotunselected", [ ]); - } - } - - // function taken from markings support in Flot - function extractRange(ranges, coord) { - var axis, from, to, key, axes = plot.getAxes(); - - for (var k in axes) { - axis = axes[k]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function setSelection(ranges, preventEvent) { - var axis, range, o = plot.getOptions(); - - if (o.selection.mode == "y") { - selection.first.x = 0; - selection.second.x = plot.width(); - } - else { - range = extractRange(ranges, "x"); - - selection.first.x = range.axis.p2c(range.from); - selection.second.x = range.axis.p2c(range.to); - } - - if (o.selection.mode == "x") { - selection.first.y = 0; - selection.second.y = plot.height(); - } - else { - range = extractRange(ranges, "y"); - - selection.first.y = range.axis.p2c(range.from); - selection.second.y = range.axis.p2c(range.to); - } - - selection.show = true; - plot.triggerRedrawOverlay(); - if (!preventEvent && selectionIsSane()) - triggerSelectedEvent(); - } - - function selectionIsSane() { - var minSize = plot.getOptions().selection.minSize; - return Math.abs(selection.second.x - selection.first.x) >= minSize && - Math.abs(selection.second.y - selection.first.y) >= minSize; - } - - plot.clearSelection = clearSelection; - plot.setSelection = setSelection; - plot.getSelection = getSelection; - - plot.hooks.bindEvents.push(function(plot, eventHolder) { - var o = plot.getOptions(); - if (o.selection.mode != null) { - eventHolder.mousemove(onMouseMove); - eventHolder.mousedown(onMouseDown); - } - }); - - - plot.hooks.drawOverlay.push(function (plot, ctx) { - // draw selection - if (selection.show && selectionIsSane()) { - var plotOffset = plot.getPlotOffset(); - var o = plot.getOptions(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var c = $.color.parse(o.selection.color); - - ctx.strokeStyle = c.scale('a', 0.8).toString(); - ctx.lineWidth = 1; - ctx.lineJoin = o.selection.shape; - ctx.fillStyle = c.scale('a', 0.4).toString(); - - var x = Math.min(selection.first.x, selection.second.x) + 0.5, - y = Math.min(selection.first.y, selection.second.y) + 0.5, - w = Math.abs(selection.second.x - selection.first.x) - 1, - h = Math.abs(selection.second.y - selection.first.y) - 1; - - ctx.fillRect(x, y, w, h); - ctx.strokeRect(x, y, w, h); - - ctx.restore(); - } - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mousedown", onMouseDown); - - if (mouseUpHandler) - $(document).unbind("mouseup", mouseUpHandler); - }); - - } - - $.plot.plugins.push({ - init: init, - options: { - selection: { - mode: null, // one of null, "x", "y" or "xy" - color: "#e8cfac", - shape: "round", // one of "round", "miter", or "bevel" - minSize: 5 // minimum number of pixels - } - }, - name: 'selection', - version: '1.1' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.stack.js b/src/legacy/ui/public/flot-charts/jquery.flot.stack.js deleted file mode 100644 index 0d91c0f3c0160..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.stack.js +++ /dev/null @@ -1,188 +0,0 @@ -/* Flot plugin for stacking data sets rather than overlaying them. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin assumes the data is sorted on x (or y if stacking horizontally). -For line charts, it is assumed that if a line has an undefined gap (from a -null point), then the line above it should have the same gap - insert zeros -instead of "null" if you want another behaviour. This also holds for the start -and end of the chart. Note that stacking a mix of positive and negative values -in most instances doesn't make sense (so it looks weird). - -Two or more series are stacked when their "stack" attribute is set to the same -key (which can be any number or string or just "true"). To specify the default -stack, you can set the stack option like this: - - series: { - stack: null/false, true, or a key (number/string) - } - -You can also specify it for a single series, like this: - - $.plot( $("#placeholder"), [{ - data: [ ... ], - stack: true - }]) - -The stacking order is determined by the order of the data series in the array -(later series end up on top of the previous). - -Internally, the plugin modifies the datapoints in each series, adding an -offset to the y value. For line series, extra data points are inserted through -interpolation. If there's a second y value, it's also adjusted (e.g for bar -charts or filled areas). - -*/ - -(function ($) { - var options = { - series: { stack: null } // or number/string - }; - - function init(plot) { - function findMatchingSeries(s, allseries) { - var res = null; - for (var i = 0; i < allseries.length; ++i) { - if (s == allseries[i]) - break; - - if (allseries[i].stack == s.stack) - res = allseries[i]; - } - - return res; - } - - function stackData(plot, s, datapoints) { - if (s.stack == null || s.stack === false) - return; - - var other = findMatchingSeries(s, plot.getData()); - if (!other) - return; - - var ps = datapoints.pointsize, - points = datapoints.points, - otherps = other.datapoints.pointsize, - otherpoints = other.datapoints.points, - newpoints = [], - px, py, intery, qx, qy, bottom, - withlines = s.lines.show, - horizontal = s.bars.horizontal, - withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), - withsteps = withlines && s.lines.steps, - fromgap = true, - keyOffset = horizontal ? 1 : 0, - accumulateOffset = horizontal ? 0 : 1, - i = 0, j = 0, l, m; - - while (true) { - if (i >= points.length) - break; - - l = newpoints.length; - - if (points[i] == null) { - // copy gaps - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - i += ps; - } - else if (j >= otherpoints.length) { - // for lines, we can't use the rest of the points - if (!withlines) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - } - i += ps; - } - else if (otherpoints[j] == null) { - // oops, got a gap - for (m = 0; m < ps; ++m) - newpoints.push(null); - fromgap = true; - j += otherps; - } - else { - // cases where we actually got two points - px = points[i + keyOffset]; - py = points[i + accumulateOffset]; - qx = otherpoints[j + keyOffset]; - qy = otherpoints[j + accumulateOffset]; - bottom = 0; - - if (px == qx) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - newpoints[l + accumulateOffset] += qy; - bottom = qy; - - i += ps; - j += otherps; - } - else if (px > qx) { - // we got past point below, might need to - // insert interpolated extra point - if (withlines && i > 0 && points[i - ps] != null) { - intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); - newpoints.push(qx); - newpoints.push(intery + qy); - for (m = 2; m < ps; ++m) - newpoints.push(points[i + m]); - bottom = qy; - } - - j += otherps; - } - else { // px < qx - if (fromgap && withlines) { - // if we come from a gap, we just skip this point - i += ps; - continue; - } - - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - // we might be able to interpolate a point below, - // this can give us a better y - if (withlines && j > 0 && otherpoints[j - otherps] != null) - bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); - - newpoints[l + accumulateOffset] += bottom; - - i += ps; - } - - fromgap = false; - - if (l != newpoints.length && withbottom) - newpoints[l + 2] += bottom; - } - - // maintain the line steps invariant - if (withsteps && l != newpoints.length && l > 0 - && newpoints[l] != null - && newpoints[l] != newpoints[l - ps] - && newpoints[l + 1] != newpoints[l - ps + 1]) { - for (m = 0; m < ps; ++m) - newpoints[l + ps + m] = newpoints[l + m]; - newpoints[l + 1] = newpoints[l - ps + 1]; - } - } - - datapoints.points = newpoints; - } - - plot.hooks.processDatapoints.push(stackData); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'stack', - version: '1.2' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.symbol.js b/src/legacy/ui/public/flot-charts/jquery.flot.symbol.js deleted file mode 100644 index 79f634971b6fa..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.symbol.js +++ /dev/null @@ -1,71 +0,0 @@ -/* Flot plugin that adds some extra symbols for plotting points. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The symbols are accessed as strings through the standard symbol options: - - series: { - points: { - symbol: "square" // or "diamond", "triangle", "cross" - } - } - -*/ - -(function ($) { - function processRawData(plot, series, datapoints) { - // we normalize the area of each symbol so it is approximately the - // same as a circle of the given radius - - var handlers = { - square: function (ctx, x, y, radius, shadow) { - // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.rect(x - size, y - size, size + size, size + size); - }, - diamond: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) - var size = radius * Math.sqrt(Math.PI / 2); - ctx.moveTo(x - size, y); - ctx.lineTo(x, y - size); - ctx.lineTo(x + size, y); - ctx.lineTo(x, y + size); - ctx.lineTo(x - size, y); - }, - triangle: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) - var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); - var height = size * Math.sin(Math.PI / 3); - ctx.moveTo(x - size/2, y + height/2); - ctx.lineTo(x + size/2, y + height/2); - if (!shadow) { - ctx.lineTo(x, y - height/2); - ctx.lineTo(x - size/2, y + height/2); - } - }, - cross: function (ctx, x, y, radius, shadow) { - // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.moveTo(x - size, y - size); - ctx.lineTo(x + size, y + size); - ctx.moveTo(x - size, y + size); - ctx.lineTo(x + size, y - size); - } - }; - - var s = series.points.symbol; - if (handlers[s]) - series.points.symbol = handlers[s]; - } - - function init(plot) { - plot.hooks.processDatapoints.push(processRawData); - } - - $.plot.plugins.push({ - init: init, - name: 'symbols', - version: '1.0' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.threshold.js b/src/legacy/ui/public/flot-charts/jquery.flot.threshold.js deleted file mode 100644 index 8c99c401d87e5..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.threshold.js +++ /dev/null @@ -1,142 +0,0 @@ -/* Flot plugin for thresholding data. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin supports these options: - - series: { - threshold: { - below: number - color: colorspec - } - } - -It can also be applied to a single series, like this: - - $.plot( $("#placeholder"), [{ - data: [ ... ], - threshold: { ... } - }]) - -An array can be passed for multiple thresholding, like this: - - threshold: [{ - below: number1 - color: color1 - },{ - below: number2 - color: color2 - }] - -These multiple threshold objects can be passed in any order since they are -sorted by the processing function. - -The data points below "below" are drawn with the specified color. This makes -it easy to mark points below 0, e.g. for budget data. - -Internally, the plugin works by splitting the data into two series, above and -below the threshold. The extra series below the threshold will have its label -cleared and the special "originSeries" attribute set to the original series. -You may need to check for this in hover events. - -*/ - -(function ($) { - var options = { - series: { threshold: null } // or { below: number, color: color spec} - }; - - function init(plot) { - function thresholdData(plot, s, datapoints, below, color) { - var ps = datapoints.pointsize, i, x, y, p, prevp, - thresholded = $.extend({}, s); // note: shallow copy - - thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format }; - thresholded.label = null; - thresholded.color = color; - thresholded.threshold = null; - thresholded.originSeries = s; - thresholded.data = []; - - var origpoints = datapoints.points, - addCrossingPoints = s.lines.show; - - var threspoints = []; - var newpoints = []; - var m; - - for (i = 0; i < origpoints.length; i += ps) { - x = origpoints[i]; - y = origpoints[i + 1]; - - prevp = p; - if (y < below) - p = threspoints; - else - p = newpoints; - - if (addCrossingPoints && prevp != p && x != null - && i > 0 && origpoints[i - ps] != null) { - var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]); - prevp.push(interx); - prevp.push(below); - for (m = 2; m < ps; ++m) - prevp.push(origpoints[i + m]); - - p.push(null); // start new segment - p.push(null); - for (m = 2; m < ps; ++m) - p.push(origpoints[i + m]); - p.push(interx); - p.push(below); - for (m = 2; m < ps; ++m) - p.push(origpoints[i + m]); - } - - p.push(x); - p.push(y); - for (m = 2; m < ps; ++m) - p.push(origpoints[i + m]); - } - - datapoints.points = newpoints; - thresholded.datapoints.points = threspoints; - - if (thresholded.datapoints.points.length > 0) { - var origIndex = $.inArray(s, plot.getData()); - // Insert newly-generated series right after original one (to prevent it from becoming top-most) - plot.getData().splice(origIndex + 1, 0, thresholded); - } - - // FIXME: there are probably some edge cases left in bars - } - - function processThresholds(plot, s, datapoints) { - if (!s.threshold) - return; - - if (s.threshold instanceof Array) { - s.threshold.sort(function(a, b) { - return a.below - b.below; - }); - - $(s.threshold).each(function(i, th) { - thresholdData(plot, s, datapoints, th.below, th.color); - }); - } - else { - thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color); - } - } - - plot.hooks.processDatapoints.push(processThresholds); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'threshold', - version: '1.2' - }); -})(jQuery); diff --git a/src/legacy/ui/public/flot-charts/jquery.flot.time.js b/src/legacy/ui/public/flot-charts/jquery.flot.time.js deleted file mode 100644 index 7612a03302764..0000000000000 --- a/src/legacy/ui/public/flot-charts/jquery.flot.time.js +++ /dev/null @@ -1,473 +0,0 @@ -/* Pretty handling of time axes. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -Set axis.mode to "time" to enable. See the section "Time series data" in -API.txt for details. - -*/ - -import { i18n } from '@kbn/i18n'; - -(function($) { - - var options = { - xaxis: { - timezone: null, // "browser" for local to the client or timezone for timezone-js - timeformat: null, // format string to use - twelveHourClock: false, // 12 or 24 time in time mode - monthNames: null // list of names of months - } - }; - - // round to nearby lower multiple of base - - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - - // Returns a string with the date d formatted according to fmt. - // A subset of the Open Group's strftime format is supported. - - function formatDate(d, fmt, monthNames, dayNames) { - - if (typeof d.strftime == "function") { - return d.strftime(fmt); - } - - var leftPad = function(n, pad) { - n = "" + n; - pad = "" + (pad == null ? "0" : pad); - return n.length == 1 ? pad + n : n; - }; - - var r = []; - var escape = false; - var hours = d.getHours(); - var isAM = hours < 12; - - if (monthNames == null) { - monthNames = [ - i18n.translate('common.ui.flotCharts.janLabel', { - defaultMessage: 'Jan', - }), i18n.translate('common.ui.flotCharts.febLabel', { - defaultMessage: 'Feb', - }), i18n.translate('common.ui.flotCharts.marLabel', { - defaultMessage: 'Mar', - }), i18n.translate('common.ui.flotCharts.aprLabel', { - defaultMessage: 'Apr', - }), i18n.translate('common.ui.flotCharts.mayLabel', { - defaultMessage: 'May', - }), i18n.translate('common.ui.flotCharts.junLabel', { - defaultMessage: 'Jun', - }), i18n.translate('common.ui.flotCharts.julLabel', { - defaultMessage: 'Jul', - }), i18n.translate('common.ui.flotCharts.augLabel', { - defaultMessage: 'Aug', - }), i18n.translate('common.ui.flotCharts.sepLabel', { - defaultMessage: 'Sep', - }), i18n.translate('common.ui.flotCharts.octLabel', { - defaultMessage: 'Oct', - }), i18n.translate('common.ui.flotCharts.novLabel', { - defaultMessage: 'Nov', - }), i18n.translate('common.ui.flotCharts.decLabel', { - defaultMessage: 'Dec', - })]; - } - - if (dayNames == null) { - dayNames = [i18n.translate('common.ui.flotCharts.sunLabel', { - defaultMessage: 'Sun', - }), i18n.translate('common.ui.flotCharts.monLabel', { - defaultMessage: 'Mon', - }), i18n.translate('common.ui.flotCharts.tueLabel', { - defaultMessage: 'Tue', - }), i18n.translate('common.ui.flotCharts.wedLabel', { - defaultMessage: 'Wed', - }), i18n.translate('common.ui.flotCharts.thuLabel', { - defaultMessage: 'Thu', - }), i18n.translate('common.ui.flotCharts.friLabel', { - defaultMessage: 'Fri', - }), i18n.translate('common.ui.flotCharts.satLabel', { - defaultMessage: 'Sat', - })]; - } - - var hours12; - - if (hours > 12) { - hours12 = hours - 12; - } else if (hours == 0) { - hours12 = 12; - } else { - hours12 = hours; - } - - for (var i = 0; i < fmt.length; ++i) { - - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'a': c = "" + dayNames[d.getDay()]; break; - case 'b': c = "" + monthNames[d.getMonth()]; break; - case 'd': c = leftPad(d.getDate()); break; - case 'e': c = leftPad(d.getDate(), " "); break; - case 'h': // For back-compat with 0.7; remove in 1.0 - case 'H': c = leftPad(hours); break; - case 'I': c = leftPad(hours12); break; - case 'l': c = leftPad(hours12, " "); break; - case 'm': c = leftPad(d.getMonth() + 1); break; - case 'M': c = leftPad(d.getMinutes()); break; - // quarters not in Open Group's strftime specification - case 'q': - c = "" + (Math.floor(d.getMonth() / 3) + 1); break; - case 'S': c = leftPad(d.getSeconds()); break; - case 'y': c = leftPad(d.getFullYear() % 100); break; - case 'Y': c = "" + d.getFullYear(); break; - case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; - case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; - case 'w': c = "" + d.getDay(); break; - } - r.push(c); - escape = false; - } else { - if (c == "%") { - escape = true; - } else { - r.push(c); - } - } - } - - return r.join(""); - } - - // To have a consistent view of time-based data independent of which time - // zone the client happens to be in we need a date-like object independent - // of time zones. This is done through a wrapper that only calls the UTC - // versions of the accessor methods. - - function makeUtcWrapper(d) { - - function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { - sourceObj[sourceMethod] = function() { - return targetObj[targetMethod].apply(targetObj, arguments); - }; - }; - - var utc = { - date: d - }; - - // support strftime, if found - - if (d.strftime != undefined) { - addProxyMethod(utc, "strftime", d, "strftime"); - } - - addProxyMethod(utc, "getTime", d, "getTime"); - addProxyMethod(utc, "setTime", d, "setTime"); - - var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; - - for (var p = 0; p < props.length; p++) { - addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); - addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); - } - - return utc; - }; - - // select time zone strategy. This returns a date-like object tied to the - // desired timezone - - function dateGenerator(ts, opts) { - if (opts.timezone == "browser") { - return new Date(ts); - } else if (!opts.timezone || opts.timezone == "utc") { - return makeUtcWrapper(new Date(ts)); - } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { - var d = new timezoneJS.Date(); - // timezone-js is fickle, so be sure to set the time zone before - // setting the time. - d.setTimezone(opts.timezone); - d.setTime(ts); - return d; - } else { - return makeUtcWrapper(new Date(ts)); - } - } - - // map of app. size of time units in milliseconds - - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "quarter": 3 * 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - - var baseSpec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"] - ]; - - // we don't know which variant(s) we'll need yet, but generating both is - // cheap - - var specMonths = baseSpec.concat([[3, "month"], [6, "month"], - [1, "year"]]); - var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], - [1, "year"]]); - - function init(plot) { - plot.hooks.processOptions.push(function (plot, options) { - $.each(plot.getAxes(), function(axisName, axis) { - - var opts = axis.options; - - if (opts.mode == "time") { - axis.tickGenerator = function(axis) { - - var ticks = []; - var d = dateGenerator(axis.min, opts); - var minSize = 0; - - // make quarter use a possibility if quarters are - // mentioned in either of these options - - var spec = (opts.tickSize && opts.tickSize[1] === - "quarter") || - (opts.minTickSize && opts.minTickSize[1] === - "quarter") ? specQuarters : specMonths; - - if (opts.minTickSize != null) { - if (typeof opts.tickSize == "number") { - minSize = opts.tickSize; - } else { - minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; - } - } - - for (var i = 0; i < spec.length - 1; ++i) { - if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { - break; - } - } - - var size = spec[i][0]; - var unit = spec[i][1]; - - // special-case the possibility of several years - - if (unit == "year") { - - // if given a minTickSize in years, just use it, - // ensuring that it's an integer - - if (opts.minTickSize != null && opts.minTickSize[1] == "year") { - size = Math.floor(opts.minTickSize[0]); - } else { - - var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); - var norm = (axis.delta / timeUnitSize.year) / magn; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - } - - // minimum size for years is 1 - - if (size < 1) { - size = 1; - } - } - - axis.tickSize = opts.tickSize || [size, unit]; - var tickSize = axis.tickSize[0]; - unit = axis.tickSize[1]; - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") { - d.setSeconds(floorInBase(d.getSeconds(), tickSize)); - } else if (unit == "minute") { - d.setMinutes(floorInBase(d.getMinutes(), tickSize)); - } else if (unit == "hour") { - d.setHours(floorInBase(d.getHours(), tickSize)); - } else if (unit == "month") { - d.setMonth(floorInBase(d.getMonth(), tickSize)); - } else if (unit == "quarter") { - d.setMonth(3 * floorInBase(d.getMonth() / 3, - tickSize)); - } else if (unit == "year") { - d.setFullYear(floorInBase(d.getFullYear(), tickSize)); - } - - // reset smaller components - - d.setMilliseconds(0); - - if (step >= timeUnitSize.minute) { - d.setSeconds(0); - } - if (step >= timeUnitSize.hour) { - d.setMinutes(0); - } - if (step >= timeUnitSize.day) { - d.setHours(0); - } - if (step >= timeUnitSize.day * 4) { - d.setDate(1); - } - if (step >= timeUnitSize.month * 2) { - d.setMonth(floorInBase(d.getMonth(), 3)); - } - if (step >= timeUnitSize.quarter * 2) { - d.setMonth(floorInBase(d.getMonth(), 6)); - } - if (step >= timeUnitSize.year) { - d.setMonth(0); - } - - var carry = 0; - var v = Number.NaN; - var prev; - - do { - - prev = v; - v = d.getTime(); - ticks.push(v); - - if (unit == "month" || unit == "quarter") { - if (tickSize < 1) { - - // a bit complicated - we'll divide the - // month/quarter up but we need to take - // care of fractions so we don't end up in - // the middle of a day - - d.setDate(1); - var start = d.getTime(); - d.setMonth(d.getMonth() + - (unit == "quarter" ? 3 : 1)); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getHours(); - d.setHours(0); - } else { - d.setMonth(d.getMonth() + - tickSize * (unit == "quarter" ? 3 : 1)); - } - } else if (unit == "year") { - d.setFullYear(d.getFullYear() + tickSize); - } else { - d.setTime(v + step); - } - } while (v < axis.max && v != prev); - - return ticks; - }; - - axis.tickFormatter = function (v, axis) { - - var d = dateGenerator(v, axis.options); - - // first check global format - - if (opts.timeformat != null) { - return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); - } - - // possibly use quarters if quarters are mentioned in - // any of these places - - var useQuarters = (axis.options.tickSize && - axis.options.tickSize[1] == "quarter") || - (axis.options.minTickSize && - axis.options.minTickSize[1] == "quarter"); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - var suffix = (opts.twelveHourClock) ? " %p" : ""; - var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; - var fmt; - - if (t < timeUnitSize.minute) { - fmt = hourCode + ":%M:%S" + suffix; - } else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) { - fmt = hourCode + ":%M" + suffix; - } else { - fmt = "%b %d " + hourCode + ":%M" + suffix; - } - } else if (t < timeUnitSize.month) { - fmt = "%b %d"; - } else if ((useQuarters && t < timeUnitSize.quarter) || - (!useQuarters && t < timeUnitSize.year)) { - if (span < timeUnitSize.year) { - fmt = "%b"; - } else { - fmt = "%b %Y"; - } - } else if (useQuarters && t < timeUnitSize.year) { - if (span < timeUnitSize.year) { - fmt = "Q%q"; - } else { - fmt = "Q%q %Y"; - } - } else { - fmt = "%Y"; - } - - var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); - - return rt; - }; - } - }); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'time', - version: '1.0' - }); - - // Time-axis support used to be in Flot core, which exposed the - // formatDate function on the plot object. Various plugins depend - // on the function, so we need to re-expose it here. - - $.plot.formatDate = formatDate; - $.plot.dateGenerator = dateGenerator; - -})(jQuery); diff --git a/src/legacy/ui/public/i18n/__snapshots__/index.test.tsx.snap b/src/legacy/ui/public/i18n/__snapshots__/index.test.tsx.snap deleted file mode 100644 index fd6a0a07ba39c..0000000000000 --- a/src/legacy/ui/public/i18n/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,10 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ui/i18n renders children and forwards properties 1`] = ` -
- Context: - - Child: some prop:100500 - -
-`; diff --git a/src/legacy/ui/public/i18n/index.test.tsx b/src/legacy/ui/public/i18n/index.test.tsx deleted file mode 100644 index be8ab4cf8d696..0000000000000 --- a/src/legacy/ui/public/i18n/index.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { render } from 'enzyme'; -import PropTypes from 'prop-types'; -import React from 'react'; - -jest.mock('angular-sanitize', () => {}); -jest.mock('ui/new_platform', () => ({ - npStart: { - core: { - i18n: { Context: ({ children }: any) =>
Context: {children}
}, - }, - }, -})); - -import { wrapInI18nContext } from '.'; - -describe('ui/i18n', () => { - test('renders children and forwards properties', () => { - const mockPropTypes = { - stringProp: PropTypes.string.isRequired, - numberProp: PropTypes.number, - }; - - const WrappedComponent = wrapInI18nContext( - class extends React.PureComponent<{ [P in keyof typeof mockPropTypes]: unknown }> { - public static propTypes = mockPropTypes; - - public render() { - return ( - - Child: {this.props.stringProp}:{this.props.numberProp} - - ); - } - } - ); - - expect(WrappedComponent.propTypes).toBe(mockPropTypes); - expect( - render() - ).toMatchSnapshot(); - }); -}); diff --git a/src/legacy/ui/public/i18n/index.tsx b/src/legacy/ui/public/i18n/index.tsx deleted file mode 100644 index 290e82a1334b9..0000000000000 --- a/src/legacy/ui/public/i18n/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -// required for `ngSanitize` angular module -import 'angular-sanitize'; - -import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { npStart } from 'ui/new_platform'; - -export const I18nContext = npStart.core.i18n.Context; - -export function wrapInI18nContext

(ComponentToWrap: React.ComponentType

) { - const ContextWrapper: React.FC

= (props) => { - return ( - - - - ); - }; - - // Original propTypes from the wrapped component should be re-exposed - // since it will be used by reactDirective Angular service - // that will rely on propTypes to watch attributes with these names - ContextWrapper.propTypes = ComponentToWrap.propTypes; - - return ContextWrapper; -} - -uiModules - .get('i18n', ['ngSanitize']) - .provider('i18n', I18nProvider) - .filter('i18n', i18nFilter) - .directive('i18nId', i18nDirective); diff --git a/src/legacy/ui/public/indexed_array/__tests__/indexed_array.js b/src/legacy/ui/public/indexed_array/__tests__/indexed_array.js deleted file mode 100644 index df96a58a6e99f..0000000000000 --- a/src/legacy/ui/public/indexed_array/__tests__/indexed_array.js +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { IndexedArray } from '..'; - -// this is generally a data-structure that IndexedArray is good for managing -const users = [ - { name: 'John', id: 6, username: 'beast', group: 'admins' }, - { name: 'Anon', id: 0, username: 'shhhh', group: 'secret' }, - { name: 'Fern', id: 42, username: 'kitty', group: 'editor' }, - { name: 'Mary', id: 55, username: 'sheep', group: 'editor' }, -]; - -// this is how we used to accomplish this, before IndexedArray -users.byName = _.keyBy(users, 'name'); -users.byUsername = _.keyBy(users, 'username'); -users.byGroup = _.groupBy(users, 'group'); -users.inIdOrder = _.sortBy(users, 'id'); - -// then things started becoming unruly... so IndexedArray! - -describe('IndexedArray', function () { - describe('Basics', function () { - let reg; - - beforeEach(function () { - reg = new IndexedArray(); - }); - - it('Extends Array', function () { - expect(reg).to.be.a(Array); - }); - - it('fails basic lodash check', function () { - expect(Array.isArray(reg)).to.be(false); - }); - - it('clones to an object', function () { - expect(_.isObject(_.clone(reg))).to.be(true); - expect(Array.isArray(_.clone(reg))).to.be(false); - }); - }); - - describe('Indexing', function () { - it('provides the initial set', function () { - const reg = new IndexedArray({ - initialSet: [1, 2, 3], - }); - - expect(reg).to.have.length(3); - - reg.forEach(function (v, i) { - expect(v).to.eql(i + 1); - }); - }); - - it('indexes the initial set', function () { - const reg = new IndexedArray({ - index: ['username'], - initialSet: users, - }); - - expect(reg).to.have.property('byUsername'); - expect(reg.byUsername).to.eql(users.byUsername); - }); - - it('updates indices after values are added', function () { - // split up the user list, and add it in chunks - const firstUser = users.slice(0, 1).pop(); - const otherUsers = users.slice(1); - - // start off with all but the first - const reg = new IndexedArray({ - group: ['group'], - order: ['id'], - initialSet: otherUsers, - }); - - // add the first - reg.push(firstUser); - - // end up with the same structure that is in the users fixture - expect(Object.keys(reg.byGroup).length).to.be(Object.keys(users.byGroup).length); - for (const group of Object.keys(reg.byGroup)) { - expect(reg.byGroup[group].toJSON()).to.eql(users.byGroup[group]); - } - - expect(reg.inIdOrder).to.eql(users.inIdOrder); - }); - - it('updates indices after values are removed', function () { - // start off with all - const reg = new IndexedArray({ - group: ['group'], - order: ['id'], - initialSet: users, - }); - - // remove the last - reg.pop(); - - const expectedCount = users.length - 1; - // indexed lists should be updated - expect(reg).to.have.length(expectedCount); - - const sumOfGroups = _.reduce( - reg.byGroup, - function (note, group) { - return note + group.length; - }, - 0 - ); - expect(sumOfGroups).to.eql(expectedCount); - }); - - it('removes items based on a predicate', function () { - const reg = new IndexedArray({ - group: ['group'], - order: ['id'], - initialSet: users, - }); - - reg.remove({ name: 'John' }); - - expect(_.isEqual(reg.raw, reg.slice(0))).to.be(true); - expect(reg.length).to.be(3); - expect(reg[0].name).to.be('Anon'); - }); - - it('updates indices after values are re-ordered', function () { - const rawUsers = users.slice(0); - - // collect and shuffle the ids available - let ids = []; - _.times(rawUsers.length, function (i) { - ids.push(i); - }); - ids = _.shuffle(ids); - - // move something here - const toI = ids.shift(); - // from here - const fromI = ids.shift(); - // do the move - const move = function (arr) { - arr.splice(toI, 0, arr.splice(fromI, 1)[0]); - }; - - const reg = new IndexedArray({ - index: ['username'], - initialSet: rawUsers, - }); - - const index = reg.byUsername; - - move(reg); - - expect(reg.byUsername).to.eql(index); - expect(reg.byUsername).to.not.be(index); - }); - }); - - describe('Ordering', function () { - it('ordering is case insensitive', function () { - const reg = new IndexedArray({ - index: ['title'], - order: ['title'], - initialSet: [{ title: 'APM' }, { title: 'Advanced Settings' }], - }); - - const ordered = reg.inTitleOrder; - expect(ordered[0].title).to.be('Advanced Settings'); - expect(ordered[1].title).to.be('APM'); - }); - - it('ordering handles numbers', function () { - const reg = new IndexedArray({ - index: ['id'], - order: ['id'], - initialSet: users, - }); - - const ordered = reg.inIdOrder; - expect(ordered[0].id).to.be(0); - expect(ordered[1].id).to.be(6); - expect(ordered[2].id).to.be(42); - expect(ordered[3].id).to.be(55); - }); - }); -}); diff --git a/src/legacy/ui/public/indexed_array/__tests__/inflector.js b/src/legacy/ui/public/indexed_array/__tests__/inflector.js deleted file mode 100644 index 49ac79094e501..0000000000000 --- a/src/legacy/ui/public/indexed_array/__tests__/inflector.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { inflector } from '../inflector'; -import expect from '@kbn/expect'; - -describe('IndexedArray Inflector', function () { - it('returns a function', function () { - const getter = inflector(); - expect(getter).to.be.a('function'); - }); - - describe('fn', function () { - it('prepends a prefix', function () { - const inflect = inflector('my'); - - expect(inflect('Family')).to.be('myFamily'); - expect(inflect('family')).to.be('myFamily'); - expect(inflect('fAmIlY')).to.be('myFAmIlY'); - }); - - it('adds both a prefix and suffix', function () { - const inflect = inflector('foo', 'Bar'); - - expect(inflect('box')).to.be('fooBoxBar'); - expect(inflect('box.car.MAX')).to.be('fooBoxCarMaxBar'); - expect(inflect('BaZzY')).to.be('fooBaZzYBar'); - }); - - it('ignores prefix if it is already at the end of the inflected string', function () { - const inflect = inflector('foo', 'Bar'); - expect(inflect('fooBox')).to.be('fooBoxBar'); - expect(inflect('FooBox')).to.be('FooBoxBar'); - }); - - it('ignores postfix if it is already at the end of the inflected string', function () { - const inflect = inflector('foo', 'Bar'); - expect(inflect('bar')).to.be('fooBar'); - expect(inflect('showBoxBar')).to.be('fooShowBoxBar'); - }); - - it('works with "name"', function () { - const inflect = inflector('in', 'Order'); - expect(inflect('name')).to.be('inNameOrder'); - }); - }); -}); diff --git a/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts b/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts deleted file mode 100644 index fc4ca8469382a..0000000000000 --- a/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { groupBy } from 'lodash'; -import { organizeBy } from './organize_by'; - -describe('organizeBy', () => { - test('it works', () => { - const col = [ - { - name: 'one', - roles: ['user', 'admin', 'owner'], - }, - { - name: 'two', - roles: ['user'], - }, - { - name: 'three', - roles: ['user'], - }, - { - name: 'four', - roles: ['user', 'admin'], - }, - ]; - - const resp = organizeBy(col, 'roles'); - expect(resp).toHaveProperty('user'); - expect(resp.user.length).toBe(4); - - expect(resp).toHaveProperty('admin'); - expect(resp.admin.length).toBe(2); - - expect(resp).toHaveProperty('owner'); - expect(resp.owner.length).toBe(1); - }); - - test('behaves just like groupBy in normal scenarios', () => { - const col = [{ name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four' }]; - - const orgs = organizeBy(col, 'name'); - const groups = groupBy(col, 'name'); - - expect(orgs).toEqual(groups); - }); -}); diff --git a/src/legacy/ui/public/indexed_array/helpers/organize_by.ts b/src/legacy/ui/public/indexed_array/helpers/organize_by.ts deleted file mode 100644 index e923767c892cd..0000000000000 --- a/src/legacy/ui/public/indexed_array/helpers/organize_by.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { each, isFunction } from 'lodash'; - -/** - * Like _.groupBy, but allows specifying multiple groups for a - * single object. - * - * organizeBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') - * // Object {1: Array[2], 2: Array[1], 3: Array[1], 4: Array[1]} - * - * _.groupBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') - * // Object {'1,2,3': Array[1], '1,4': Array[1]} - * - * @param {array} collection - the list of values to organize - * @param {Function} callback - either a property name, or a callback. - * @return {object} - */ -export function organizeBy(collection: object[], callback: ((obj: object) => string) | string) { - const buckets: { [key: string]: object[] } = {}; - - function add(key: string, obj: object) { - if (!buckets[key]) { - buckets[key] = []; - } - buckets[key].push(obj); - } - - each(collection, (obj: Record) => { - const keys = isFunction(callback) ? callback(obj) : obj[callback]; - - if (!Array.isArray(keys)) { - add(keys, obj); - return; - } - - let length = keys.length; - while (length-- > 0) { - add(keys[length], obj); - } - }); - - return buckets; -} diff --git a/src/legacy/ui/public/indexed_array/index.d.ts b/src/legacy/ui/public/indexed_array/index.d.ts deleted file mode 100644 index 21c0a818731ac..0000000000000 --- a/src/legacy/ui/public/indexed_array/index.d.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ListIterator } from 'lodash'; - -interface IndexedArrayConfig { - index?: string[]; - group?: string[]; - order?: string[]; - initialSet?: T[]; - immutable?: boolean; -} - -declare class IndexedArray extends Array { - public immutable: boolean; - public raw: T[]; - - // These may not actually be present, as they are dynamically defined - public inOrder: T[]; - public byType: Record; - public byName: Record; - - constructor(config: IndexedArrayConfig); - - public remove(predicate: ListIterator): T[]; - - public toJSON(): T[]; -} diff --git a/src/legacy/ui/public/indexed_array/index.js b/src/legacy/ui/public/indexed_array/index.js deleted file mode 100644 index 6a42961c9e680..0000000000000 --- a/src/legacy/ui/public/indexed_array/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { IndexedArray } from './indexed_array'; diff --git a/src/legacy/ui/public/indexed_array/indexed_array.js b/src/legacy/ui/public/indexed_array/indexed_array.js deleted file mode 100644 index b9a427b8da7ad..0000000000000 --- a/src/legacy/ui/public/indexed_array/indexed_array.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { inflector } from './inflector'; -import { organizeBy } from './helpers/organize_by'; - -const pathGetter = _(_.get).rearg(1, 0).ary(2); -const inflectIndex = inflector('by'); -const inflectOrder = inflector('in', 'Order'); - -const CLEAR_CACHE = {}; -const OPT_NAMES = ['index', 'group', 'order', 'initialSet', 'immutable']; - -/** - * Generic extension of Array class, which will index (and reindex) the - * objects it contains based on their properties. - * - * @param {Object} config describes the properties of this registry object - * @param {Array} [config.index] a list of props/paths that should be used to index the docs. - * @param {Array} [config.group] a list of keys/paths to group docs by. - * @param {Array} [config.order] a list of keys/paths to order the keys by. - * @param {Array} [config.initialSet] the initial dataset the IndexedArray should contain. - * @param {boolean} [config.immutable] a flag that hints to people reading the implementation that this IndexedArray - * should not be modified - */ - -export class IndexedArray { - static OPT_NAMES = OPT_NAMES; - - constructor(config) { - config = _.pick(config || {}, OPT_NAMES); - - // use defineProperty so that value can't be changed - Object.defineProperty(this, 'raw', { value: [] }); - - this._indexNames = _.union( - this._setupIndex(config.group, inflectIndex, organizeByIndexedArray(config)), - this._setupIndex(config.index, inflectIndex, _.keyBy), - this._setupIndex(config.order, inflectOrder, (raw, pluckValue) => { - return [...raw].sort((itemA, itemB) => { - const a = pluckValue(itemA); - const b = pluckValue(itemB); - if (typeof a === 'number' && typeof b === 'number') { - return a - b; - } - return String(a).toLowerCase().localeCompare(String(b).toLowerCase()); - }); - }) - ); - - if (config.initialSet) { - this.push.apply(this, config.initialSet); - } - - Object.defineProperty(this, 'immutable', { value: !!config.immutable }); - } - - /** - * Remove items from this based on a predicate - * @param {Function|Object|string} predicate - the predicate used to decide what is removed - * @return {array} - the removed data - */ - remove(predicate) { - this._assertMutable('remove'); - const out = _.remove(this, predicate); - _.remove(this.raw, predicate); - this._clearIndices(); - return out; - } - - /** - * provide a hook for the JSON serializer - * @return {array} - a plain, vanilla array with our same data - */ - toJSON() { - return this.raw; - } - - // wrappers for mutable Array methods - copyWithin(...args) { - return this._mutation('copyWithin', args); - } - fill(...args) { - return this._mutation('fill', args); - } - pop(...args) { - return this._mutation('pop', args); - } - push(...args) { - return this._mutation('push', args); - } - reverse(...args) { - return this._mutation('reverse', args); - } - shift(...args) { - return this._mutation('shift', args); - } - sort(...args) { - return this._mutation('sort', args); - } - splice(...args) { - return this._mutation('splice', args); - } - unshift(...args) { - return this._mutation('unshift', args); - } - - /** - * If this instance of IndexedArray is not mutable, throw an error - * @private - * @param {String} methodName - user facing method name, for error message - * @return {undefined} - */ - _assertMutable(methodName) { - if (this.immutable) { - throw new Error(`${methodName}() is not allowed on immutable IndexedArray instances`); - } - } - - /** - * Execute some mutable method from the Array prototype - * on the IndexedArray and this.raw - * - * @private - * @param {string} methodName - * @param {Array} args - * @return {any} - */ - _mutation(methodName, args) { - this._assertMutable(methodName); - super[methodName].apply(this, args); - this._clearIndices(); - return super[methodName].apply(this.raw, args); - } - - /** - * Create indices for a group of object properties. getters and setters are used to - * read and control the indices. - * @private - * @param {string[]} props - the properties that should be used to index docs - * @param {function} inflect - a function that will be called with a property name, and - * creates the public property at which the index will be exposed - * @param {function} op - the function that will be used to create the indices, it is passed - * the raw representation of the registry, and a getter for reading the - * right prop - * - * @returns {string[]} - the public keys of all indices created - */ - _setupIndex(props, inflect, op) { - // shortcut for empty props - if (!props || props.length === 0) return; - - return props.map((prop) => { - const indexName = inflect(prop); - const getIndexValueFromItem = pathGetter.partial(prop).value(); - let cache; - - Object.defineProperty(this, indexName, { - enumerable: false, - configurable: false, - - set: (val) => { - // can't set any value other than the CLEAR_CACHE constant - if (val === CLEAR_CACHE) { - cache = false; - } else { - throw new TypeError(indexName + ' can not be set, it is a computed index of values'); - } - }, - get: () => { - if (!cache) { - cache = op(this.raw, getIndexValueFromItem); - } - - return cache; - }, - }); - - return indexName; - }); - } - - /** - * Clear cached index/group/order caches so they will be recreated - * on next access - * @private - * @return {undefined} - */ - _clearIndices() { - this._indexNames.forEach((name) => { - this[name] = CLEAR_CACHE; - }); - } -} - -// using traditional `extends Array` syntax doesn't work with babel -// See https://babeljs.io/docs/usage/caveats/ -Object.setPrototypeOf(IndexedArray.prototype, Array.prototype); - -// Similar to `organizeBy` but returns IndexedArrays instead of normal Arrays. -function organizeByIndexedArray(config) { - return (...args) => { - const grouped = organizeBy(...args); - - return _.reduce( - grouped, - (acc, value, group) => { - acc[group] = new IndexedArray({ - ...config, - initialSet: value, - }); - - return acc; - }, - {} - ); - }; -} diff --git a/src/legacy/ui/public/indexed_array/inflector.js b/src/legacy/ui/public/indexed_array/inflector.js deleted file mode 100644 index e034146f5f62f..0000000000000 --- a/src/legacy/ui/public/indexed_array/inflector.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -function upFirst(str, total) { - return str.charAt(0).toUpperCase() + (total ? str.substr(1).toLowerCase() : str.substr(1)); -} - -function startsWith(str, test) { - return str.substr(0, test.length).toLowerCase() === test.toLowerCase(); -} - -function endsWith(str, test) { - const tooShort = str.length < test.length; - if (tooShort) return; - - return str.substr(str.length - test.length).toLowerCase() === test.toLowerCase(); -} - -export function inflector(prefix, postfix) { - return function inflect(key) { - let inflected; - - if (key.indexOf('.') !== -1) { - inflected = key - .split('.') - .map(function (step, i) { - return i === 0 ? step : upFirst(step, true); - }) - .join(''); - } else { - inflected = key; - } - - if (prefix && !startsWith(key, prefix)) { - inflected = prefix + upFirst(inflected); - } - - if (postfix && !endsWith(key, postfix)) { - inflected = inflected + postfix; - } - - return inflected; - }; -} diff --git a/src/legacy/ui/public/kfetch/__mocks__/index.ts b/src/legacy/ui/public/kfetch/__mocks__/index.ts deleted file mode 100644 index 1a128e2b85260..0000000000000 --- a/src/legacy/ui/public/kfetch/__mocks__/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export const kfetch = () => Promise.resolve(); diff --git a/src/legacy/ui/public/kfetch/_import_objects.ndjson b/src/legacy/ui/public/kfetch/_import_objects.ndjson deleted file mode 100644 index 3511fb44cdfb2..0000000000000 --- a/src/legacy/ui/public/kfetch/_import_objects.ndjson +++ /dev/null @@ -1 +0,0 @@ -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Log Agents","uiStateJSON":"{}","visState":"{\"title\":\"Log Agents\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"agent.raw: Descending\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"agent.raw\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}]}"},"id":"082f1d60-a2e7-11e7-bb30-233be9be6a15","migrationVersion":{"visualization":"7.0.0"},"references":[{"id":"f1e4c910-a2e6-11e7-bb30-233be9be6a15","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","version":1} diff --git a/src/legacy/ui/public/kfetch/index.ts b/src/legacy/ui/public/kfetch/index.ts deleted file mode 100644 index 105df171ad370..0000000000000 --- a/src/legacy/ui/public/kfetch/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { npSetup } from 'ui/new_platform'; -import { createKfetch, KFetchKibanaOptions, KFetchOptions } from './kfetch'; -export { addInterceptor, KFetchOptions, KFetchQuery } from './kfetch'; - -const kfetchInstance = createKfetch(npSetup.core.http); - -export const kfetch = (options: KFetchOptions, kfetchOptions?: KFetchKibanaOptions) => { - return kfetchInstance(options, kfetchOptions); -}; diff --git a/src/legacy/ui/public/kfetch/kfetch.test.mocks.ts b/src/legacy/ui/public/kfetch/kfetch.test.mocks.ts deleted file mode 100644 index ea066b3623f13..0000000000000 --- a/src/legacy/ui/public/kfetch/kfetch.test.mocks.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { setup } from '../../../../test_utils/public/http_test_setup'; - -jest.doMock('ui/new_platform', () => ({ - npSetup: { - core: setup(), - }, -})); diff --git a/src/legacy/ui/public/kfetch/kfetch.test.ts b/src/legacy/ui/public/kfetch/kfetch.test.ts deleted file mode 100644 index c45a142d54e9b..0000000000000 --- a/src/legacy/ui/public/kfetch/kfetch.test.ts +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-ignore -import fetchMock from 'fetch-mock/es5/client'; -import './kfetch.test.mocks'; -import { readFileSync } from 'fs'; -import { join } from 'path'; -import { addInterceptor, kfetch, KFetchOptions } from '.'; -import { Interceptor, resetInterceptors, withDefaultOptions } from './kfetch'; -import { KFetchError } from './kfetch_error'; - -describe('kfetch', () => { - afterEach(() => { - fetchMock.restore(); - resetInterceptors(); - }); - - it('should use supplied request method', async () => { - fetchMock.post('*', {}); - await kfetch({ pathname: '/my/path', method: 'POST' }); - expect(fetchMock.lastOptions()!.method).toBe('POST'); - }); - - it('should use supplied Content-Type', async () => { - fetchMock.get('*', {}); - await kfetch({ pathname: '/my/path', headers: { 'Content-Type': 'CustomContentType' } }); - expect(fetchMock.lastOptions()!.headers).toMatchObject({ - 'content-type': 'CustomContentType', - }); - }); - - it('should use supplied pathname and querystring', async () => { - fetchMock.get('*', {}); - await kfetch({ pathname: '/my/path', query: { a: 'b' } }); - expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path?a=b'); - }); - - it('should use supplied headers', async () => { - fetchMock.get('*', {}); - await kfetch({ - pathname: '/my/path', - headers: { myHeader: 'foo' }, - }); - - expect(fetchMock.lastOptions()!.headers).toEqual({ - 'content-type': 'application/json', - 'kbn-version': 'kibanaVersion', - myheader: 'foo', - }); - }); - - it('should return response', async () => { - fetchMock.get('*', { foo: 'bar' }); - const res = await kfetch({ pathname: '/my/path' }); - expect(res).toEqual({ foo: 'bar' }); - }); - - it('should prepend url with basepath by default', async () => { - fetchMock.get('*', {}); - await kfetch({ pathname: '/my/path' }); - expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path'); - }); - - it('should not prepend url with basepath when disabled', async () => { - fetchMock.get('*', {}); - await kfetch({ pathname: '/my/path' }, { prependBasePath: false }); - expect(fetchMock.lastUrl()).toBe('/my/path'); - }); - - it('should make request with defaults', async () => { - fetchMock.get('*', {}); - await kfetch({ pathname: '/my/path' }); - - expect(fetchMock.lastCall()!.request.credentials).toBe('same-origin'); - expect(fetchMock.lastOptions()!).toMatchObject({ - method: 'GET', - headers: { - 'content-type': 'application/json', - 'kbn-version': 'kibanaVersion', - }, - }); - }); - - it('should make requests for NDJSON content', async () => { - const content = readFileSync(join(__dirname, '_import_objects.ndjson'), { encoding: 'utf-8' }); - - fetchMock.post('*', { - body: content, - headers: { 'Content-Type': 'application/ndjson' }, - }); - - const data = await kfetch({ - method: 'POST', - pathname: '/my/path', - body: content, - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - expect(data).toBeInstanceOf(Blob); - - const ndjson = await new Response(data).text(); - - expect(ndjson).toEqual(content); - }); - - it('should reject on network error', async () => { - expect.assertions(1); - fetchMock.get('*', { status: 500 }); - - try { - await kfetch({ pathname: '/my/path' }); - } catch (e) { - expect(e.message).toBe('Internal Server Error'); - } - }); - - describe('when throwing response error (KFetchError)', () => { - let error: KFetchError; - beforeEach(async () => { - fetchMock.get('*', { status: 404, body: { foo: 'bar' } }); - try { - await kfetch({ pathname: '/my/path' }); - } catch (e) { - error = e; - } - }); - - it('should contain error message', () => { - expect(error.message).toBe('Not Found'); - }); - - it('should return response body', () => { - expect(error.body).toEqual({ foo: 'bar' }); - }); - - it('should contain response properties', () => { - expect(error.res.status).toBe(404); - expect(error.res.url).toBe('http://localhost/myBase/my/path'); - }); - }); - - describe('when all interceptor resolves', () => { - let resp: any; - let interceptorCalls: string[]; - - beforeEach(async () => { - fetchMock.get('*', { foo: 'bar' }); - - interceptorCalls = mockInterceptorCalls([{}, {}, {}]); - resp = await kfetch({ pathname: '/my/path' }); - }); - - it('should call interceptors in correct order', () => { - expect(interceptorCalls).toEqual([ - 'Request #3', - 'Request #2', - 'Request #1', - 'Response #1', - 'Response #2', - 'Response #3', - ]); - }); - - it('should make request', () => { - expect(fetchMock.called()).toBe(true); - }); - - it('should return response', () => { - expect(resp).toEqual({ foo: 'bar' }); - }); - }); - - describe('when a request interceptor throws; and the next requestError interceptor resolves', () => { - let resp: any; - let interceptorCalls: string[]; - - beforeEach(async () => { - fetchMock.get('*', { foo: 'bar' }); - - interceptorCalls = mockInterceptorCalls([ - { requestError: () => ({ pathname: '/my/path' } as KFetchOptions) }, - { request: () => Promise.reject(new Error('Error in request')) }, - {}, - ]); - - resp = await kfetch({ pathname: '/my/path' }); - }); - - it('should call interceptors in correct order', () => { - expect(interceptorCalls).toEqual([ - 'Request #3', - 'Request #2', - 'RequestError #1', - 'Response #1', - 'Response #2', - 'Response #3', - ]); - }); - - it('should make request', () => { - expect(fetchMock.called()).toBe(true); - }); - - it('should return response', () => { - expect(resp).toEqual({ foo: 'bar' }); - }); - }); - - describe('when a request interceptor throws', () => { - let error: Error; - let interceptorCalls: string[]; - - beforeEach(async () => { - fetchMock.get('*', { foo: 'bar' }); - - interceptorCalls = mockInterceptorCalls([ - {}, - { request: () => Promise.reject(new Error('Error in request')) }, - {}, - ]); - - try { - await kfetch({ pathname: '/my/path' }); - } catch (e) { - error = e; - } - }); - - it('should call interceptors in correct order', () => { - expect(interceptorCalls).toEqual([ - 'Request #3', - 'Request #2', - 'RequestError #1', - 'ResponseError #1', - 'ResponseError #2', - 'ResponseError #3', - ]); - }); - - it('should not make request', () => { - expect(fetchMock.called()).toBe(false); - }); - - it('should throw error', () => { - expect(error.message).toEqual('Error in request'); - }); - }); - - describe('when a response interceptor throws', () => { - let error: Error; - let interceptorCalls: string[]; - - beforeEach(async () => { - fetchMock.get('*', { foo: 'bar' }); - - interceptorCalls = mockInterceptorCalls([ - { response: () => Promise.reject(new Error('Error in response')) }, - {}, - {}, - ]); - - try { - await kfetch({ pathname: '/my/path' }); - } catch (e) { - error = e; - } - }); - - it('should call in correct order', () => { - expect(interceptorCalls).toEqual([ - 'Request #3', - 'Request #2', - 'Request #1', - 'Response #1', - 'ResponseError #2', - 'ResponseError #3', - ]); - }); - - it('should make request', () => { - expect(fetchMock.called()).toBe(true); - }); - - it('should throw error', () => { - expect(error.message).toEqual('Error in response'); - }); - }); - - describe('when request interceptor throws; and a responseError interceptor resolves', () => { - let resp: any; - let interceptorCalls: string[]; - - beforeEach(async () => { - fetchMock.get('*', { foo: 'bar' }); - - interceptorCalls = mockInterceptorCalls([ - {}, - { - request: () => { - throw new Error('My request error'); - }, - responseError: () => { - return { custom: 'response' }; - }, - }, - {}, - ]); - - resp = await kfetch({ pathname: '/my/path' }); - }); - - it('should call in correct order', () => { - expect(interceptorCalls).toEqual([ - 'Request #3', - 'Request #2', - 'RequestError #1', - 'ResponseError #1', - 'ResponseError #2', - 'Response #3', - ]); - }); - - it('should not make request', () => { - expect(fetchMock.called()).toBe(false); - }); - - it('should resolve', () => { - expect(resp).toEqual({ custom: 'response' }); - }); - }); - - describe('when interceptors return synchronously', () => { - let resp: any; - beforeEach(async () => { - fetchMock.get('*', { foo: 'bar' }); - addInterceptor({ - request: (config) => ({ - ...config, - pathname: '/my/intercepted-route', - }), - response: (res) => ({ - ...res, - addedByResponseInterceptor: true, - }), - }); - - resp = await kfetch({ pathname: '/my/path' }); - }); - - it('should modify request', () => { - expect(fetchMock.lastUrl()).toContain('/my/intercepted-route'); - expect(fetchMock.lastOptions()!).toMatchObject({ - method: 'GET', - }); - }); - - it('should modify response', () => { - expect(resp).toEqual({ - addedByResponseInterceptor: true, - foo: 'bar', - }); - }); - }); - - describe('when interceptors return promise', () => { - let resp: any; - beforeEach(async () => { - fetchMock.get('*', { foo: 'bar' }); - addInterceptor({ - request: (config) => - Promise.resolve({ - ...config, - pathname: '/my/intercepted-route', - }), - response: (res) => - Promise.resolve({ - ...res, - addedByResponseInterceptor: true, - }), - }); - - resp = await kfetch({ pathname: '/my/path' }); - }); - - it('should modify request', () => { - expect(fetchMock.lastUrl()).toContain('/my/intercepted-route'); - expect(fetchMock.lastOptions()!).toMatchObject({ - method: 'GET', - }); - }); - - it('should modify response', () => { - expect(resp).toEqual({ - addedByResponseInterceptor: true, - foo: 'bar', - }); - }); - }); -}); - -function mockInterceptorCalls(interceptors: Interceptor[]) { - const interceptorCalls: string[] = []; - interceptors.forEach((interceptor, i) => { - addInterceptor({ - request: (config) => { - interceptorCalls.push(`Request #${i + 1}`); - - if (interceptor.request) { - return interceptor.request(config); - } - - return config; - }, - requestError: (e) => { - interceptorCalls.push(`RequestError #${i + 1}`); - if (interceptor.requestError) { - return interceptor.requestError(e); - } - - throw e; - }, - response: (res) => { - interceptorCalls.push(`Response #${i + 1}`); - - if (interceptor.response) { - return interceptor.response(res); - } - - return res; - }, - responseError: (e) => { - interceptorCalls.push(`ResponseError #${i + 1}`); - - if (interceptor.responseError) { - return interceptor.responseError(e); - } - - throw e; - }, - }); - }); - - return interceptorCalls; -} - -describe('withDefaultOptions', () => { - it('should remove undefined query params', () => { - const { query } = withDefaultOptions({ - pathname: '/withDefaultOptions', - query: { - foo: 'bar', - param1: (undefined as any) as string, - param2: (null as any) as string, - param3: '', - }, - }); - expect(query).toEqual({ foo: 'bar', param2: null, param3: '' }); - }); - - it('should add default options', () => { - expect(withDefaultOptions({ pathname: '/addDefaultOptions' })).toEqual({ - pathname: '/addDefaultOptions', - credentials: 'same-origin', - headers: { 'Content-Type': 'application/json' }, - method: 'GET', - }); - }); -}); diff --git a/src/legacy/ui/public/kfetch/kfetch.ts b/src/legacy/ui/public/kfetch/kfetch.ts deleted file mode 100644 index 4eb7149931575..0000000000000 --- a/src/legacy/ui/public/kfetch/kfetch.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { merge } from 'lodash'; -// @ts-ignore not really worth typing -import { KFetchError } from './kfetch_error'; - -import { HttpSetup } from '../../../../core/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { HttpRequestInit } from '../../../../core/public/http/types'; - -export interface KFetchQuery { - [key: string]: string | number | boolean | undefined; -} - -export interface KFetchOptions extends HttpRequestInit { - pathname: string; - query?: KFetchQuery; - asSystemRequest?: boolean; -} - -export interface KFetchKibanaOptions { - prependBasePath?: boolean; -} - -export interface Interceptor { - request?: (config: KFetchOptions) => Promise | KFetchOptions; - requestError?: (e: any) => Promise | KFetchOptions; - response?: (res: any) => any; - responseError?: (e: any) => any; -} - -const interceptors: Interceptor[] = []; -export const resetInterceptors = () => (interceptors.length = 0); -export const addInterceptor = (interceptor: Interceptor) => interceptors.push(interceptor); - -export function createKfetch(http: HttpSetup) { - return function kfetch( - options: KFetchOptions, - { prependBasePath = true }: KFetchKibanaOptions = {} - ) { - return responseInterceptors( - requestInterceptors(withDefaultOptions(options)) - .then(({ pathname, ...restOptions }) => - http.fetch(pathname, { ...restOptions, prependBasePath }) - ) - .catch((err) => { - throw new KFetchError(err.response || { statusText: err.message }, err.body); - }) - ); - }; -} - -// Request/response interceptors are called in opposite orders. -// Request hooks start from the newest interceptor and end with the oldest. -function requestInterceptors(config: KFetchOptions): Promise { - return interceptors.reduceRight((acc, interceptor) => { - return acc.then(interceptor.request, interceptor.requestError); - }, Promise.resolve(config)); -} - -// Response hooks start from the oldest interceptor and end with the newest. -function responseInterceptors(responsePromise: Promise) { - return interceptors.reduce((acc, interceptor) => { - return acc.then(interceptor.response, interceptor.responseError); - }, responsePromise); -} - -export function withDefaultOptions(options?: KFetchOptions): KFetchOptions { - const withDefaults = merge( - { - method: 'GET', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - }, - options - ) as KFetchOptions; - - if ( - options && - options.headers && - 'Content-Type' in options.headers && - options.headers['Content-Type'] === undefined - ) { - // TS thinks headers could be undefined here, but that isn't possible because - // of the merge above. - // @ts-ignore - withDefaults.headers['Content-Type'] = undefined; - } - - return withDefaults; -} diff --git a/src/legacy/ui/public/kfetch/kfetch_error.ts b/src/legacy/ui/public/kfetch/kfetch_error.ts deleted file mode 100644 index f351959e624b8..0000000000000 --- a/src/legacy/ui/public/kfetch/kfetch_error.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export class KFetchError extends Error { - constructor(public readonly res: Response, public readonly body?: any) { - super(res.statusText); - - // captureStackTrace is only available in the V8 engine, so any browser using - // a different JS engine won't have access to this method. - if (Error.captureStackTrace) { - Error.captureStackTrace(this, KFetchError); - } - } -} diff --git a/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js b/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js deleted file mode 100644 index efcfb77997265..0000000000000 --- a/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; - -import { $setupXsrfRequestInterceptor } from '../../../../../plugins/kibana_legacy/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { version } from '../../../../../core/server/utils/package_json'; - -const xsrfHeader = 'kbn-version'; - -describe('chrome xsrf apis', function () { - const sandbox = sinon.createSandbox(); - - afterEach(function () { - sandbox.restore(); - }); - - describe('jQuery support', function () { - it('adds a global jQuery prefilter', function () { - sandbox.stub($, 'ajaxPrefilter'); - $setupXsrfRequestInterceptor(version); - expect($.ajaxPrefilter.callCount).to.be(1); - }); - - describe('jQuery prefilter', function () { - let prefilter; - - beforeEach(function () { - sandbox.stub($, 'ajaxPrefilter'); - $setupXsrfRequestInterceptor(version); - prefilter = $.ajaxPrefilter.args[0][0]; - }); - - it(`sets the ${xsrfHeader} header`, function () { - const setHeader = sinon.stub(); - prefilter({}, {}, { setRequestHeader: setHeader }); - - expect(setHeader.callCount).to.be(1); - expect(setHeader.args[0]).to.eql([xsrfHeader, version]); - }); - - it('can be canceled by setting the kbnXsrfToken option', function () { - const setHeader = sinon.stub(); - prefilter({ kbnXsrfToken: false }, {}, { setRequestHeader: setHeader }); - expect(setHeader.callCount).to.be(0); - }); - }); - - describe('Angular support', function () { - let $http; - let $httpBackend; - - beforeEach(function () { - sandbox.stub($, 'ajaxPrefilter'); - ngMock.module($setupXsrfRequestInterceptor(version)); - }); - - beforeEach( - ngMock.inject(function ($injector) { - $http = $injector.get('$http'); - $httpBackend = $injector.get('$httpBackend'); - - $httpBackend.when('POST', '/api/test').respond('ok'); - }) - ); - - afterEach(function () { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - it(`injects a ${xsrfHeader} header on every request`, function () { - $httpBackend - .expectPOST('/api/test', undefined, function (headers) { - return headers[xsrfHeader] === version; - }) - .respond(200, ''); - - $http.post('/api/test'); - $httpBackend.flush(); - }); - - it('skips requests with the kbnXsrfToken set falsy', function () { - $httpBackend - .expectPOST('/api/test', undefined, function (headers) { - return !(xsrfHeader in headers); - }) - .respond(200, ''); - - $http({ - method: 'POST', - url: '/api/test', - kbnXsrfToken: 0, - }); - - $http({ - method: 'POST', - url: '/api/test', - kbnXsrfToken: '', - }); - - $http({ - method: 'POST', - url: '/api/test', - kbnXsrfToken: false, - }); - - $httpBackend.flush(); - }); - - it('treats the kbnXsrfToken option as boolean-y', function () { - const customToken = `custom:${version}`; - $httpBackend - .expectPOST('/api/test', undefined, function (headers) { - return headers[xsrfHeader] === version; - }) - .respond(200, ''); - - $http({ - method: 'POST', - url: '/api/test', - kbnXsrfToken: customToken, - }); - - $httpBackend.flush(); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/legacy_compat/index.ts b/src/legacy/ui/public/legacy_compat/index.ts deleted file mode 100644 index 2067fa6489304..0000000000000 --- a/src/legacy/ui/public/legacy_compat/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { configureAppAngularModule } from '../../../../plugins/kibana_legacy/public'; diff --git a/src/legacy/ui/public/metadata.ts b/src/legacy/ui/public/metadata.ts deleted file mode 100644 index fade0f0d8629a..0000000000000 --- a/src/legacy/ui/public/metadata.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { npSetup } from 'ui/new_platform'; - -export const metadata: { - branch: string; - version: string; -} = npSetup.core.injectedMetadata.getLegacyMetadata(); diff --git a/src/legacy/ui/public/modules.js b/src/legacy/ui/public/modules.js deleted file mode 100644 index bb1c8aead1c34..0000000000000 --- a/src/legacy/ui/public/modules.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular from 'angular'; -import _ from 'lodash'; -/** - * This module is used by Kibana to create and reuse angular modules. Angular modules - * can only be created once and need to have their dependencies at creation. This is - * hard/impossible to do in require.js since all of the dependencies for a module are - * loaded before it is. - * - * Here is an example: - * - * In the scenario below, require.js would load directive.js first because it is a - * dependency of app.js. This would cause the call to `angular.module('app')` to - * execute before the module is actually created. This causes angular to throw an - * error. This effect is magnified when app.js links off to many different modules. - * - * This is normally solved by creating unique modules per file, listed as the 1st - * alternate solution below. Unfortunately this solution would have required that - * we replicate our require statements. - * - * app.js - * ``` - * angular.module('app', ['ui.bootstrap']) - * .controller('AppController', function () { ... }); - * - * require('./directive'); - * ``` - * - * directive.js - * ``` - * angular.module('app') - * .directive('someDirective', function () { ... }); - * ``` - * - * Before taking this approach we saw three possible solutions: - * 1. replicate our js modules in angular modules/use a different module per file - * 2. create a single module outside of our js modules and share it - * 3. use a helper lib to dynamically create modules as needed. - * - * We decided to go with #3 - * - * This ends up working by creating a list of modules that the code base creates by - * calling `modules.get(name)` with different names, and then before bootstrapping - * the application kibana uses `modules.link()` to set the dependencies of the "kibana" - * module to include every defined module. This guarantees that kibana can always find - * any angular dependency defined in the kibana code base. This **also** means that - * Private modules are able to find any dependency, since they are injected using the - * "kibana" module's injector. - * - */ -const existingModules = {}; -const links = []; - -/** - * Take an angular module and extends the dependencies for that module to include all of the modules - * created using `ui/modules` - * - * @param {AngularModule} module - the module to extend - * @return {undefined} - */ -export function link(module) { - // as modules are defined they will be set as requirements for this app - links.push(module); - - // merge in the existing modules - module.requires = _.union(module.requires, _.keys(existingModules)); -} - -/** - * The primary means of interacting with `ui/modules`. Returns an angular module. If the module already - * exists the existing version will be returned. `dependencies` are either set as or merged into the - * modules total dependencies. - * - * This is in contrast to the `angular.module(name, [dependencies])` function which will only - * create a module if the `dependencies` list is passed and get an existing module if no dependencies - * are passed. This requires knowing the order that your files will load, which we can't guarantee. - * - * @param {string} moduleName - the unique name for this module - * @param {array[string]} [requires=[]] - the other modules this module requires - * @return {AngularModule} - */ -export function get(moduleName, requires) { - let module = existingModules[moduleName]; - - if (module === void 0) { - // create the module - module = existingModules[moduleName] = angular.module(moduleName, []); - - module.close = _.partial(close, moduleName); - - // ensure that it is required by linked modules - _.each(links, function (app) { - if (!~app.requires.indexOf(moduleName)) app.requires.push(moduleName); - }); - } - - if (requires) { - // update requires list with possibly new requirements - module.requires = _.union(module.requires, requires); - } - - return module; -} - -export function close(moduleName) { - const module = existingModules[moduleName]; - - // already closed - if (!module) return; - - // if the module is currently linked, unlink it - const i = links.indexOf(module); - if (i > -1) links.splice(i, 1); - - // remove from linked modules list of required modules - _.each(links, function (app) { - _.pull(app.requires, moduleName); - }); - - // remove module from existingModules - delete existingModules[moduleName]; -} - -export const uiModules = { link, get, close }; diff --git a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts deleted file mode 100644 index 35aa8e4830428..0000000000000 --- a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { coreMock } from '../../../../../core/public/mocks'; -import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; -import { embeddablePluginMock } from '../../../../../plugins/embeddable/public/mocks'; -import { navigationPluginMock } from '../../../../../plugins/navigation/public/mocks'; -import { expressionsPluginMock } from '../../../../../plugins/expressions/public/mocks'; -import { inspectorPluginMock } from '../../../../../plugins/inspector/public/mocks'; -import { uiActionsPluginMock } from '../../../../../plugins/ui_actions/public/mocks'; -import { managementPluginMock } from '../../../../../plugins/management/public/mocks'; -import { usageCollectionPluginMock } from '../../../../../plugins/usage_collection/public/mocks'; -import { kibanaLegacyPluginMock } from '../../../../../plugins/kibana_legacy/public/mocks'; -import { chartPluginMock } from '../../../../../plugins/charts/public/mocks'; -import { advancedSettingsMock } from '../../../../../plugins/advanced_settings/public/mocks'; -import { savedObjectsManagementPluginMock } from '../../../../../plugins/saved_objects_management/public/mocks'; -import { visualizationsPluginMock } from '../../../../../plugins/visualizations/public/mocks'; -import { discoverPluginMock } from '../../../../../plugins/discover/public/mocks'; - -export const pluginsMock = { - createSetup: () => ({ - data: dataPluginMock.createSetupContract(), - charts: chartPluginMock.createSetupContract(), - navigation: navigationPluginMock.createSetupContract(), - embeddable: embeddablePluginMock.createSetupContract(), - inspector: inspectorPluginMock.createSetupContract(), - expressions: expressionsPluginMock.createSetupContract(), - uiActions: uiActionsPluginMock.createSetupContract(), - usageCollection: usageCollectionPluginMock.createSetupContract(), - advancedSettings: advancedSettingsMock.createSetupContract(), - visualizations: visualizationsPluginMock.createSetupContract(), - kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), - savedObjectsManagement: savedObjectsManagementPluginMock.createSetupContract(), - discover: discoverPluginMock.createSetupContract(), - }), - createStart: () => ({ - data: dataPluginMock.createStartContract(), - charts: chartPluginMock.createStartContract(), - navigation: navigationPluginMock.createStartContract(), - embeddable: embeddablePluginMock.createStartContract(), - inspector: inspectorPluginMock.createStartContract(), - expressions: expressionsPluginMock.createStartContract(), - uiActions: uiActionsPluginMock.createStartContract(), - management: managementPluginMock.createStartContract(), - advancedSettings: advancedSettingsMock.createStartContract(), - visualizations: visualizationsPluginMock.createStartContract(), - kibanaLegacy: kibanaLegacyPluginMock.createStartContract(), - savedObjectsManagement: savedObjectsManagementPluginMock.createStartContract(), - discover: discoverPluginMock.createStartContract(), - }), -}; - -export const createUiNewPlatformMock = () => { - const mock = { - npSetup: { - core: coreMock.createSetup(), - plugins: pluginsMock.createSetup(), - }, - npStart: { - core: coreMock.createStart(), - plugins: pluginsMock.createStart(), - }, - }; - return mock; -}; diff --git a/src/legacy/ui/public/new_platform/__mocks__/index.ts b/src/legacy/ui/public/new_platform/__mocks__/index.ts deleted file mode 100644 index d469960ddd7b7..0000000000000 --- a/src/legacy/ui/public/new_platform/__mocks__/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createUiNewPlatformMock } from './helpers'; - -const { npSetup, npStart } = createUiNewPlatformMock(); -export { npSetup, npStart }; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js deleted file mode 100644 index b8d48b784dba7..0000000000000 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ /dev/null @@ -1,550 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import { getFieldFormatsRegistry } from '../../../../test_utils/public/stub_field_formats'; -import { METRIC_TYPE } from '@kbn/analytics'; -import { setSetupServices, setStartServices } from './set_services'; -import { - AggTypesRegistry, - getAggTypes, - AggConfigs, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../src/plugins/data/common/search/aggs'; -import { ComponentRegistry } from '../../../../../src/plugins/advanced_settings/public/'; -import { UI_SETTINGS } from '../../../../../src/plugins/data/public/'; -import { - CSV_SEPARATOR_SETTING, - CSV_QUOTE_VALUES_SETTING, -} from '../../../../../src/plugins/share/public'; - -const mockObservable = () => { - return { - subscribe: () => {}, - pipe: () => { - return { - subscribe: () => {}, - }; - }, - }; -}; - -const mockComponent = () => { - return null; -}; - -let refreshInterval = undefined; -let isTimeRangeSelectorEnabled = true; -let isAutoRefreshSelectorEnabled = true; - -export const mockUiSettings = { - get: (item, defaultValue) => { - const defaultValues = { - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - 'dateFormat:tz': 'UTC', - [UI_SETTINGS.SHORT_DOTS_ENABLE]: true, - [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: true, - [UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS]: true, - [UI_SETTINGS.QUERY_STRING_OPTIONS]: {}, - [UI_SETTINGS.FORMAT_CURRENCY_DEFAULT_PATTERN]: '($0,0.[00])', - [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_PATTERN]: '0,0.[000]', - [UI_SETTINGS.FORMAT_PERCENT_DEFAULT_PATTERN]: '0,0.[000]%', - [UI_SETTINGS.FORMAT_NUMBER_DEFAULT_LOCALE]: 'en', - [UI_SETTINGS.FORMAT_DEFAULT_TYPE_MAP]: {}, - [CSV_SEPARATOR_SETTING]: ',', - [CSV_QUOTE_VALUES_SETTING]: true, - [UI_SETTINGS.SEARCH_QUERY_LANGUAGE]: 'kuery', - 'state:storeInSessionStorage': false, - }; - - return defaultValues[item] || defaultValue; - }, - getUpdate$: () => ({ - subscribe: sinon.fake(), - }), - isDefault: sinon.fake(), -}; - -const mockCoreSetup = { - chrome: {}, - http: { - basePath: { - get: sinon.fake.returns(''), - }, - }, - injectedMetadata: {}, - uiSettings: mockUiSettings, -}; - -const mockCoreStart = { - application: { - capabilities: {}, - }, - chrome: { - overlays: { - openModal: sinon.fake(), - }, - }, - http: { - basePath: { - get: sinon.fake.returns(''), - }, - }, - notifications: { - toasts: {}, - }, - i18n: {}, - overlays: {}, - savedObjects: { - client: {}, - }, - uiSettings: mockUiSettings, -}; - -const querySetup = { - state$: mockObservable(), - filterManager: { - getFetches$: sinon.fake(), - getFilters: sinon.fake(), - getAppFilters: sinon.fake(), - getGlobalFilters: sinon.fake(), - removeFilter: sinon.fake(), - addFilters: sinon.fake(), - setFilters: sinon.fake(), - removeAll: sinon.fake(), - getUpdates$: mockObservable, - }, - timefilter: { - timefilter: { - getFetch$: mockObservable, - getAutoRefreshFetch$: mockObservable, - getEnabledUpdated$: mockObservable, - getTimeUpdate$: mockObservable, - getRefreshIntervalUpdate$: mockObservable, - isTimeRangeSelectorEnabled: () => { - return isTimeRangeSelectorEnabled; - }, - isAutoRefreshSelectorEnabled: () => { - return isAutoRefreshSelectorEnabled; - }, - disableAutoRefreshSelector: () => { - isAutoRefreshSelectorEnabled = false; - }, - enableAutoRefreshSelector: () => { - isAutoRefreshSelectorEnabled = true; - }, - getRefreshInterval: () => { - return refreshInterval; - }, - setRefreshInterval: (interval) => { - refreshInterval = interval; - }, - enableTimeRangeSelector: () => { - isTimeRangeSelectorEnabled = true; - }, - disableTimeRangeSelector: () => { - isTimeRangeSelectorEnabled = false; - }, - getTime: sinon.fake(), - setTime: sinon.fake(), - getActiveBounds: sinon.fake(), - getBounds: sinon.fake(), - calculateBounds: sinon.fake(), - createFilter: sinon.fake(), - }, - history: sinon.fake(), - }, - savedQueries: { - saveQuery: sinon.fake(), - getAllSavedQueries: sinon.fake(), - findSavedQueries: sinon.fake(), - getSavedQuery: sinon.fake(), - deleteSavedQuery: sinon.fake(), - getSavedQueryCount: sinon.fake(), - }, -}; - -const mockAggTypesRegistry = () => { - const registry = new AggTypesRegistry(); - const registrySetup = registry.setup(); - const aggTypes = getAggTypes({ - calculateBounds: sinon.fake(), - getConfig: sinon.fake(), - getFieldFormatsStart: () => ({ - deserialize: sinon.fake(), - getDefaultInstance: sinon.fake(), - }), - isDefaultTimezone: () => true, - }); - aggTypes.buckets.forEach((type) => registrySetup.registerBucket(type)); - aggTypes.metrics.forEach((type) => registrySetup.registerMetric(type)); - - return registry; -}; - -const aggTypesRegistry = mockAggTypesRegistry(); - -export const npSetup = { - core: mockCoreSetup, - plugins: { - advancedSettings: { - component: { - register: sinon.fake(), - componentType: ComponentRegistry.componentType, - }, - }, - usageCollection: { - allowTrackUserAgent: sinon.fake(), - reportUiStats: sinon.fake(), - METRIC_TYPE, - }, - embeddable: { - registerEmbeddableFactory: sinon.fake(), - }, - expressions: { - registerFunction: sinon.fake(), - registerRenderer: sinon.fake(), - registerType: sinon.fake(), - }, - data: { - autocomplete: { - addProvider: sinon.fake(), - getProvider: sinon.fake(), - }, - query: querySetup, - search: { - aggs: { - types: aggTypesRegistry.setup(), - }, - __LEGACY: { - esClient: { - search: sinon.fake(), - msearch: sinon.fake(), - }, - }, - }, - fieldFormats: getFieldFormatsRegistry(mockCoreSetup), - }, - share: { - register: () => {}, - urlGenerators: { - registerUrlGenerator: () => {}, - }, - }, - devTools: { - register: () => {}, - }, - kibanaLegacy: { - registerLegacyApp: () => {}, - forwardApp: () => {}, - config: { - defaultAppId: 'home', - }, - }, - inspector: { - registerView: () => undefined, - __LEGACY: { - views: { - register: () => undefined, - }, - }, - }, - uiActions: { - attachAction: sinon.fake(), - registerAction: sinon.fake(), - registerTrigger: sinon.fake(), - }, - home: { - featureCatalogue: { - register: sinon.fake(), - }, - environment: { - update: sinon.fake(), - }, - config: { - disableWelcomeScreen: false, - }, - tutorials: { - setVariable: sinon.fake(), - }, - }, - charts: { - theme: { - chartsTheme$: mockObservable, - useChartsTheme: sinon.fake(), - }, - colors: { - seedColors: ['white', 'black'], - }, - }, - management: { - sections: { - getSection: () => ({ - registerApp: sinon.fake(), - }), - }, - }, - indexPatternManagement: { - list: { addListConfig: sinon.fake() }, - creation: { addCreationConfig: sinon.fake() }, - }, - discover: { - docViews: { - addDocView: sinon.fake(), - setAngularInjectorGetter: sinon.fake(), - }, - }, - visTypeVega: { - config: sinon.fake(), - }, - visualizations: { - createBaseVisualization: sinon.fake(), - createReactVisualization: sinon.fake(), - registerAlias: sinon.fake(), - hideTypes: sinon.fake(), - }, - - mapsLegacy: { - serviceSettings: sinon.fake(), - getPrecision: sinon.fake(), - getZoomPrecision: sinon.fake(), - }, - }, -}; - -export const npStart = { - core: mockCoreStart, - plugins: { - management: { - legacy: { - getSection: () => ({ - register: sinon.fake(), - deregister: sinon.fake(), - hasItem: sinon.fake(), - }), - }, - sections: { - getSection: () => ({ - registerApp: sinon.fake(), - }), - }, - }, - indexPatternManagement: { - list: { - getType: sinon.fake(), - getIndexPatternCreationOptions: sinon.fake(), - }, - creation: { - getIndexPatternTags: sinon.fake(), - getFieldInfo: sinon.fake(), - areScriptedFieldsEnabled: sinon.fake(), - }, - }, - embeddable: { - getEmbeddableFactory: sinon.fake(), - getEmbeddableFactories: sinon.fake(), - registerEmbeddableFactory: sinon.fake(), - }, - expressions: { - registerFunction: sinon.fake(), - registerRenderer: sinon.fake(), - registerType: sinon.fake(), - }, - kibanaLegacy: { - getForwards: () => [], - loadFontAwesome: () => {}, - config: { - defaultAppId: 'home', - }, - dashboardConfig: { - turnHideWriteControlsOn: sinon.fake(), - getHideWriteControls: sinon.fake(), - }, - }, - dashboard: { - getSavedDashboardLoader: sinon.fake(), - }, - data: { - actions: { - createFiltersFromValueClickAction: Promise.resolve(['yes']), - createFiltersFromRangeSelectAction: sinon.fake(), - }, - autocomplete: { - getProvider: sinon.fake(), - }, - getSuggestions: sinon.fake(), - indexPatterns: { - get: sinon.spy((indexPatternId) => - Promise.resolve({ - id: indexPatternId, - isTimeNanosBased: () => false, - popularizeField: () => {}, - }) - ), - }, - ui: { - IndexPatternSelect: mockComponent, - SearchBar: mockComponent, - }, - query: { - filterManager: { - getFetches$: sinon.fake(), - getFilters: sinon.fake(), - getAppFilters: sinon.fake(), - getGlobalFilters: sinon.fake(), - removeFilter: sinon.fake(), - addFilters: sinon.fake(), - setFilters: sinon.fake(), - removeAll: sinon.fake(), - getUpdates$: mockObservable, - }, - timefilter: { - timefilter: { - getFetch$: mockObservable, - getAutoRefreshFetch$: mockObservable, - getEnabledUpdated$: mockObservable, - getTimeUpdate$: mockObservable, - getRefreshIntervalUpdate$: mockObservable, - isTimeRangeSelectorEnabled: () => { - return isTimeRangeSelectorEnabled; - }, - isAutoRefreshSelectorEnabled: () => { - return isAutoRefreshSelectorEnabled; - }, - disableAutoRefreshSelector: () => { - isAutoRefreshSelectorEnabled = false; - }, - enableAutoRefreshSelector: () => { - isAutoRefreshSelectorEnabled = true; - }, - getRefreshInterval: () => { - return refreshInterval; - }, - setRefreshInterval: (interval) => { - refreshInterval = interval; - }, - enableTimeRangeSelector: () => { - isTimeRangeSelectorEnabled = true; - }, - disableTimeRangeSelector: () => { - isTimeRangeSelectorEnabled = false; - }, - getTime: sinon.fake(), - setTime: sinon.fake(), - getActiveBounds: sinon.fake(), - getBounds: sinon.fake(), - calculateBounds: sinon.fake(), - createFilter: sinon.fake(), - }, - history: sinon.fake(), - }, - }, - search: { - aggs: { - calculateAutoTimeExpression: sinon.fake(), - createAggConfigs: (indexPattern, configStates = []) => { - return new AggConfigs(indexPattern, configStates, { - typesRegistry: aggTypesRegistry.start(), - fieldFormats: getFieldFormatsRegistry(mockCoreStart), - }); - }, - types: aggTypesRegistry.start(), - }, - __LEGACY: { - esClient: { - search: sinon.fake(), - msearch: sinon.fake(), - }, - }, - }, - fieldFormats: getFieldFormatsRegistry(mockCoreStart), - }, - share: { - toggleShareContextMenu: () => {}, - }, - inspector: { - isAvailable: () => false, - open: () => ({ - onClose: Promise.resolve(undefined), - close: () => Promise.resolve(undefined), - }), - }, - uiActions: { - attachAction: sinon.fake(), - registerAction: sinon.fake(), - registerTrigger: sinon.fake(), - detachAction: sinon.fake(), - executeTriggerActions: sinon.fake(), - getTrigger: sinon.fake(), - getTriggerActions: sinon.fake(), - getTriggerCompatibleActions: sinon.fake(), - }, - visualizations: { - get: sinon.fake(), - all: sinon.fake(), - getAliases: sinon.fake(), - savedVisualizationsLoader: {}, - showNewVisModal: sinon.fake(), - createVis: sinon.fake(), - convertFromSerializedVis: sinon.fake(), - convertToSerializedVis: sinon.fake(), - }, - navigation: { - ui: { - TopNavMenu: mockComponent, - }, - }, - charts: { - theme: { - chartsTheme$: mockObservable, - useChartsTheme: sinon.fake(), - }, - }, - discover: { - docViews: { - DocViewer: () => null, - }, - savedSearchLoader: {}, - }, - }, -}; - -export function __setup__(coreSetup) { - npSetup.core = coreSetup; - - // no-op application register calls (this is overwritten to - // bootstrap an LP plugin outside of tests) - npSetup.core.application.register = () => {}; - - npSetup.core.uiSettings.get = mockUiSettings.get; - - // Services that need to be set in the legacy platform since the legacy data - // & vis plugins which previously provided them have been removed. - setSetupServices(npSetup); -} - -export function __start__(coreStart) { - npStart.core = coreStart; - - npStart.core.uiSettings.get = mockUiSettings.get; - - // Services that need to be set in the legacy platform since the legacy data - // & vis plugins which previously provided them have been removed. - setStartServices(npStart); -} diff --git a/src/legacy/ui/public/new_platform/new_platform.test.mocks.ts b/src/legacy/ui/public/new_platform/new_platform.test.mocks.ts deleted file mode 100644 index f44efe17ef8ee..0000000000000 --- a/src/legacy/ui/public/new_platform/new_platform.test.mocks.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { scopedHistoryMock } from '../../../../core/public/mocks'; - -export const setRootControllerMock = jest.fn(); - -jest.doMock('ui/chrome', () => ({ - setRootController: setRootControllerMock, -})); - -export const historyMock = scopedHistoryMock.create(); -jest.doMock('../../../../core/public', () => ({ - ScopedHistory: jest.fn(() => historyMock), -})); diff --git a/src/legacy/ui/public/new_platform/new_platform.test.ts b/src/legacy/ui/public/new_platform/new_platform.test.ts deleted file mode 100644 index d515c348ca440..0000000000000 --- a/src/legacy/ui/public/new_platform/new_platform.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -jest.mock('history'); - -import { setRootControllerMock, historyMock } from './new_platform.test.mocks'; -import { legacyAppRegister, __reset__, __setup__, __start__ } from './new_platform'; -import { coreMock } from '../../../../core/public/mocks'; -import { AppMount } from '../../../../core/public'; - -describe('ui/new_platform', () => { - describe('legacyAppRegister', () => { - beforeEach(() => { - setRootControllerMock.mockReset(); - __reset__(); - __setup__(coreMock.createSetup({ basePath: '/test/base/path' }) as any, {} as any); - }); - - const registerApp = () => { - const unmountMock = jest.fn(); - const mountMock = jest.fn, Parameters>(() => unmountMock); - legacyAppRegister({ - id: 'test', - title: 'Test', - mount: mountMock, - }); - return { mountMock, unmountMock }; - }; - - test('sets ui/chrome root controller', () => { - registerApp(); - expect(setRootControllerMock).toHaveBeenCalledWith('test', expect.any(Function)); - }); - - test('throws if called more than once', () => { - registerApp(); - expect(registerApp).toThrowErrorMatchingInlineSnapshot( - `"core.application.register may only be called once for legacy plugins."` - ); - }); - - test('controller calls app.mount when invoked', () => { - const { mountMock } = registerApp(); - const controller = setRootControllerMock.mock.calls[0][1]; - const scopeMock = { $on: jest.fn() }; - const elementMock = [document.createElement('div')]; - - controller(scopeMock, elementMock); - expect(mountMock).toHaveBeenCalledWith({ - element: expect.any(HTMLElement), - appBasePath: '/test/base/path/app/test', - onAppLeave: expect.any(Function), - history: historyMock, - }); - }); - - test('app is mounted in new div inside containing element', () => { - const { mountMock } = registerApp(); - const controller = setRootControllerMock.mock.calls[0][1]; - const scopeMock = { $on: jest.fn() }; - const elementMock = [document.createElement('div')]; - - controller(scopeMock, elementMock); - - const { element } = mountMock.mock.calls[0][0]; - expect(element.parentElement).toEqual(elementMock[0]); - }); - - test('controller calls deprecated context app.mount when invoked', () => { - const unmountMock = jest.fn(); - // Two arguments changes how this is called. - const mountMock = jest.fn((context, params) => unmountMock); - legacyAppRegister({ - id: 'test', - title: 'Test', - mount: mountMock, - }); - const controller = setRootControllerMock.mock.calls[0][1]; - const scopeMock = { $on: jest.fn() }; - const elementMock = [document.createElement('div')]; - - controller(scopeMock, elementMock); - expect(mountMock).toHaveBeenCalledWith(expect.any(Object), { - element: expect.any(HTMLElement), - appBasePath: '/test/base/path/app/test', - onAppLeave: expect.any(Function), - history: historyMock, - }); - }); - - test('controller calls unmount when $scope.$destroy', async () => { - const { unmountMock } = registerApp(); - const controller = setRootControllerMock.mock.calls[0][1]; - const scopeMock = { $on: jest.fn() }; - const elementMock = [document.createElement('div')]; - - controller(scopeMock, elementMock); - // Flush promise queue. Must be done this way because the controller cannot return a Promise without breaking - // angular. - await new Promise((resolve) => setTimeout(resolve, 1)); - - const [event, eventHandler] = scopeMock.$on.mock.calls[0]; - expect(event).toEqual('$destroy'); - eventHandler(); - expect(unmountMock).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts deleted file mode 100644 index 37787ffbde4fc..0000000000000 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IScope } from 'angular'; - -import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; -import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; -import { createBrowserHistory } from 'history'; -import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public'; -import { DashboardStart } from '../../../../plugins/dashboard/public'; -import { setSetupServices, setStartServices } from './set_services'; -import { - LegacyCoreSetup, - LegacyCoreStart, - App, - AppMountDeprecated, - ScopedHistory, -} from '../../../../core/public'; -import { Plugin as DataPlugin } from '../../../../plugins/data/public'; -import { Plugin as ExpressionsPlugin } from '../../../../plugins/expressions/public'; -import { - Setup as InspectorSetup, - Start as InspectorStart, -} from '../../../../plugins/inspector/public'; -import { ChartsPluginSetup, ChartsPluginStart } from '../../../../plugins/charts/public'; -import { DevToolsSetup } from '../../../../plugins/dev_tools/public'; -import { KibanaLegacySetup, KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public'; -import { HomePublicPluginSetup } from '../../../../plugins/home/public'; -import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public'; -import { - AdvancedSettingsSetup, - AdvancedSettingsStart, -} from '../../../../plugins/advanced_settings/public'; -import { ManagementSetup, ManagementStart } from '../../../../plugins/management/public'; -import { - IndexPatternManagementSetup, - IndexPatternManagementStart, -} from '../../../../plugins/index_pattern_management/public'; -import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public'; -import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; -import { TelemetryPluginSetup, TelemetryPluginStart } from '../../../../plugins/telemetry/public'; -import { - NavigationPublicPluginSetup, - NavigationPublicPluginStart, -} from '../../../../plugins/navigation/public'; -import { DiscoverSetup, DiscoverStart } from '../../../../plugins/discover/public'; -import { - SavedObjectsManagementPluginSetup, - SavedObjectsManagementPluginStart, -} from '../../../../plugins/saved_objects_management/public'; -import { - VisualizationsSetup, - VisualizationsStart, -} from '../../../../plugins/visualizations/public'; -import { VisTypeTimelionPluginStart } from '../../../../plugins/vis_type_timelion/public'; -import { MapsLegacyPluginSetup } from '../../../../plugins/maps_legacy/public'; - -export interface PluginsSetup { - bfetch: BfetchPublicSetup; - charts: ChartsPluginSetup; - data: ReturnType; - embeddable: EmbeddableSetup; - expressions: ReturnType; - home: HomePublicPluginSetup; - inspector: InspectorSetup; - uiActions: UiActionsSetup; - navigation: NavigationPublicPluginSetup; - devTools: DevToolsSetup; - kibanaLegacy: KibanaLegacySetup; - share: SharePluginSetup; - usageCollection: UsageCollectionSetup; - advancedSettings: AdvancedSettingsSetup; - management: ManagementSetup; - discover: DiscoverSetup; - visualizations: VisualizationsSetup; - telemetry?: TelemetryPluginSetup; - savedObjectsManagement: SavedObjectsManagementPluginSetup; - mapsLegacy: MapsLegacyPluginSetup; - indexPatternManagement: IndexPatternManagementSetup; - visTypeXy?: VisTypeXyPluginSetup; -} - -export interface PluginsStart { - bfetch: BfetchPublicStart; - charts: ChartsPluginStart; - data: ReturnType; - embeddable: EmbeddableStart; - expressions: ReturnType; - inspector: InspectorStart; - uiActions: UiActionsStart; - navigation: NavigationPublicPluginStart; - kibanaLegacy: KibanaLegacyStart; - share: SharePluginStart; - management: ManagementStart; - advancedSettings: AdvancedSettingsStart; - discover: DiscoverStart; - visualizations: VisualizationsStart; - telemetry?: TelemetryPluginStart; - dashboard: DashboardStart; - savedObjectsManagement: SavedObjectsManagementPluginStart; - visTypeTimelion: VisTypeTimelionPluginStart; - indexPatternManagement: IndexPatternManagementStart; -} - -export const npSetup = { - core: (null as unknown) as LegacyCoreSetup, - plugins: {} as PluginsSetup, -}; - -export const npStart = { - core: (null as unknown) as LegacyCoreStart, - plugins: {} as PluginsStart, -}; - -/** - * Only used by unit tests - * @internal - */ -export function __reset__() { - npSetup.core = (null as unknown) as LegacyCoreSetup; - npSetup.plugins = {} as any; - npStart.core = (null as unknown) as LegacyCoreStart; - npStart.plugins = {} as any; - legacyAppRegistered = false; -} - -export function __setup__(coreSetup: LegacyCoreSetup, plugins: PluginsSetup) { - npSetup.core = coreSetup; - npSetup.plugins = plugins; - - // Setup compatibility layer for AppService in legacy platform - npSetup.core.application.register = legacyAppRegister; - - // Services that need to be set in the legacy platform since the legacy data - // & vis plugins which previously provided them have been removed. - setSetupServices(npSetup); -} - -export function __start__(coreStart: LegacyCoreStart, plugins: PluginsStart) { - npStart.core = coreStart; - npStart.plugins = plugins; - - // Services that need to be set in the legacy platform since the legacy data - // & vis plugins which previously provided them have been removed. - setStartServices(npStart); -} - -/** Flag used to ensure `legacyAppRegister` is only called once. */ -let legacyAppRegistered = false; - -/** - * Exported for testing only. Use `npSetup.core.application.register` in legacy apps. - * @internal - */ -export const legacyAppRegister = (app: App) => { - if (legacyAppRegistered) { - throw new Error(`core.application.register may only be called once for legacy plugins.`); - } - legacyAppRegistered = true; - - // eslint-disable-next-line @typescript-eslint/no-var-requires - require('ui/chrome').setRootController(app.id, ($scope: IScope, $element: JQLite) => { - const element = document.createElement('div'); - $element[0].appendChild(element); - - // Root controller cannot return a Promise so use an internal async function and call it immediately - (async () => { - const appRoute = app.appRoute || `/app/${app.id}`; - const appBasePath = npSetup.core.http.basePath.prepend(appRoute); - const params = { - element, - appBasePath, - history: new ScopedHistory( - createBrowserHistory({ basename: npSetup.core.http.basePath.get() }), - appRoute - ), - onAppLeave: () => undefined, - }; - const unmount = isAppMountDeprecated(app.mount) - ? await app.mount({ core: npStart.core }, params) - : await app.mount(params); - $scope.$on('$destroy', () => { - unmount(); - }); - })(); - }); -}; - -function isAppMountDeprecated(mount: (...args: any[]) => any): mount is AppMountDeprecated { - // Mount functions with two arguments are assumed to expect deprecated `context` object. - return mount.length === 2; -} diff --git a/src/legacy/ui/public/new_platform/set_services.test.ts b/src/legacy/ui/public/new_platform/set_services.test.ts deleted file mode 100644 index 74e789ec220b1..0000000000000 --- a/src/legacy/ui/public/new_platform/set_services.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { __reset__, __setup__, __start__, PluginsSetup, PluginsStart } from './new_platform'; -import * as dataServices from '../../../../plugins/data/public/services'; -import * as visualizationsServices from '../../../../plugins/visualizations/public/services'; -import { LegacyCoreSetup, LegacyCoreStart } from '../../../../core/public'; -import { coreMock } from '../../../../core/public/mocks'; -import { npSetup, npStart } from './__mocks__'; - -describe('ui/new_platform', () => { - describe('set service getters', () => { - const testServiceGetters = (name: string, services: Record) => { - const getters = Object.keys(services).filter((k) => k.substring(0, 3) === 'get'); - getters.forEach((g) => { - it(`ui/new_platform sets a value for ${name} getter ${g}`, () => { - __reset__(); - __setup__( - (coreMock.createSetup() as unknown) as LegacyCoreSetup, - (npSetup.plugins as unknown) as PluginsSetup - ); - __start__( - (coreMock.createStart() as unknown) as LegacyCoreStart, - (npStart.plugins as unknown) as PluginsStart - ); - - expect(services[g]()).toBeDefined(); - }); - }); - }; - - testServiceGetters('data', dataServices); - testServiceGetters('visualizations', visualizationsServices); - }); -}); diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts deleted file mode 100644 index 036157a9f3fbc..0000000000000 --- a/src/legacy/ui/public/new_platform/set_services.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { pick } from 'lodash'; - -import { PluginsSetup, PluginsStart } from './new_platform'; -import { LegacyCoreSetup, LegacyCoreStart } from '../../../../core/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import * as dataServices from '../../../../plugins/data/public/services'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import * as visualizationsServices from '../../../../plugins/visualizations/public/services'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { createSavedVisLoader } from '../../../../plugins/visualizations/public/saved_visualizations/saved_visualizations'; - -interface NpSetup { - core: LegacyCoreSetup; - plugins: PluginsSetup; -} - -interface NpStart { - core: LegacyCoreStart; - plugins: PluginsStart; -} - -export function setSetupServices(npSetup: NpSetup) { - // Services that need to be set in the legacy platform since the legacy data plugin - // which previously provided them has been removed. - visualizationsServices.setUISettings(npSetup.core.uiSettings); - visualizationsServices.setUsageCollector(npSetup.plugins.usageCollection); -} - -export function setStartServices(npStart: NpStart) { - // Services that need to be set in the legacy platform since the legacy data plugin - // which previously provided them has been removed. - dataServices.setNotifications(npStart.core.notifications); - dataServices.setOverlays(npStart.core.overlays); - dataServices.setUiSettings(npStart.core.uiSettings); - dataServices.setFieldFormats(npStart.plugins.data.fieldFormats); - dataServices.setIndexPatterns(npStart.plugins.data.indexPatterns); - dataServices.setQueryService(npStart.plugins.data.query); - dataServices.setSearchService(npStart.plugins.data.search); - - visualizationsServices.setI18n(npStart.core.i18n); - visualizationsServices.setTypes( - pick(npStart.plugins.visualizations, ['get', 'all', 'getAliases']) - ); - visualizationsServices.setCapabilities(npStart.core.application.capabilities); - visualizationsServices.setHttp(npStart.core.http); - visualizationsServices.setApplication(npStart.core.application); - visualizationsServices.setEmbeddable(npStart.plugins.embeddable); - visualizationsServices.setSavedObjects(npStart.core.savedObjects); - visualizationsServices.setIndexPatterns(npStart.plugins.data.indexPatterns); - visualizationsServices.setFilterManager(npStart.plugins.data.query.filterManager); - visualizationsServices.setExpressions(npStart.plugins.expressions); - visualizationsServices.setUiActions(npStart.plugins.uiActions); - visualizationsServices.setTimeFilter(npStart.plugins.data.query.timefilter.timefilter); - visualizationsServices.setAggs(npStart.plugins.data.search.aggs); - visualizationsServices.setOverlays(npStart.core.overlays); - visualizationsServices.setChrome(npStart.core.chrome); - visualizationsServices.setSearch(npStart.plugins.data.search); - const savedVisualizationsLoader = createSavedVisLoader({ - savedObjectsClient: npStart.core.savedObjects.client, - indexPatterns: npStart.plugins.data.indexPatterns, - search: npStart.plugins.data.search, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, - visualizationTypes: visualizationsServices.getTypes(), - }); - visualizationsServices.setSavedVisualizationsLoader(savedVisualizationsLoader); - visualizationsServices.setSavedSearchLoader(npStart.plugins.discover.savedSearchLoader); -} diff --git a/src/legacy/ui/public/notify/banners/BANNERS.md b/src/legacy/ui/public/notify/banners/BANNERS.md deleted file mode 100644 index fc6bddc3aa4ed..0000000000000 --- a/src/legacy/ui/public/notify/banners/BANNERS.md +++ /dev/null @@ -1,360 +0,0 @@ -# Banners - -Use this service to surface banners at the top of the screen. The expectation is that the banner will used an -`` to render, but that is not a requirement. See [the EUI docs](https://elastic.github.io/eui/) for -more information on banners and their role within the UI. - -Banners should be considered with respect to their lifecycle. Most banners are best served by using the `add` and -`remove` functions. - -## Importing the module - -```js -import { banners } from 'ui/notify'; -``` - -## Interface - -There are three methods defined to manipulate the list of banners: `add`, `set`, and `remove`. A fourth method, -`onChange` exists to listen to changes made via `add`, `set`, and `remove`. - -### `add()` - -This is the preferred way to add banners because it implies the best usage of the banner: added once during a page's -lifecycle. For other usages, consider *not* using a banner. - -#### Syntax - -```js -const bannerId = banners.add({ - // required: - component, - // optional: - priority, -}); -``` - -##### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `component` | Any | The value displayed as the banner. | -| `priority` | Number | Optional priority, which defaults to `0` used to place the banner. | - -To add a banner, you only need to define the `component` field. - -The `priority` sorts in descending order. Items sharing the same priority are sorted from oldest to newest. For example: - -```js -const banner1 = banners.add({ component: }); -const banner2 = banners.add({ component: , priority: 0 }); -const banner3 = banners.add({ component: , priority: 1 }); -``` - -That would be displayed as: - -``` -[ fake3 ] -[ fake1 ] -[ fake2 ] -``` - -##### Returns - -| Type | Description | -|------|-------------| -| String | A newly generated ID. | - -#### Example - -This example includes buttons that allow the user to remove the banner. In some cases, you may not want any buttons -and in other cases you will want an action to proceed the banner's removal (e.g., apply an Advanced Setting). - -This makes the most sense to use when a banner is added at the beginning of the page life cycle and not expected to -be touched, except by its own buttons triggering an action or navigating away. - -```js -const bannerId = banners.add({ - component: ( - - - - banners.remove(bannerId)} - > - Dismiss - - - - window.alert('Do Something Else')} - > - Do Something Else - - - - - ), -}); -``` - -### `remove()` - -Unlike toast notifications, banners stick around until they are explicitly removed. Using the `add` example above,you can remove it by calling `remove`. - -Note: They will stick around as long as the scope is remembered by whatever set it; navigating away won't remove it -unless the scope is forgotten (e.g., when the "app" changes)! - -#### Syntax - -```js -const removed = banners.remove(bannerId); -``` - -##### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `id` | String | ID of a banner. | - -##### Returns - -| Type | Description | -|------|-------------| -| Boolean | `true` if the ID was recognized and the banner was removed. `false` otherwise. | - -#### Example - -To remove a banner, you need to pass the `id` of the banner. - -```js -if (banners.remove(bannerId)) { - // removed; otherwise it didn't exist (maybe it was already removed) -} -``` - -#### Scheduled removal - -Like toast notifications do automatically, you can have a banner automatically removed after a set of time, by -setting a timer: - -```js -setTimeout(() => banners.remove(bannerId), 15000); -``` - -Note: It is safe to remove a banner more than once as unknown IDs will be ignored. - -### `set()` - -Banners can be replaced once added by supplying their `id`. If one is supplied, then the ID will be used to replace -any banner with the same ID and a **new** `id` will be returned. - -You should only consider using `set` when the banner is manipulated frequently in the lifecycle of the page, where -maintaining the banner's `id` can be a burden. It is easier to allow `banners` to create the ID for you in most -situations where a banner is useful (e.g., set once), which safely avoids any chance to have an ID-based collision, -which happens automatically with `add`. - -Usage of `set` can imply that your use case is abusing the banner system. - -Note: `set` will only trigger the callback once for both the implicit remove and add operation. - -#### Syntax - -```js -const id = banners.set({ - // required: - component, - // optional: - id, - priority, -}); -``` - -##### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `component` | Any | The value displayed as the banner. | -| `id` | String | Optional ID used to remove an existing banner. | -| `priority` | Number | Optional priority, which defaults to `0` used to place the banner. | - -The `id` is optional because it follows the same semantics as the `remove` method: unknown IDs are ignored. This -is useful when first creating a banner so that you do not have to call `add` instead. - -##### Returns - -| Type | Description | -|------|-------------| -| String | A newly generated ID. | - -#### Example - -This example does not include any way for the user to clear the banner directly. Instead, it is cleared based on -time. Related to it being cleared by time, it can also reappear within the same page life cycle by navigating between -different paths that need it displayed. Instead of adding a new banner for every navigation, you should replace any -existing banner. - -```js -let bannerId; -let timeoutId; - -function displayBanner() { - clearTimeout(timeoutId); - - bannerId = banners.set({ - id: bannerId, // the first time it will be undefined, but reused as long as this is in the same lifecycle - component: ( - - ) - }); - - // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around - banner.timeoutId = setTimeout(() => { - banners.remove(bannerId); - timeoutId = undefined; - }, 6000); -} -``` - -### `onChange()` - -For React components that intend to display the banners, it is not enough to simply `render` the `banners.list` -values. Because they can change after being rendered, the React component that renders the list must be alerted -to changes to the list. - -#### Syntax - -```js -// inside your React component -banners.onChange(() => this.forceUpdate()); -``` - -##### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `callback` | Function | The function to invoke whenever the internal banner list is changed. | - -Every new `callback` replaces the previous callback. So calling this with `null` or `undefined` will unset the -callback. - -##### Returns - -Nothing. - -#### Example - -This can be used inside of a React component to trigger a re-`render` of the banners. - -```js -import { GlobalBannerList } from 'ui/notify'; - - -``` - -### `list` - -For React components that intend to display the banners, it is not enough to simply `render` the `banners.list` -values. Because they can change after being rendered, the React component that renders the list must be alerted -to changes to the list. - -#### Syntax - -```js - -``` - -##### Returns - -| Type | Description | -|------|-------------| -| Array | The array of banner objects. | - -Banner objects are sorted in descending order based on their `priority`, in the form: - -```js -{ - id: 'banner-123', - component: , - priority: 12, -} -``` - -| Field | Type | Description | -|-------|------|-------------| -| `component` | Any | The value displayed as the banner. | -| `id` | String | The ID of the banner, which can be used as a React "key". | -| `priority` | Number | The priority of the banner. | - -#### Example - -This can be used to supply the banners to the `GlobalBannerList` React component (which is done for you). - -```js -import { GlobalBannerList } from 'ui/notify'; - - -``` - -## Use in functional tests - -Functional tests are commonly used to verify that an action yielded a successful outcome. You can place a -`data-test-subj` attribute on the banner and use it to check if the banner exists inside of your functional test. -This acts as a proxy for verifying the successful outcome. Any unrecognized field will be added as a property of the -containing element. - -```js -banners.add({ - component: ( - - ), - data-test-subj: 'my-tested-banner', -}); -``` - -This will apply the `data-test-subj` to the element containing the `component`, so the inner HTML of that element -will exclusively be the specified `component`. - -Given that `component` is expected to be a React component, you could also add the `data-test-subj` directly to it: - -```js -banners.add({ - component: ( - - ), -}); -``` \ No newline at end of file diff --git a/src/legacy/ui/public/notify/banners/banners.tsx b/src/legacy/ui/public/notify/banners/banners.tsx deleted file mode 100644 index 777e408b8dfc4..0000000000000 --- a/src/legacy/ui/public/notify/banners/banners.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { npStart } from 'ui/new_platform'; -import { I18nProvider } from '@kbn/i18n/react'; - -const npBanners = npStart.core.overlays.banners; - -/** compatibility layer for new platform */ -const mountForComponent = (component: React.ReactElement) => (element: HTMLElement) => { - ReactDOM.render({component}, element); - return () => ReactDOM.unmountComponentAtNode(element); -}; - -/** - * Banners represents a prioritized list of displayed components. - */ -export class Banners { - /** - * Add a new banner. - * - * @param {Object} component The React component to display. - * @param {Number} priority The optional priority order to display this banner. Higher priority values are shown first. - * @return {String} A newly generated ID. This value can be used to remove/replace the banner. - */ - add = ({ component, priority }: { component: React.ReactElement; priority?: number }) => { - return npBanners.add(mountForComponent(component), priority); - }; - - /** - * Remove an existing banner. - * - * @param {String} id The ID of the banner to remove. - * @return {Boolean} {@code true} if the ID is recognized and the banner is removed. {@code false} otherwise. - */ - remove = (id: string): boolean => { - return npBanners.remove(id); - }; - - /** - * Replace an existing banner by removing it, if it exists, and adding a new one in its place. - * - * This is similar to calling banners.remove, followed by banners.add, except that it only notifies the listener - * after adding. - * - * @param {Object} component The React component to display. - * @param {String} id The ID of the Banner to remove. - * @param {Number} priority The optional priority order to display this banner. Higher priority values are shown first. - * @return {String} A newly generated ID. This value can be used to remove/replace the banner. - */ - set = ({ - component, - id, - priority = 0, - }: { - component: React.ReactElement; - id: string; - priority?: number; - }): string => { - return npBanners.replace(id, mountForComponent(component), priority); - }; -} - -/** - * A singleton instance meant to represent all Kibana banners. - */ -export const banners = new Banners(); diff --git a/src/legacy/ui/public/notify/banners/index.ts b/src/legacy/ui/public/notify/banners/index.ts deleted file mode 100644 index 9221f95074cd9..0000000000000 --- a/src/legacy/ui/public/notify/banners/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { banners } from './banners'; diff --git a/src/legacy/ui/public/notify/fatal_error.ts b/src/legacy/ui/public/notify/fatal_error.ts deleted file mode 100644 index 5614ffea7913e..0000000000000 --- a/src/legacy/ui/public/notify/fatal_error.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { npSetup } from 'ui/new_platform'; -import { AngularHttpError, addFatalError } from '../../../../plugins/kibana_legacy/public'; - -export function fatalError(error: AngularHttpError | Error | string, location?: string) { - addFatalError(npSetup.core.fatalErrors, error, location); -} diff --git a/src/legacy/ui/public/notify/index.d.ts b/src/legacy/ui/public/notify/index.d.ts deleted file mode 100644 index 3c1a7ba02db72..0000000000000 --- a/src/legacy/ui/public/notify/index.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { toastNotifications } from './toasts'; -export { fatalError } from './fatal_error'; diff --git a/src/legacy/ui/public/notify/index.js b/src/legacy/ui/public/notify/index.js deleted file mode 100644 index 51394033e4d2e..0000000000000 --- a/src/legacy/ui/public/notify/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { fatalError } from './fatal_error'; -export { toastNotifications } from './toasts'; -export { banners } from './banners'; diff --git a/src/legacy/ui/public/notify/toasts/index.ts b/src/legacy/ui/public/notify/toasts/index.ts deleted file mode 100644 index c5e8e3b7ca7a3..0000000000000 --- a/src/legacy/ui/public/notify/toasts/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -export { ToastNotifications } from './toast_notifications'; -export { toastNotifications } from './toasts'; diff --git a/src/legacy/ui/public/notify/toasts/toasts.ts b/src/legacy/ui/public/notify/toasts/toasts.ts deleted file mode 100644 index 15be7a7891553..0000000000000 --- a/src/legacy/ui/public/notify/toasts/toasts.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { npSetup } from 'ui/new_platform'; -import { ToastNotifications } from './toast_notifications'; -export const toastNotifications = new ToastNotifications(npSetup.core.notifications.toasts); diff --git a/src/legacy/ui/public/private/__tests__/private.js b/src/legacy/ui/public/private/__tests__/private.js deleted file mode 100644 index 1f9d696bb440f..0000000000000 --- a/src/legacy/ui/public/private/__tests__/private.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; - -describe('Private module loader', function () { - let Private; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function ($injector) { - Private = $injector.get('Private'); - }) - ); - - it('accepts a provider that will be called to init a module', function () { - const football = {}; - function Provider() { - return football; - } - - const instance = Private(Provider); - expect(instance).to.be(football); - }); - - it('injects angular dependencies into the Provider', function () { - function Provider(Private) { - return Private; - } - - const instance = Private(Provider); - expect(instance).to.be(Private); - }); - - it('detects circular dependencies', function () { - expect(function () { - function Provider1() { - Private(Provider2); - } - - function Provider2() { - Private(Provider1); - } - - Private(Provider1); - }).to.throwException(/circular/i); - }); - - it('always provides the same instance form the Provider', function () { - function Provider() { - return {}; - } - - expect(Private(Provider)).to.be(Private(Provider)); - }); - - describe('#stub', function () { - it('accepts a replacement instance for a Provider', function () { - const replaced = {}; - const replacement = {}; - - function Provider() { - return replaced; - } - - const instance = Private(Provider); - expect(instance).to.be(replaced); - - Private.stub(Provider, replacement); - - const instance2 = Private(Provider); - expect(instance2).to.be(replacement); - - Private.stub(Provider, replaced); - - const instance3 = Private(Provider); - expect(instance3).to.be(replaced); - }); - }); - - describe('#swap', function () { - it('accepts a new Provider that should replace an existing Provider', function () { - function Provider1() { - return {}; - } - - function Provider2() { - return {}; - } - - const instance1 = Private(Provider1); - expect(instance1).to.be.an('object'); - - Private.swap(Provider1, Provider2); - - const instance2 = Private(Provider1); - expect(instance2).to.be.an('object'); - expect(instance2).to.not.be(instance1); - - Private.swap(Provider1, Provider1); - - const instance3 = Private(Provider1); - expect(instance3).to.be(instance1); - }); - - it('gives the new Provider access to the Provider it replaced via an injectable dependency called $decorate', function () { - function Provider1() { - return {}; - } - - function Provider2($decorate) { - return { - instance1: $decorate(), - }; - } - - const instance1 = Private(Provider1); - expect(instance1).to.be.an('object'); - - Private.swap(Provider1, Provider2); - - const instance2 = Private(Provider1); - expect(instance2).to.have.property('instance1'); - expect(instance2.instance1).to.be(instance1); - }); - }); -}); diff --git a/src/legacy/ui/public/private/index.d.ts b/src/legacy/ui/public/private/index.d.ts deleted file mode 100644 index 3b692ba58cbe1..0000000000000 --- a/src/legacy/ui/public/private/index.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { IPrivate } from '../../../../plugins/kibana_legacy/public/'; diff --git a/src/legacy/ui/public/private/index.js b/src/legacy/ui/public/private/index.js deleted file mode 100644 index 3815f230df466..0000000000000 --- a/src/legacy/ui/public/private/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import './private'; diff --git a/src/legacy/ui/public/private/private.js b/src/legacy/ui/public/private/private.js deleted file mode 100644 index 7a0751959417e..0000000000000 --- a/src/legacy/ui/public/private/private.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiModules } from '../modules'; -import { PrivateProvider } from '../../../../plugins/kibana_legacy/public'; - -uiModules.get('kibana/private').provider('Private', PrivateProvider); diff --git a/src/legacy/ui/public/promises/__tests__/promises.js b/src/legacy/ui/public/promises/__tests__/promises.js deleted file mode 100644 index 7041aa2993376..0000000000000 --- a/src/legacy/ui/public/promises/__tests__/promises.js +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import sinon from 'sinon'; - -describe('Promise service', () => { - let Promise; - let $rootScope; - - const sandbox = sinon.createSandbox(); - function tick(ms = 0) { - sandbox.clock.tick(ms); - - // Ugly, but necessary for promises to resolve: https://github.com/angular/angular.js/issues/12555 - $rootScope.$apply(); - } - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(($injector) => { - sandbox.useFakeTimers(); - - Promise = $injector.get('Promise'); - $rootScope = $injector.get('$rootScope'); - }) - ); - - afterEach(() => sandbox.restore()); - - describe('Constructor', () => { - it('provides resolve and reject function', () => { - const executor = sinon.stub(); - new Promise(executor); - - sinon.assert.calledOnce(executor); - sinon.assert.calledWithExactly(executor, sinon.match.func, sinon.match.func); - }); - }); - - it('Promise.resolve', () => { - const onResolve = sinon.stub(); - Promise.resolve(true).then(onResolve); - - tick(); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, true); - }); - - describe('Promise.fromNode', () => { - it('creates a callback that controls a promise', () => { - const callback = sinon.stub(); - Promise.fromNode(callback); - - tick(); - - sinon.assert.calledOnce(callback); - sinon.assert.calledWithExactly(callback, sinon.match.func); - }); - - it('rejects if the callback receives an error', () => { - const err = new Error(); - const onReject = sinon.stub(); - Promise.fromNode(sinon.stub().yields(err)).catch(onReject); - - tick(); - - sinon.assert.calledOnce(onReject); - sinon.assert.calledWithExactly(onReject, sinon.match.same(err)); - }); - - it('resolves with the second argument', () => { - const result = {}; - const onResolve = sinon.stub(); - Promise.fromNode(sinon.stub().yields(null, result)).then(onResolve); - - tick(); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, sinon.match.same(result)); - }); - - it('resolves with an array if multiple arguments are received', () => { - const result1 = {}; - const result2 = {}; - const onResolve = sinon.stub(); - Promise.fromNode(sinon.stub().yields(null, result1, result2)).then(onResolve); - - tick(); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, [ - sinon.match.same(result1), - sinon.match.same(result2), - ]); - }); - - it('resolves with an array if multiple undefined are received', () => { - const onResolve = sinon.stub(); - Promise.fromNode(sinon.stub().yields(null, undefined, undefined)).then(onResolve); - - tick(); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, [undefined, undefined]); - }); - }); - - describe('Promise.race()', () => { - it(`resolves with the first resolved promise's value`, () => { - const p1 = new Promise((resolve) => setTimeout(resolve, 100, 1)); - const p2 = new Promise((resolve) => setTimeout(resolve, 200, 2)); - const onResolve = sinon.stub(); - Promise.race([p1, p2]).then(onResolve); - - tick(200); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, 1); - }); - - it(`rejects with the first rejected promise's rejection reason`, () => { - const p1Error = new Error('1'); - const p1 = new Promise((r, reject) => setTimeout(reject, 200, p1Error)); - - const p2Error = new Error('2'); - const p2 = new Promise((r, reject) => setTimeout(reject, 100, p2Error)); - - const onReject = sinon.stub(); - Promise.race([p1, p2]).catch(onReject); - - tick(200); - - sinon.assert.calledOnce(onReject); - sinon.assert.calledWithExactly(onReject, sinon.match.same(p2Error)); - }); - - it('does not wait for subsequent promises to resolve/reject', () => { - const onP1Resolve = sinon.stub(); - const p1 = new Promise((resolve) => setTimeout(resolve, 100)).then(onP1Resolve); - - const onP2Resolve = sinon.stub(); - const p2 = new Promise((resolve) => setTimeout(resolve, 101)).then(onP2Resolve); - - const onResolve = sinon.stub(); - Promise.race([p1, p2]).then(onResolve); - - tick(100); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledOnce(onP1Resolve); - sinon.assert.callOrder(onP1Resolve, onResolve); - sinon.assert.notCalled(onP2Resolve); - }); - - it('allows non-promises in the array', () => { - const onResolve = sinon.stub(); - Promise.race([1, 2, 3]).then(onResolve); - - tick(); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, 1); - }); - - describe('argument is undefined', () => { - it('rejects the promise', () => { - const football = {}; - const onReject = sinon.stub(); - Promise.race() - .catch(() => football) - .then(onReject); - - tick(); - - sinon.assert.calledOnce(onReject); - sinon.assert.calledWithExactly(onReject, sinon.match.same(football)); - }); - }); - - describe('argument is a string', () => { - it(`resolves with the first character`, () => { - const onResolve = sinon.stub(); - Promise.race('abc').then(onResolve); - - tick(); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, 'a'); - }); - }); - - describe('argument is a non-iterable object', () => { - it('reject the promise', () => { - const football = {}; - const onReject = sinon.stub(); - Promise.race({}) - .catch(() => football) - .then(onReject); - - tick(); - - sinon.assert.calledOnce(onReject); - sinon.assert.calledWithExactly(onReject, sinon.match.same(football)); - }); - }); - - describe('argument is a generator', () => { - it('resolves with the first resolved value', () => { - function* gen() { - yield new Promise((resolve) => setTimeout(resolve, 100, 1)); - yield new Promise((resolve) => setTimeout(resolve, 200, 2)); - } - - const onResolve = sinon.stub(); - Promise.race(gen()).then(onResolve); - - tick(200); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, 1); - }); - - it('resolves with the first non-promise value', () => { - function* gen() { - yield 1; - yield new Promise((resolve) => setTimeout(resolve, 200, 2)); - } - - const onResolve = sinon.stub(); - Promise.race(gen()).then(onResolve); - - tick(200); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, 1); - }); - - it('iterates all values from the generator, even if one is already "resolved"', () => { - let yieldCount = 0; - function* gen() { - yieldCount += 1; - yield 1; - yieldCount += 1; - yield new Promise((resolve) => setTimeout(resolve, 200, 2)); - } - - const onResolve = sinon.stub(); - Promise.race(gen()).then(onResolve); - - tick(200); - - sinon.assert.calledOnce(onResolve); - sinon.assert.calledWithExactly(onResolve, 1); - expect(yieldCount).to.be(2); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/promises/defer.ts b/src/legacy/ui/public/promises/defer.ts deleted file mode 100644 index 3d435f2ba8dfd..0000000000000 --- a/src/legacy/ui/public/promises/defer.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export interface Defer { - promise: Promise; - resolve(value: T): void; - reject(reason: Error): void; -} - -export function createDefer(Class: typeof Promise): Defer { - const defer: Partial> = {}; - defer.promise = new Class((resolve, reject) => { - defer.resolve = resolve; - defer.reject = reject; - }); - return defer as Defer; -} diff --git a/src/legacy/ui/public/promises/index.ts b/src/legacy/ui/public/promises/index.ts deleted file mode 100644 index 07bb2554c5eda..0000000000000 --- a/src/legacy/ui/public/promises/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PromiseServiceCreator } from '../../../../plugins/kibana_legacy/public'; -export { createDefer } from './defer'; -// @ts-ignore -import { uiModules } from '../modules'; - -const module = uiModules.get('kibana'); -// Provides a tiny subset of the excellent API from -// bluebird, reimplemented using the $q service -module.service('Promise', PromiseServiceCreator); diff --git a/src/legacy/ui/public/react_components.js b/src/legacy/ui/public/react_components.js deleted file mode 100644 index 21fb53d6407fa..0000000000000 --- a/src/legacy/ui/public/react_components.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'ngreact'; - -import { EuiIcon, EuiIconTip } from '@elastic/eui'; - -import { uiModules } from './modules'; - -const app = uiModules.get('app/kibana', ['react']); - -app.directive('icon', (reactDirective) => reactDirective(EuiIcon)); - -app.directive('iconTip', (reactDirective) => - reactDirective(EuiIconTip, ['content', 'type', 'position', 'title', 'color']) -); diff --git a/src/legacy/ui/public/registry/__tests__/registry.js b/src/legacy/ui/public/registry/__tests__/registry.js deleted file mode 100644 index 10b2423abc0e3..0000000000000 --- a/src/legacy/ui/public/registry/__tests__/registry.js +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiRegistry } from '../_registry'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; - -describe('Registry', function () { - let Private; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function ($injector) { - Private = $injector.get('Private'); - }) - ); - - it('is technically a function', function () { - const reg = uiRegistry(); - expect(reg).to.be.a('function'); - }); - - describe('#register', function () { - it('accepts a Private module', function () { - const reg = uiRegistry(); - const mod = function SomePrivateModule() {}; - - reg.register(mod); - // modules are not exposed, so this is the most that we can test - }); - - it('applies the filter function if one is specified', function () { - const reg = uiRegistry({ - filter: (item) => item.value % 2 === 0, // register only even numbers - }); - - reg.register(() => ({ value: 17 })); - reg.register(() => ({ value: 18 })); // only this one should get registered - reg.register(() => ({ value: 19 })); - - const modules = Private(reg); - expect(modules).to.have.length(1); - expect(modules[0].value).to.be(18); - }); - }); - - describe('as a module', function () { - it('exposes the list of registered modules', function () { - const reg = uiRegistry(); - const mod = function SomePrivateModule(Private) { - this.PrivateModuleLoader = Private; - }; - - reg.register(mod); - const modules = Private(reg); - expect(modules).to.have.length(1); - expect(modules[0]).to.have.property('PrivateModuleLoader', Private); - }); - }); - - describe('spec', function () { - it('executes with the module list as "this", and can override it', function () { - let self; - - const reg = uiRegistry({ - constructor: function () { - return { mods: (self = this) }; - }, - }); - - const modules = Private(reg); - expect(modules).to.be.an('object'); - expect(modules).to.have.property('mods', self); - }); - }); - - describe('spec.name', function () { - it('sets the displayName of the registry and the name param on the final instance', function () { - const reg = uiRegistry({ - name: 'visTypes', - }); - - expect(reg).to.have.property('displayName', '[registry visTypes]'); - expect(Private(reg)).to.have.property('name', 'visTypes'); - }); - }); - - describe('spec.constructor', function () { - it('executes before the modules are returned', function () { - let i = 0; - - const reg = uiRegistry({ - constructor: function () { - i = i + 1; - }, - }); - - Private(reg); - expect(i).to.be(1); - }); - - it('executes with the module list as "this", and can override it', function () { - let self; - - const reg = uiRegistry({ - constructor: function () { - return { mods: (self = this) }; - }, - }); - - const modules = Private(reg); - expect(modules).to.be.an('object'); - expect(modules).to.have.property('mods', self); - }); - }); - - describe('spec.invokeProviders', () => { - it('is called with the registered providers and defines the initial set of values in the registry', () => { - const reg = uiRegistry({ - invokeProviders(providers) { - return providers.map((i) => i * 1000); - }, - }); - - reg.register(1); - reg.register(2); - reg.register(3); - expect(Private(reg).toJSON()).to.eql([1000, 2000, 3000]); - }); - it('does not get assigned as a property of the registry', () => { - expect( - uiRegistry({ - invokeProviders() {}, - }) - ).to.not.have.property('invokeProviders'); - }); - }); - - describe('spec[any]', function () { - it('mixes the extra properties into the module list', function () { - const reg = uiRegistry({ - someMethod: function () { - return this; - }, - }); - - const modules = Private(reg); - expect(modules).to.have.property('someMethod'); - expect(modules.someMethod()).to.be(modules); - }); - }); -}); diff --git a/src/legacy/ui/public/registry/_registry.d.ts b/src/legacy/ui/public/registry/_registry.d.ts deleted file mode 100644 index 42f1bb521763c..0000000000000 --- a/src/legacy/ui/public/registry/_registry.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexedArray, IndexedArrayConfig } from '../indexed_array'; - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface UIRegistry extends IndexedArray {} - -interface UIRegistrySpec extends IndexedArrayConfig { - name: string; - filter?(item: T): boolean; -} - -/** - * Creates a new UiRegistry (See js method for detailed documentation) - * The generic type T is the type of objects which are stored in the registry. - * The generic type A is an interface of accessors which depend on the - * fields of the objects stored in the registry. - * Example: if there is a string field "name" in type T, then A should be - * `{ byName: { [typeName: string]: T }; }` - */ -declare function uiRegistry( - spec: UIRegistrySpec -): { (): UIRegistry & A; register(privateModule: T): UIRegistry & A }; diff --git a/src/legacy/ui/public/registry/_registry.js b/src/legacy/ui/public/registry/_registry.js deleted file mode 100644 index 85aa1d9f2eca8..0000000000000 --- a/src/legacy/ui/public/registry/_registry.js +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { IndexedArray } from '../indexed_array'; - -const notPropsOptNames = IndexedArray.OPT_NAMES.concat('constructor', 'invokeProviders'); - -/** - * Create a registry, which is just a Private module provider. - * - * The registry allows modifying the values it will provide - * using the #register method. - * - * To access these modules, pass the registry to the Private - * module loader. - * - * # Examples - * - * + register a module - * ```js - * let registry = require('ui/registry/vis_types'); - * registry.add(function InjectablePrivateModule($http, Promise) { - * ... - * }) - * ``` - * - * + get all registered modules - * ```js - * let visTypes = Private(RegistryVisTypesProvider); - * ``` - * - * - * @param {object} [spec] - an object describing the properties of - * the registry to create. Any property specified - * that is not listed below will be mixed into the - * final IndexedArray object. - * - * # init - * @param {Function} [spec.constructor] - an injectable function that is called when - * the registry is first instantiated by the app. - * @param {boolean} [spec.filter] - function that will be used to filter items before - * registering them. Function will called on each item and - * should return true to keep the item (register it) or - * skip it (don't register it) - * - * # IndexedArray params - * @param {array[String]} [spec.index] - passed to the IndexedArray constructor - * @param {array[String]} [spec.group] - passed to the IndexedArray constructor - * @param {array[String]} [spec.order] - passed to the IndexedArray constructor - * @param {array[String]} [spec.initialSet] - passed to the IndexedArray constructor - * @param {array[String]} [spec.immutable] - passed to the IndexedArray constructor - * - * @return {[type]} [description] - */ -export function uiRegistry(spec) { - spec = spec || {}; - - const constructor = _.has(spec, 'constructor') && spec.constructor; - const filter = _.has(spec, 'filter') && spec.filter; - const invokeProviders = _.has(spec, 'invokeProviders') && spec.invokeProviders; - const iaOpts = _.defaults(_.pick(spec, IndexedArray.OPT_NAMES), { index: ['name'] }); - const props = _.omit(spec, notPropsOptNames); - const providers = []; - - let isInstantiated = false; - let getInvokedProviders; - let modules; - - /** - * This is the Private module that will be instantiated by - * - * @tag:PrivateModule - * @return {IndexedArray} - an indexed array containing the values - * that were registered, the registry spec - * defines how things will be indexed. - */ - const registry = function (Private, $injector) { - getInvokedProviders = function (newProviders) { - let set = invokeProviders - ? $injector.invoke(invokeProviders, undefined, { providers: newProviders }) - : newProviders.map(Private); - - if (filter && _.isFunction(filter)) { - set = set.filter((item) => filter(item)); - } - - return set; - }; - - iaOpts.initialSet = getInvokedProviders(providers); - - modules = new IndexedArray(iaOpts); - - // mixin other props - _.assign(modules, props); - - // construct - if (constructor) { - modules = $injector.invoke(constructor, modules) || modules; - } - - isInstantiated = true; - - return modules; - }; - - registry.displayName = '[registry ' + props.name + ']'; - - registry.register = function (privateModule) { - providers.push(privateModule); - - if (isInstantiated) { - const [provider] = getInvokedProviders([privateModule]); - - if (provider) { - modules.push(provider); - } - } - - return registry; - }; - - return registry; -} diff --git a/src/legacy/ui/public/registry/chrome_header_nav_controls.ts b/src/legacy/ui/public/registry/chrome_header_nav_controls.ts deleted file mode 100644 index 7e6c80692cd63..0000000000000 --- a/src/legacy/ui/public/registry/chrome_header_nav_controls.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexedArray } from '../indexed_array'; -import { uiRegistry, UIRegistry } from './_registry'; - -interface ChromeHeaderNavControlsRegistryAccessors { - bySide: { [typeName: string]: IndexedArray }; -} - -export enum NavControlSide { - Left = 'left', - Right = 'right', -} - -export interface NavControl { - name: string; - order: number; - side: NavControlSide; - render: (targetDomElement: HTMLDivElement) => () => void; -} - -export type ChromeHeaderNavControlsRegistry = UIRegistry & - ChromeHeaderNavControlsRegistryAccessors; - -export const chromeHeaderNavControlsRegistry = uiRegistry< - NavControl, - ChromeHeaderNavControlsRegistryAccessors ->({ - name: 'chromeHeaderNavControls', - order: ['order'], - group: ['side'], -}); diff --git a/src/legacy/ui/public/registry/field_format_editors.ts b/src/legacy/ui/public/registry/field_format_editors.ts deleted file mode 100644 index 5489ce9250e04..0000000000000 --- a/src/legacy/ui/public/registry/field_format_editors.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiRegistry } from './_registry'; - -export const RegistryFieldFormatEditorsProvider = uiRegistry({ - name: 'fieldFormatEditors', - index: ['formatId'], -}); diff --git a/src/legacy/ui/public/routes/__tests__/_route_manager.js b/src/legacy/ui/public/routes/__tests__/_route_manager.js deleted file mode 100644 index eb47a3e9ace70..0000000000000 --- a/src/legacy/ui/public/routes/__tests__/_route_manager.js +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import ngMock from 'ng_mock'; -import sinon from 'sinon'; -import RouteManager from '../route_manager'; -import expect from '@kbn/expect'; - -let routes; // will contain an new instance of RouteManager for each test -const chainableMethods = [ - { name: 'when', args: ['', {}] }, - { name: 'otherwise', args: [{}] }, - { name: 'defaults', args: [/regexp/, {}] }, -]; - -let $rp; -describe('routes/route_manager', function () { - beforeEach( - ngMock.module('kibana', function ($routeProvider) { - $rp = $routeProvider; - sinon.stub($rp, 'otherwise'); - sinon.stub($rp, 'when'); - }) - ); - - beforeEach( - ngMock.inject(function () { - routes = new RouteManager(); - }) - ); - - it('should have chainable methods: ' + _.map(chainableMethods, 'name').join(', '), function () { - chainableMethods.forEach(function (meth) { - expect(routes[meth.name].apply(routes, _.clone(meth.args))).to.be(routes); - }); - }); - - describe('#otherwise', function () { - it('should forward the last otherwise route', function () { - const otherRoute = {}; - routes.otherwise({}); - routes.otherwise(otherRoute); - - routes.config($rp); - - expect($rp.otherwise.callCount).to.be(1); - expect($rp.otherwise.getCall(0).args[0]).to.be(otherRoute); - }); - }); - - describe('#when', function () { - it('should merge the additions into the when() defined routes', function () { - routes.when('/some/route'); - routes.when('/some/other/route'); - - // add the addition resolve to every route - routes.defaults(/.*/, { - resolve: { - addition: function () {}, - }, - }); - - routes.config($rp); - - // should have run once for each when route - expect($rp.when.callCount).to.be(2); - expect($rp.otherwise.callCount).to.be(0); - - // every route should have the "addition" resolve - expect($rp.when.getCall(0).args[1].resolve.addition).to.be.a('function'); - expect($rp.when.getCall(1).args[1].resolve.addition).to.be.a('function'); - }); - }); - - describe('#config', function () { - it('should add defined routes to the global $routeProvider service in order', function () { - const args = [ - ['/one', {}], - ['/two', {}], - ]; - - args.forEach(function (a) { - routes.when(a[0], a[1]); - }); - - routes.config($rp); - - expect($rp.when.callCount).to.be(args.length); - _.times(args.length, function (i) { - const call = $rp.when.getCall(i); - const a = args.shift(); - - expect(call.args[0]).to.be(a[0]); - expect(call.args[1]).to.be(a[1]); - }); - }); - - it('sets route.reloadOnSearch to false by default', function () { - routes.when('/nothing-set'); - routes.when('/no-reload', { reloadOnSearch: false }); - routes.when('/always-reload', { reloadOnSearch: true }); - routes.config($rp); - - expect($rp.when.callCount).to.be(3); - expect($rp.when.firstCall.args[1]).to.have.property('reloadOnSearch', false); - expect($rp.when.secondCall.args[1]).to.have.property('reloadOnSearch', false); - expect($rp.when.lastCall.args[1]).to.have.property('reloadOnSearch', true); - }); - }); - - describe('#defaults()', () => { - it('adds defaults to routes with matching paths', () => { - routes.when('/foo', { name: 'foo' }); - routes.when('/bar', { name: 'bar' }); - routes.when('/baz', { name: 'baz' }); - routes.defaults(/^\/ba/, { - withDefaults: true, - }); - routes.config($rp); - - sinon.assert.calledWithExactly( - $rp.when, - '/foo', - sinon.match({ name: 'foo', withDefaults: undefined }) - ); - sinon.assert.calledWithExactly( - $rp.when, - '/bar', - sinon.match({ name: 'bar', withDefaults: true }) - ); - sinon.assert.calledWithExactly( - $rp.when, - '/baz', - sinon.match({ name: 'baz', withDefaults: true }) - ); - }); - - it('does not override values specified in the route', () => { - routes.when('/foo', { name: 'foo' }); - routes.defaults(/./, { name: 'bar' }); - routes.config($rp); - - sinon.assert.calledWithExactly($rp.when, '/foo', sinon.match({ name: 'foo' })); - }); - - // See https://github.com/elastic/kibana/issues/13294 - it('does not assign defaults by reference, to prevent accidentally merging unrelated defaults together', () => { - routes.when('/foo', { name: 'foo' }); - routes.when('/bar', { name: 'bar' }); - routes.when('/baz', { name: 'baz', funcs: { bazFunc() {} } }); - - // multiple defaults must be defined that, when applied correctly, will - // create a new object property on all routes that is unique to all of them - routes.defaults(/./, { funcs: { all() {} } }); - routes.defaults(/^\/foo/, { funcs: { fooFunc() {} } }); - routes.defaults(/^\/bar/, { funcs: { barFunc() {} } }); - routes.config($rp); - - sinon.assert.calledThrice($rp.when); - sinon.assert.calledWithExactly( - $rp.when, - '/foo', - sinon.match({ - name: 'foo', - funcs: sinon.match({ - all: sinon.match.func, - fooFunc: sinon.match.func, - barFunc: undefined, - bazFunc: undefined, - }), - }) - ); - sinon.assert.calledWithExactly( - $rp.when, - '/bar', - sinon.match({ - name: 'bar', - funcs: sinon.match({ - all: sinon.match.func, - fooFunc: undefined, - barFunc: sinon.match.func, - bazFunc: undefined, - }), - }) - ); - sinon.assert.calledWithExactly( - $rp.when, - '/baz', - sinon.match({ - name: 'baz', - funcs: sinon.match({ - all: sinon.match.func, - fooFunc: undefined, - barFunc: undefined, - bazFunc: sinon.match.func, - }), - }) - ); - }); - }); -}); diff --git a/src/legacy/ui/public/routes/__tests__/_work_queue.js b/src/legacy/ui/public/routes/__tests__/_work_queue.js deleted file mode 100644 index 72891f7321fbd..0000000000000 --- a/src/legacy/ui/public/routes/__tests__/_work_queue.js +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { WorkQueue } from '../work_queue'; -import sinon from 'sinon'; -import { createDefer } from 'ui/promises'; - -describe('work queue', function () { - let queue; - let Promise; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function (_Promise_) { - Promise = _Promise_; - }) - ); - beforeEach(function () { - queue = new WorkQueue(); - }); - afterEach(function () { - queue.empty(); - }); - - describe('#push', function () { - it('adds to the interval queue', function () { - queue.push(createDefer(Promise)); - expect(queue).to.have.length(1); - }); - }); - - describe('#resolveWhenFull', function () { - it('resolves requests waiting for the queue to fill when appropriate', function () { - const size = _.random(5, 50); - queue.limit = size; - - const whenFull = createDefer(Promise); - sinon.stub(whenFull, 'resolve'); - queue.resolveWhenFull(whenFull); - - // push all but one into the queue - _.times(size - 1, function () { - queue.push(createDefer(Promise)); - }); - - expect(whenFull.resolve.callCount).to.be(0); - queue.push(createDefer(Promise)); - expect(whenFull.resolve.callCount).to.be(1); - - queue.empty(); - }); - }); - - /** - * Fills the queue with a random number of work defers, but stubs all defer methods - * with the same stub and passed it back. - * - * @param {function} then - called with then(size, stub) so that the test - * can manipulate the filled queue - */ - function fillWithStubs(then) { - const size = _.random(5, 50); - const stub = sinon.stub(); - - _.times(size, function () { - const d = createDefer(Promise); - // overwrite the defer methods with the stub - d.resolve = stub; - d.reject = stub; - queue.push(d); - }); - - then(size, stub); - } - - describe('#doWork', function () { - it('flushes the queue and resolves all promises', function () { - fillWithStubs(function (size, stub) { - expect(queue).to.have.length(size); - queue.doWork(); - expect(queue).to.have.length(0); - expect(stub.callCount).to.be(size); - }); - }); - }); - - describe('#empty()', function () { - it('empties the internal queue WITHOUT resolving any promises', function () { - fillWithStubs(function (size, stub) { - expect(queue).to.have.length(size); - queue.empty(); - expect(queue).to.have.length(0); - expect(stub.callCount).to.be(0); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js b/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js deleted file mode 100644 index 8ae85fce591a1..0000000000000 --- a/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import RouteManager from '../route_manager'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; - -import _ from 'lodash'; -import '../../private'; - -let routes; - -describe('wrapRouteWithPrep fn', function () { - require('test_utils/no_digest_promises').activateForSuite(); - - beforeEach(function () { - routes = new RouteManager(); - }); - - const SchedulingTest = function (opts) { - opts = opts || {}; - - const delaySetup = opts.delayUserWork ? 0 : 50; - const delayUserWork = opts.delayUserWork ? 50 : 0; - - return function () { - ngMock.module('kibana'); - let setupComplete = false; - let userWorkComplete = false; - let route; - let Promise; - let $injector; - - ngMock.inject(function (_Promise_, _$injector_) { - Promise = _Promise_; - $injector = _$injector_; - }); - - routes.addSetupWork(function () { - return new Promise(function (resolve) { - setTimeout(function () { - setupComplete = true; - resolve(); - }, delaySetup); - }); - }); - - routes - .when('/', { - resolve: { - test: function () { - expect(setupComplete).to.be(true); - userWorkComplete = true; - }, - }, - }) - .config({ - when: function (p, _r) { - route = _r; - }, - }); - - return new Promise(function (resolve, reject) { - setTimeout(function () { - Promise.all( - _.map(route.resolve, function (fn) { - return $injector.invoke(fn); - }) - ) - .then(function () { - expect(setupComplete).to.be(true); - expect(userWorkComplete).to.be(true); - }) - .then(resolve, reject); - }, delayUserWork); - }); - }; - }; - - it('always waits for setup to complete before calling user work', new SchedulingTest()); - - it('does not call user work when setup fails', new SchedulingTest({ failSetup: true })); - - it( - 'calls all user work even if it is not initialized until after setup is complete', - new SchedulingTest({ - delayUserWork: false, - }) - ); -}); diff --git a/src/legacy/ui/public/routes/__tests__/index.js b/src/legacy/ui/public/routes/__tests__/index.js deleted file mode 100644 index 30cedaaca3d19..0000000000000 --- a/src/legacy/ui/public/routes/__tests__/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import './_route_manager'; -import './_work_queue'; -import './_wrap_route_with_prep'; -describe('Custom Route Management', function () {}); diff --git a/src/legacy/ui/public/routes/breadcrumbs.js b/src/legacy/ui/public/routes/breadcrumbs.js deleted file mode 100644 index 7917ffbd7c6e6..0000000000000 --- a/src/legacy/ui/public/routes/breadcrumbs.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { trim, startCase } from 'lodash'; - -/** - * Take a path (from $location.path() usually) and parse - * it's segments into a list of breadcrumbs - * - * @param {string} path - * @return {Array} - */ -export function parsePathToBreadcrumbs(path) { - return trim(path, '/') - .split('/') - .reduce( - (acc, id, i, parts) => [ - ...acc, - { - id, - display: startCase(id), - href: i === 0 ? `#/${id}` : `${acc[i - 1].href}/${id}`, - current: i === parts.length - 1, - }, - ], - [] - ); -} diff --git a/src/legacy/ui/public/routes/index.d.ts b/src/legacy/ui/public/routes/index.d.ts deleted file mode 100644 index 84e17b0a69452..0000000000000 --- a/src/legacy/ui/public/routes/index.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiRoutes, UIRoutes } from 'ui/routes/routes'; - -// eslint-disable-next-line import/no-default-export -export default uiRoutes; -export { UIRoutes }; diff --git a/src/legacy/ui/public/routes/index.js b/src/legacy/ui/public/routes/index.js deleted file mode 100644 index 9c826ebee1230..0000000000000 --- a/src/legacy/ui/public/routes/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiRoutes } from './routes'; - -// eslint-disable-next-line import/no-default-export -export default uiRoutes; diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts deleted file mode 100644 index a5261a7c8ee3a..0000000000000 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * WARNING: these types are incomplete - */ - -import { ChromeBreadcrumb } from '../../../../core/public'; - -export interface RouteConfiguration { - controller?: string | ((...args: any[]) => void); - redirectTo?: string; - resolveRedirectTo?: (...args: any[]) => void; - reloadOnSearch?: boolean; - reloadOnUrl?: boolean; - outerAngularWrapperRoute?: boolean; - resolve?: object; - template?: string; - k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; - requireUICapability?: string; -} - -interface RouteManager { - addSetupWork(cb: (...args: any[]) => void): void; - when(path: string, routeConfiguration: RouteConfiguration): RouteManager; - otherwise(routeConfiguration: RouteConfiguration): RouteManager; - defaults(path: string | RegExp, defaults: RouteConfiguration): RouteManager; -} - -// eslint-disable-next-line import/no-default-export -export default RouteManager; diff --git a/src/legacy/ui/public/routes/route_manager.js b/src/legacy/ui/public/routes/route_manager.js deleted file mode 100644 index de8a541d1c50a..0000000000000 --- a/src/legacy/ui/public/routes/route_manager.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { cloneDeep, defaultsDeep, wrap } from 'lodash'; - -import { wrapRouteWithPrep } from './wrap_route_with_prep'; -import { RouteSetupManager } from './route_setup_manager'; -import { parsePathToBreadcrumbs } from './breadcrumbs'; - -// eslint-disable-next-line import/no-default-export -export default function RouteManager() { - const self = this; - const setup = new RouteSetupManager(); - const when = []; - const defaults = []; - let otherwise; - - self.config = function ($routeProvider) { - when.forEach(function (args) { - const path = args[0]; - const route = args[1] || {}; - - defaults.forEach((def) => { - if (def.regex.test(path)) { - defaultsDeep(route, cloneDeep(def.value)); - } - }); - - if (route.reloadOnSearch == null) { - route.reloadOnSearch = false; - } - - wrapRouteWithPrep(route, setup); - $routeProvider.when(path, route); - }); - - if (otherwise) { - wrapRouteWithPrep(otherwise, setup); - $routeProvider.otherwise(otherwise); - } - }; - - self.run = function ($location, $route, $injector, $rootScope) { - if (window.elasticApm && typeof window.elasticApm.startTransaction === 'function') { - /** - * capture route-change events as transactions which happens after - * the browser's on load event. - * - * In Kibana app, this logic would run after the boostrap js files gets - * downloaded and get associated with the page-load transaction - */ - $rootScope.$on('$routeChangeStart', (_, nextRoute) => { - if (nextRoute.$$route) { - const name = nextRoute.$$route.originalPath; - window.elasticApm.startTransaction(name, 'route-change'); - } - }); - } - - self.getBreadcrumbs = () => { - const breadcrumbs = parsePathToBreadcrumbs($location.path()); - const map = $route.current.mapBreadcrumbs; - return map ? $injector.invoke(map, null, { breadcrumbs }) : breadcrumbs; - }; - }; - - const wrapSetupAndChain = (fn, ...args) => { - fn.apply(setup, args); - return this; - }; - - this.addSetupWork = wrap(setup.addSetupWork, wrapSetupAndChain); - this.afterSetupWork = wrap(setup.afterSetupWork, wrapSetupAndChain); - this.afterWork = wrap(setup.afterWork, wrapSetupAndChain); - - self.when = function (path, route) { - when.push([path, route]); - return self; - }; - - // before attaching the routes to the routeProvider, test the RE - // against the .when() path and add/override the resolves if there is a match - self.defaults = function (regex, value) { - defaults.push({ regex, value }); - return self; - }; - - self.otherwise = function (route) { - otherwise = route; - return self; - }; - - self.getBreadcrumbs = function () { - // overwritten in self.run(); - return []; - }; - - self.RouteManager = RouteManager; -} diff --git a/src/legacy/ui/public/routes/route_setup_manager.js b/src/legacy/ui/public/routes/route_setup_manager.js deleted file mode 100644 index a7a2f078f40fb..0000000000000 --- a/src/legacy/ui/public/routes/route_setup_manager.js +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { createDefer } from 'ui/promises'; - -// Throw this inside of an Angular route resolver after calling `kbnUrl.change` -// so that the $router can observe the $location update. Otherwise, the location -// route setup work will resolve before the route update occurs. -export const WAIT_FOR_URL_CHANGE_TOKEN = new Error('WAIT_FOR_URL_CHANGE_TOKEN'); - -export class RouteSetupManager { - constructor() { - this.setupWork = []; - this.onSetupComplete = []; - this.onSetupError = []; - this.onWorkComplete = []; - this.onWorkError = []; - } - - addSetupWork(fn) { - this.setupWork.push(fn); - } - - afterSetupWork(onComplete, onError) { - this.onSetupComplete.push(onComplete); - this.onSetupError.push(onError); - } - - afterWork(onComplete, onError) { - this.onWorkComplete.push(onComplete); - this.onWorkError.push(onError); - } - - /** - * Do each setupWork function by injecting it with angular dependencies - * and accepting promises from it. - * @return {[type]} [description] - */ - doWork(Promise, $injector, userWork) { - const invokeEach = (arr, locals) => { - return Promise.map(arr, (fn) => { - if (!fn) return; - return $injector.invoke(fn, null, locals); - }); - }; - - // call each error handler in order, until one of them resolves - // or we run out of handlers - const callErrorHandlers = (handlers, origError) => { - if (!_.size(handlers)) throw origError; - - // clone so we don't discard handlers or loose them - handlers = handlers.slice(0); - - const next = (err) => { - if (!handlers.length) throw err; - - const handler = handlers.shift(); - if (!handler) return next(err); - - return Promise.try(function () { - return $injector.invoke(handler, null, { err }); - }).catch(next); - }; - - return next(origError); - }; - - return invokeEach(this.setupWork) - .then( - () => invokeEach(this.onSetupComplete), - (err) => callErrorHandlers(this.onSetupError, err) - ) - .then(() => { - // wait for the queue to fill up, then do all the work - const defer = createDefer(Promise); - userWork.resolveWhenFull(defer); - - return defer.promise.then(() => Promise.all(userWork.doWork())); - }) - .catch((error) => { - if (error === WAIT_FOR_URL_CHANGE_TOKEN) { - // prevent moving forward, return a promise that never resolves - // so that the $router can observe the $location update - return Promise.halt(); - } - - throw error; - }) - .then( - () => invokeEach(this.onWorkComplete), - (err) => callErrorHandlers(this.onWorkError, err) - ); - } -} diff --git a/src/legacy/ui/public/routes/routes.d.ts b/src/legacy/ui/public/routes/routes.d.ts deleted file mode 100644 index d48230e9d56f9..0000000000000 --- a/src/legacy/ui/public/routes/routes.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import RouteManager from 'ui/routes/route_manager'; - -interface DefaultRouteManager extends RouteManager { - WAIT_FOR_URL_CHANGE_TOKEN: string; - enable(): void; -} - -export const uiRoutes: DefaultRouteManager; -export type UIRoutes = DefaultRouteManager; diff --git a/src/legacy/ui/public/routes/routes.js b/src/legacy/ui/public/routes/routes.js deleted file mode 100644 index 2cfb7a608e853..0000000000000 --- a/src/legacy/ui/public/routes/routes.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import RouteManager from './route_manager'; -import 'angular-route/angular-route'; -import { uiModules } from '../modules'; -import { WAIT_FOR_URL_CHANGE_TOKEN } from './route_setup_manager'; -const defaultRouteManager = new RouteManager(); - -export const uiRoutes = Object.create(defaultRouteManager, { - WAIT_FOR_URL_CHANGE_TOKEN: { - value: WAIT_FOR_URL_CHANGE_TOKEN, - }, - - enable: { - value() { - uiModules - .get('kibana', ['ngRoute']) - .config(defaultRouteManager.config) - .run(defaultRouteManager.run); - }, - }, -}); diff --git a/src/legacy/ui/public/routes/work_queue.js b/src/legacy/ui/public/routes/work_queue.js deleted file mode 100644 index 5c4c83663c590..0000000000000 --- a/src/legacy/ui/public/routes/work_queue.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function WorkQueue() { - const q = this; - - const work = []; - const fullDefers = []; - - q.limit = 0; - Object.defineProperty(q, 'length', { - get: function () { - return work.length; - }, - }); - - const resolve = function (defers) { - return defers.splice(0).map(function (defer) { - return defer.resolve(); - }); - }; - - const checkIfFull = function () { - if (work.length >= q.limit && fullDefers.length) { - resolve(fullDefers); - } - }; - - q.resolveWhenFull = function (defer) { - fullDefers.push(defer); - checkIfFull(); - }; - - q.doWork = function () { - const resps = resolve(work); - checkIfFull(); - return resps; - }; - - q.empty = function () { - work.splice(0); - checkIfFull(); - }; - - q.push = function (defer) { - work.push(defer); - checkIfFull(); - }; -} diff --git a/src/legacy/ui/public/routes/wrap_route_with_prep.js b/src/legacy/ui/public/routes/wrap_route_with_prep.js deleted file mode 100644 index e9ed33148d9ac..0000000000000 --- a/src/legacy/ui/public/routes/wrap_route_with_prep.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular from 'angular'; -import _ from 'lodash'; -import { createDefer } from 'ui/promises'; -import { WorkQueue } from './work_queue'; - -export function wrapRouteWithPrep(route, setup) { - if (!route.resolve && route.redirectTo) return; - - const userWork = new WorkQueue(); - // the point at which we will consider the queue "full" - userWork.limit = _.keys(route.resolve).length; - - const resolve = { - __prep__: function ($injector) { - return $injector.invoke(setup.doWork, setup, { userWork }); - }, - }; - - // send each user resolve to the userWork queue, which will prevent it from running before the - // prep is complete - _.forOwn(route.resolve || {}, function (expr, name) { - resolve[name] = function ($injector, Promise) { - const defer = createDefer(Promise); - userWork.push(defer); - return defer.promise.then(function () { - return $injector[angular.isString(expr) ? 'get' : 'invoke'](expr); - }); - }; - }); - - // we're copied everything over so now overwrite - route.resolve = resolve; -} diff --git a/src/legacy/ui/public/state_management/__tests__/app_state.js b/src/legacy/ui/public/state_management/__tests__/app_state.js deleted file mode 100644 index c47fa3bb0417f..0000000000000 --- a/src/legacy/ui/public/state_management/__tests__/app_state.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { AppStateProvider } from '../app_state'; - -describe('State Management', function () { - let $rootScope; - let AppState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function (_$rootScope_, _$location_, Private) { - $rootScope = _$rootScope_; - AppState = Private(AppStateProvider); - }) - ); - - describe('App State', function () { - let appState; - - beforeEach(function () { - appState = new AppState(); - }); - - it('should have _urlParam of _a', function () { - expect(appState).to.have.property('_urlParam'); - expect(appState._urlParam).to.equal('_a'); - }); - - it('should use passed in params', function () { - const params = { - test: true, - mock: false, - }; - - appState = new AppState(params); - expect(appState).to.have.property('_defaults'); - - Object.keys(params).forEach(function (key) { - expect(appState._defaults).to.have.property(key); - expect(appState._defaults[key]).to.equal(params[key]); - }); - }); - - it('should have a destroy method', function () { - expect(appState).to.have.property('destroy'); - }); - - it('should be destroyed on $routeChangeStart', function () { - const destroySpy = sinon.spy(appState, 'destroy'); - - $rootScope.$emit('$routeChangeStart'); - - expect(destroySpy.callCount).to.be(1); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/__tests__/config_provider.js b/src/legacy/ui/public/state_management/__tests__/config_provider.js deleted file mode 100644 index 9f756bc51dcb0..0000000000000 --- a/src/legacy/ui/public/state_management/__tests__/config_provider.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../config_provider'; - -describe('State Management Config', function () { - let stateManagementConfig; - - describe('is enabled', () => { - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function (_stateManagementConfig_) { - stateManagementConfig = _stateManagementConfig_; - }) - ); - - it('should be enabled by default', () => { - expect(stateManagementConfig.enabled).to.be(true); - }); - }); - - describe('can be disabled', () => { - beforeEach( - ngMock.module('kibana', function (stateManagementConfigProvider) { - stateManagementConfigProvider.disable(); - }) - ); - - beforeEach( - ngMock.inject(function (_stateManagementConfig_) { - stateManagementConfig = _stateManagementConfig_; - }) - ); - - it('is disabled by config', () => { - expect(stateManagementConfig.enabled).to.be(false); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/__tests__/global_state.js b/src/legacy/ui/public/state_management/__tests__/global_state.js deleted file mode 100644 index e9dae5880a8d1..0000000000000 --- a/src/legacy/ui/public/state_management/__tests__/global_state.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../global_state'; - -describe('State Management', function () { - let $location; - let state; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function (_$location_, globalState) { - $location = _$location_; - state = globalState; - }) - ); - - describe('Global State', function () { - it('should use previous state when not in URL', function () { - // set satte via URL - $location.search({ _g: '(foo:(bar:baz))' }); - state.fetch(); - expect(state.toObject()).to.eql({ foo: { bar: 'baz' } }); - - $location.search({ _g: '(fizz:buzz)' }); - state.fetch(); - expect(state.toObject()).to.eql({ fizz: 'buzz' }); - - $location.search({}); - state.fetch(); - expect(state.toObject()).to.eql({ fizz: 'buzz' }); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/__tests__/state.js b/src/legacy/ui/public/state_management/__tests__/state.js deleted file mode 100644 index b6c705e814509..0000000000000 --- a/src/legacy/ui/public/state_management/__tests__/state.js +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { encode as encodeRison } from 'rison-node'; -import uiRoutes from 'ui/routes'; -import '../../private'; -import { toastNotifications } from '../../notify'; -import * as FatalErrorNS from '../../notify/fatal_error'; -import { StateProvider } from '../state'; -import { - unhashQuery, - createStateHash, - isStateHash, - HashedItemStore, -} from '../../../../../plugins/kibana_utils/public'; -import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; -import { EventsProvider } from '../../events'; - -describe('State Management', () => { - const sandbox = sinon.createSandbox(); - afterEach(() => sandbox.restore()); - - uiRoutes.enable(); - - describe('Enabled', () => { - let $rootScope; - let $location; - let Events; - let setup; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function (_$rootScope_, _$location_, Private, config) { - const State = Private(StateProvider); - $location = _$location_; - $rootScope = _$rootScope_; - Events = Private(EventsProvider); - - setup = (opts) => { - const { param, initial, storeInHash } = opts || {}; - sinon.stub(config, 'get').withArgs('state:storeInSessionStorage').returns(!!storeInHash); - const store = new StubBrowserStorage(); - const hashedItemStore = new HashedItemStore(store); - const state = new State(param, initial, hashedItemStore); - - const getUnhashedSearch = () => unhashQuery($location.search()); - - return { store, hashedItemStore, state, getUnhashedSearch }; - }; - }) - ); - - describe('Provider', () => { - it('should reset the state to the defaults', () => { - const { state, getUnhashedSearch } = setup({ initial: { message: ['test'] } }); - state.reset(); - const search = getUnhashedSearch(state); - expect(search).to.have.property('_s'); - expect(search._s).to.equal('(message:!(test))'); - expect(state.message).to.eql(['test']); - }); - - it('should apply the defaults upon initialization', () => { - const { state } = setup({ initial: { message: 'test' } }); - expect(state).to.have.property('message', 'test'); - }); - - it('should inherit from Events', () => { - const { state } = setup(); - expect(state).to.be.an(Events); - }); - - it('should emit an event if reset with changes', (done) => { - const { state } = setup({ initial: { message: ['test'] } }); - state.on('reset_with_changes', (keys) => { - expect(keys).to.eql(['message']); - done(); - }); - state.save(); - state.message = 'foo'; - state.reset(); - $rootScope.$apply(); - }); - - it('should not emit an event if reset without changes', () => { - const { state } = setup({ initial: { message: 'test' } }); - state.on('reset_with_changes', () => { - expect().fail(); - }); - state.save(); - state.message = 'test'; - state.reset(); - $rootScope.$apply(); - }); - }); - - describe('Search', () => { - it('should save to $location.search()', () => { - const { state, getUnhashedSearch } = setup({ initial: { test: 'foo' } }); - state.save(); - const search = getUnhashedSearch(state); - expect(search).to.have.property('_s'); - expect(search._s).to.equal('(test:foo)'); - }); - - it('should emit an event if changes are saved', (done) => { - const { state, getUnhashedSearch } = setup(); - state.on('save_with_changes', (keys) => { - expect(keys).to.eql(['test']); - done(); - }); - state.test = 'foo'; - state.save(); - getUnhashedSearch(state); - $rootScope.$apply(); - }); - }); - - describe('Fetch', () => { - it('should emit an event if changes are fetched', (done) => { - const { state } = setup(); - state.on('fetch_with_changes', (keys) => { - expect(keys).to.eql(['foo']); - done(); - }); - $location.search({ _s: '(foo:bar)' }); - state.fetch(); - expect(state).to.have.property('foo', 'bar'); - $rootScope.$apply(); - }); - - it('should have events that attach to scope', (done) => { - const { state } = setup(); - state.on('test', (message) => { - expect(message).to.equal('foo'); - done(); - }); - state.emit('test', 'foo'); - $rootScope.$apply(); - }); - - it('should fire listeners for #onUpdate() on #fetch()', (done) => { - const { state } = setup(); - state.on('fetch_with_changes', (keys) => { - expect(keys).to.eql(['foo']); - done(); - }); - $location.search({ _s: '(foo:bar)' }); - state.fetch(); - expect(state).to.have.property('foo', 'bar'); - $rootScope.$apply(); - }); - - it('should apply defaults to fetches', () => { - const { state } = setup({ initial: { message: 'test' } }); - $location.search({ _s: '(foo:bar)' }); - state.fetch(); - expect(state).to.have.property('foo', 'bar'); - expect(state).to.have.property('message', 'test'); - }); - - it('should call fetch when $routeUpdate is fired on $rootScope', () => { - const { state } = setup(); - const spy = sinon.spy(state, 'fetch'); - $rootScope.$emit('$routeUpdate', 'test'); - sinon.assert.calledOnce(spy); - }); - - it('should clear state when missing form URL', () => { - let stateObj; - const { state } = setup(); - - // set state via URL - $location.search({ _s: '(foo:(bar:baz))' }); - state.fetch(); - stateObj = state.toObject(); - expect(stateObj).to.eql({ foo: { bar: 'baz' } }); - - // ensure changing URL changes state - $location.search({ _s: '(one:two)' }); - state.fetch(); - stateObj = state.toObject(); - expect(stateObj).to.eql({ one: 'two' }); - - // remove search, state should be empty - $location.search({}); - state.fetch(); - stateObj = state.toObject(); - expect(stateObj).to.eql({}); - }); - - it('should clear state when it is invalid', () => { - let stateObj; - const { state } = setup(); - - $location.search({ _s: '' }); - state.fetch(); - stateObj = state.toObject(); - expect(stateObj).to.eql({}); - - $location.search({ _s: '!n' }); - state.fetch(); - stateObj = state.toObject(); - expect(stateObj).to.eql({}); - - $location.search({ _s: 'alert(1)' }); - state.fetch(); - stateObj = state.toObject(); - expect(stateObj).to.eql({}); - }); - - it('does not replace the state value on read', () => { - const { state } = setup(); - sinon.stub($location, 'search').callsFake((newSearch) => { - if (newSearch) { - return $location; - } else { - return { - [state.getQueryParamName()]: '(a:1)', - }; - } - }); - const replaceStub = sinon.stub($location, 'replace').returns($location); - - state.fetch(); - sinon.assert.notCalled(replaceStub); - }); - }); - - describe('Hashing', () => { - it('stores state values in a hashedItemStore, writing the hash to the url', () => { - const { state, hashedItemStore } = setup({ storeInHash: true }); - state.foo = 'bar'; - state.save(); - const urlVal = $location.search()[state.getQueryParamName()]; - - expect(isStateHash(urlVal)).to.be(true); - expect(hashedItemStore.getItem(urlVal)).to.eql(JSON.stringify({ foo: 'bar' })); - }); - - it('should replace rison in the URL with a hash', () => { - const { state, hashedItemStore } = setup({ storeInHash: true }); - const obj = { foo: { bar: 'baz' } }; - const rison = encodeRison(obj); - - $location.search({ _s: rison }); - state.fetch(); - - const urlVal = $location.search()._s; - expect(urlVal).to.not.be(rison); - expect(isStateHash(urlVal)).to.be(true); - expect(hashedItemStore.getItem(urlVal)).to.eql(JSON.stringify(obj)); - }); - - describe('error handling', () => { - it('notifies the user when a hash value does not map to a stored value', () => { - // Ideally, state.js shouldn't be tightly coupled to toastNotifications. Instead, it - // should notify its consumer of this error state and the consumer should be responsible - // for notifying the user of the error. This test verifies the side effect of the error - // until we can remove this coupling. - - // Clear existing toasts. - toastNotifications.list.splice(0); - - const { state } = setup({ storeInHash: true }); - const search = $location.search(); - const badHash = createStateHash('{"a": "b"}', () => null); - - search[state.getQueryParamName()] = badHash; - $location.search(search); - - expect(toastNotifications.list).to.have.length(0); - state.fetch(); - expect(toastNotifications.list).to.have.length(1); - expect(toastNotifications.list[0].title).to.match(/use the share functionality/i); - }); - - it.skip('triggers fatal error linking to github when setting item fails', () => { - // NOTE: this test needs to be written in jest and removed from the browser ones - // More info could be read in the opened issue: - // https://github.com/elastic/kibana/issues/22751 - const { state, hashedItemStore } = setup({ storeInHash: true }); - const fatalErrorStub = sandbox.stub(); - Object.defineProperty(FatalErrorNS, 'fatalError', { - writable: true, - value: fatalErrorStub, - }); - - sandbox.stub(hashedItemStore, 'setItem').returns(false); - state.toQueryParam(); - sinon.assert.calledOnce(fatalErrorStub); - sinon.assert.calledWith( - fatalErrorStub, - sinon.match((error) => error instanceof Error && error.message.includes('github.com')) - ); - }); - - it('translateHashToRison should gracefully fallback if parameter can not be parsed', () => { - const { state, hashedItemStore } = setup({ storeInHash: false }); - - expect(state.translateHashToRison('(a:b)')).to.be('(a:b)'); - expect(state.translateHashToRison('')).to.be(''); - - const existingHash = createStateHash('{"a": "b"}', () => null); - hashedItemStore.setItem(existingHash, '{"a": "b"}'); - - const nonExistingHash = createStateHash('{"b": "c"}', () => null); - - expect(state.translateHashToRison(existingHash)).to.be('(a:b)'); - expect(state.translateHashToRison(nonExistingHash)).to.be('!n'); - }); - }); - }); - }); - - describe('Disabled with persisted state', () => { - let state; - let $location; - let $rootScope; - const stateParam = '_config_test'; - - const getLocationState = () => { - const search = $location.search(); - return search[stateParam]; - }; - - beforeEach( - ngMock.module('kibana', function (stateManagementConfigProvider) { - stateManagementConfigProvider.disable(); - }) - ); - beforeEach( - ngMock.inject(function (_$rootScope_, _$location_, Private, config) { - const State = Private(StateProvider); - $location = _$location_; - $rootScope = _$rootScope_; - - sinon.stub(config, 'get').withArgs('state:storeInSessionStorage').returns(false); - - class MockPersistedState extends State { - _persistAcrossApps = true; - } - - MockPersistedState.prototype._persistAcrossApps = true; - - state = new MockPersistedState(stateParam); - }) - ); - - describe('changing state', () => { - const methods = ['save', 'replace', 'reset']; - - methods.forEach((method) => { - it(`${method} should not change the URL`, () => { - $location.search({ _s: '(foo:bar)' }); - state[method](); - $rootScope.$apply(); - expect(getLocationState()).to.be(undefined); - }); - }); - }); - - describe('reading state', () => { - it('should not change the URL', () => { - const saveSpy = sinon.spy(state, 'save'); - $location.search({ _s: '(foo:bar)' }); - state.fetch(); - $rootScope.$apply(); - sinon.assert.notCalled(saveSpy); - expect(getLocationState()).to.be(undefined); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js b/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js deleted file mode 100644 index dc00d4e05e82f..0000000000000 --- a/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { EventEmitter } from 'events'; -import { cloneDeep } from 'lodash'; -import { stateMonitorFactory } from '../state_monitor_factory'; - -describe('stateMonitorFactory', function () { - const noop = () => {}; - const eventTypes = ['save_with_changes', 'reset_with_changes', 'fetch_with_changes']; - - let mockState; - - function setState(mockState, obj, emit = true) { - mockState.toJSON = () => cloneDeep(obj); - if (emit) mockState.emit(eventTypes[0]); - } - - function createMockState(state = {}) { - const mockState = new EventEmitter(); - setState(mockState, state, false); - return mockState; - } - - beforeEach(() => { - mockState = createMockState({}); - }); - - it('should have a create method', function () { - expect(stateMonitorFactory).to.have.property('create'); - expect(stateMonitorFactory.create).to.be.a('function'); - }); - - describe('factory creation', function () { - it('should not call onChange with only the state', function () { - const monitor = stateMonitorFactory.create(mockState); - const changeStub = sinon.stub(); - monitor.onChange(changeStub); - sinon.assert.notCalled(changeStub); - }); - - it('should not call onChange with matching defaultState', function () { - const monitor = stateMonitorFactory.create(mockState, {}); - const changeStub = sinon.stub(); - monitor.onChange(changeStub); - sinon.assert.notCalled(changeStub); - }); - - it('should call onChange with differing defaultState', function () { - const monitor = stateMonitorFactory.create(mockState, { test: true }); - const changeStub = sinon.stub(); - monitor.onChange(changeStub); - sinon.assert.calledOnce(changeStub); - }); - }); - - describe('instance', function () { - let monitor; - - beforeEach(() => { - monitor = stateMonitorFactory.create(mockState); - }); - - describe('onChange', function () { - it('should throw if not given a handler function', function () { - const fn = () => monitor.onChange('not a function'); - expect(fn).to.throwException(/must be a function/); - }); - - eventTypes.forEach((eventType) => { - describe(`when ${eventType} is emitted`, function () { - let handlerFn; - - beforeEach(() => { - handlerFn = sinon.stub(); - monitor.onChange(handlerFn); - sinon.assert.notCalled(handlerFn); - }); - - it('should get called', function () { - mockState.emit(eventType); - sinon.assert.calledOnce(handlerFn); - }); - - it('should be given the state status', function () { - mockState.emit(eventType); - const args = handlerFn.firstCall.args; - expect(args[0]).to.be.an('object'); - }); - - it('should be given the event type', function () { - mockState.emit(eventType); - const args = handlerFn.firstCall.args; - expect(args[1]).to.equal(eventType); - }); - - it('should be given the changed keys', function () { - const keys = ['one', 'two', 'three']; - mockState.emit(eventType, keys); - const args = handlerFn.firstCall.args; - expect(args[2]).to.equal(keys); - }); - }); - }); - }); - - describe('ignoreProps', function () { - it('should not set status to dirty when ignored properties change', function () { - let status; - const mockState = createMockState({ messages: { world: 'hello', foo: 'bar' } }); - const monitor = stateMonitorFactory.create(mockState); - const changeStub = sinon.stub(); - monitor.ignoreProps('messages.world'); - monitor.onChange(changeStub); - sinon.assert.notCalled(changeStub); - - // update the ignored state prop - setState(mockState, { messages: { world: 'howdy', foo: 'bar' } }); - sinon.assert.calledOnce(changeStub); - status = changeStub.firstCall.args[0]; - expect(status).to.have.property('clean', true); - expect(status).to.have.property('dirty', false); - - // update a prop that is not ignored - setState(mockState, { messages: { world: 'howdy', foo: 'baz' } }); - sinon.assert.calledTwice(changeStub); - status = changeStub.secondCall.args[0]; - expect(status).to.have.property('clean', false); - expect(status).to.have.property('dirty', true); - }); - }); - - describe('setInitialState', function () { - let changeStub; - - beforeEach(() => { - changeStub = sinon.stub(); - monitor.onChange(changeStub); - sinon.assert.notCalled(changeStub); - }); - - it('should throw if no state is provided', function () { - const fn = () => monitor.setInitialState(); - expect(fn).to.throwException(/must be an object/); - }); - - it('should throw if given the wrong type', function () { - const fn = () => monitor.setInitialState([]); - expect(fn).to.throwException(/must be an object/); - }); - - it('should trigger the onChange handler', function () { - monitor.setInitialState({ new: 'state' }); - sinon.assert.calledOnce(changeStub); - }); - - it('should change the status with differing state', function () { - monitor.setInitialState({ new: 'state' }); - sinon.assert.calledOnce(changeStub); - - const status = changeStub.firstCall.args[0]; - expect(status).to.have.property('clean', false); - expect(status).to.have.property('dirty', true); - }); - - it('should not trigger the onChange handler without state change', function () { - monitor.setInitialState(cloneDeep(mockState.toJSON())); - sinon.assert.notCalled(changeStub); - }); - }); - - describe('status object', function () { - let handlerFn; - - beforeEach(() => { - handlerFn = sinon.stub(); - monitor.onChange(handlerFn); - }); - - it('should be clean by default', function () { - mockState.emit(eventTypes[0]); - const status = handlerFn.firstCall.args[0]; - expect(status).to.have.property('clean', true); - expect(status).to.have.property('dirty', false); - }); - - it('should be dirty when state changes', function () { - setState(mockState, { message: 'i am dirty now' }); - const status = handlerFn.firstCall.args[0]; - expect(status).to.have.property('clean', false); - expect(status).to.have.property('dirty', true); - }); - - it('should be clean when state is reset', function () { - const defaultState = { message: 'i am the original state' }; - const handlerFn = sinon.stub(); - - let status; - - // initial state and monitor setup - const mockState = createMockState(defaultState); - const monitor = stateMonitorFactory.create(mockState); - monitor.onChange(handlerFn); - sinon.assert.notCalled(handlerFn); - - // change the state and emit an event - setState(mockState, { message: 'i am dirty now' }); - sinon.assert.calledOnce(handlerFn); - status = handlerFn.firstCall.args[0]; - expect(status).to.have.property('clean', false); - expect(status).to.have.property('dirty', true); - - // reset the state and emit an event - setState(mockState, defaultState); - sinon.assert.calledTwice(handlerFn); - status = handlerFn.secondCall.args[0]; - expect(status).to.have.property('clean', true); - expect(status).to.have.property('dirty', false); - }); - }); - - describe('destroy', function () { - let stateSpy; - - beforeEach(() => { - stateSpy = sinon.spy(mockState, 'off'); - sinon.assert.notCalled(stateSpy); - }); - - it('should remove the listeners', function () { - monitor.onChange(noop); - monitor.destroy(); - sinon.assert.callCount(stateSpy, eventTypes.length); - eventTypes.forEach((eventType) => { - sinon.assert.calledWith(stateSpy, eventType); - }); - }); - - it('should stop the instance from being used any more', function () { - monitor.onChange(noop); - monitor.destroy(); - const fn = () => monitor.onChange(noop); - expect(fn).to.throwException(/has been destroyed/); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/app_state.d.ts b/src/legacy/ui/public/state_management/app_state.d.ts deleted file mode 100644 index ddd0b90710766..0000000000000 --- a/src/legacy/ui/public/state_management/app_state.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { State } from './state'; - -export class AppState extends State {} - -export type AppStateClass< - TAppState extends AppState = AppState, - TDefaults = { [key: string]: any } -> = new (defaults: TDefaults) => TAppState; diff --git a/src/legacy/ui/public/state_management/app_state.js b/src/legacy/ui/public/state_management/app_state.js deleted file mode 100644 index ec680d163b9da..0000000000000 --- a/src/legacy/ui/public/state_management/app_state.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * @name AppState - * - * @extends State - * - * @description Inherits State, which inherits Events. This class seems to be - * concerned with mapping "props" to PersistedState instances, and surfacing the - * ability to destroy those mappings. - */ - -import { uiModules } from '../modules'; -import { StateProvider } from './state'; -import { PersistedState } from '../../../../plugins/visualizations/public'; -import { createLegacyClass } from '../utils/legacy_class'; - -const urlParam = '_a'; - -export function AppStateProvider(Private, $location) { - const State = Private(StateProvider); - - let persistedStates; - let eventUnsubscribers; - - createLegacyClass(AppState).inherits(State); - function AppState(defaults) { - // Initialize persistedStates. This object maps "prop" names to - // PersistedState instances. These are used to make properties "stateful". - persistedStates = {}; - - // Initialize eventUnsubscribers. These will be called in `destroy`, to - // remove handlers for the 'change' and 'fetch_with_changes' events which - // are dispatched via the rootScope. - eventUnsubscribers = []; - - AppState.Super.call(this, urlParam, defaults); - AppState.getAppState._set(this); - } - - // if the url param is missing, write it back - AppState.prototype._persistAcrossApps = false; - - AppState.prototype.destroy = function () { - AppState.Super.prototype.destroy.call(this); - AppState.getAppState._set(null); - - eventUnsubscribers.forEach((listener) => listener()); - }; - - /** - * @returns PersistedState instance. - */ - AppState.prototype.makeStateful = function (prop) { - if (persistedStates[prop]) return persistedStates[prop]; - const self = this; - - // set up the ui state - persistedStates[prop] = new PersistedState(); - - // update the app state when the stateful instance changes - const updateOnChange = function () { - const replaceState = false; // TODO: debouncing logic - self[prop] = persistedStates[prop].getChanges(); - // Save state to the URL. - self.save(replaceState); - }; - const handlerOnChange = (method) => persistedStates[prop][method]('change', updateOnChange); - handlerOnChange('on'); - eventUnsubscribers.push(() => handlerOnChange('off')); - - // update the stateful object when the app state changes - const persistOnChange = function (changes) { - if (!changes) return; - - if (changes.indexOf(prop) !== -1) { - persistedStates[prop].set(self[prop]); - } - }; - const handlePersist = (method) => this[method]('fetch_with_changes', persistOnChange); - handlePersist('on'); - eventUnsubscribers.push(() => handlePersist('off')); - - // if the thing we're making stateful has an appState value, write to persisted state - if (self[prop]) persistedStates[prop].setSilent(self[prop]); - - return persistedStates[prop]; - }; - - AppState.getAppState = (function () { - let currentAppState; - - function get() { - return currentAppState; - } - - // Checks to see if the appState might already exist, even if it hasn't been newed up - get.previouslyStored = function () { - const search = $location.search(); - return search[urlParam] ? true : false; - }; - - get._set = function (current) { - currentAppState = current; - }; - - return get; - })(); - - return AppState; -} - -uiModules - .get('kibana/global_state') - .factory('AppState', function (Private) { - return Private(AppStateProvider); - }) - .service('getAppState', function (Private) { - return Private(AppStateProvider).getAppState; - }); diff --git a/src/legacy/ui/public/state_management/config_provider.js b/src/legacy/ui/public/state_management/config_provider.js deleted file mode 100644 index 68de449a57a56..0000000000000 --- a/src/legacy/ui/public/state_management/config_provider.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * @name stateManagementConfig - * - * @description Allows apps to configure state management - */ - -import { uiModules } from '../modules'; - -export class StateManagementConfigProvider { - _enabled = true; - - $get(/* inject stuff */) { - return { - enabled: this._enabled, - }; - } - - disable() { - this._enabled = false; - } - - enable() { - this._enabled = true; - } -} - -uiModules - .get('kibana/state_management') - .provider('stateManagementConfig', StateManagementConfigProvider); diff --git a/src/legacy/ui/public/state_management/global_state.d.ts b/src/legacy/ui/public/state_management/global_state.d.ts deleted file mode 100644 index 66a85d88956c7..0000000000000 --- a/src/legacy/ui/public/state_management/global_state.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { State } from './state'; - -export type GlobalState = State; diff --git a/src/legacy/ui/public/state_management/global_state.js b/src/legacy/ui/public/state_management/global_state.js deleted file mode 100644 index 0e8dfe40d5950..0000000000000 --- a/src/legacy/ui/public/state_management/global_state.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { StateProvider } from './state'; -import { uiModules } from '../modules'; -import { createLegacyClass } from '../utils/legacy_class'; - -const module = uiModules.get('kibana/global_state'); - -export function GlobalStateProvider(Private) { - const State = Private(StateProvider); - - createLegacyClass(GlobalState).inherits(State); - function GlobalState(defaults) { - GlobalState.Super.call(this, '_g', defaults); - } - - // if the url param is missing, write it back - GlobalState.prototype._persistAcrossApps = true; - - return new GlobalState(); -} - -module.service('globalState', function (Private) { - return Private(GlobalStateProvider); -}); diff --git a/src/legacy/ui/public/state_management/state.d.ts b/src/legacy/ui/public/state_management/state.d.ts deleted file mode 100644 index b31862b681f55..0000000000000 --- a/src/legacy/ui/public/state_management/state.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export class State { - [key: string]: any; - translateHashToRison: (stateHashOrRison: string | string[]) => string | string[]; - getQueryParamName: () => string; -} diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js deleted file mode 100644 index d91834adb4a79..0000000000000 --- a/src/legacy/ui/public/state_management/state.js +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * @name State - * - * @extends Events - * - * @description Persists generic "state" to and reads it from the URL. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import angular from 'angular'; -import rison from 'rison-node'; -import { EventsProvider } from '../events'; -import { fatalError, toastNotifications } from '../notify'; -import './config_provider'; -import { createLegacyClass } from '../utils/legacy_class'; -import { - hashedItemStore, - isStateHash, - createStateHash, - applyDiff, -} from '../../../../plugins/kibana_utils/public'; - -export function StateProvider( - Private, - $rootScope, - $location, - stateManagementConfig, - config, - kbnUrl, - $injector -) { - const Events = Private(EventsProvider); - - const isDummyRoute = () => - $injector.has('$route') && - $injector.get('$route').current && - $injector.get('$route').current.outerAngularWrapperRoute; - - createLegacyClass(State).inherits(Events); - function State(urlParam, defaults, _hashedItemStore = hashedItemStore) { - State.Super.call(this); - - this.setDefaults(defaults); - this._urlParam = urlParam || '_s'; - this._hashedItemStore = _hashedItemStore; - - // When the URL updates we need to fetch the values from the URL - this._cleanUpListeners = [ - // partial route update, no app reload - $rootScope.$on('$routeUpdate', () => { - this.fetch(); - }), - - // beginning of full route update, new app will be initialized before - // $routeChangeSuccess or $routeChangeError - $rootScope.$on('$routeChangeStart', () => { - if (!this._persistAcrossApps) { - this.destroy(); - } - }), - - $rootScope.$on('$routeChangeSuccess', () => { - if (this._persistAcrossApps) { - this.fetch(); - } - }), - ]; - - // Initialize the State with fetch - this.fetch(); - } - - State.prototype._readFromURL = function () { - const search = $location.search(); - const urlVal = search[this._urlParam]; - - if (!urlVal) { - return null; - } - - if (isStateHash(urlVal)) { - return this._parseStateHash(urlVal); - } - - let risonEncoded; - let unableToParse; - try { - risonEncoded = rison.decode(urlVal); - } catch (e) { - unableToParse = true; - } - - if (unableToParse) { - toastNotifications.addDanger( - i18n.translate('common.ui.stateManagement.unableToParseUrlErrorMessage', { - defaultMessage: 'Unable to parse URL', - }) - ); - search[this._urlParam] = this.toQueryParam(this._defaults); - $location.search(search).replace(); - } - - if (!risonEncoded) { - return null; - } - - if (this.isHashingEnabled()) { - // RISON can find its way into the URL any number of ways, including the navbar links or - // shared urls with the entire state embedded. These values need to be translated into - // hashes and replaced in the browser history when state-hashing is enabled - search[this._urlParam] = this.toQueryParam(risonEncoded); - $location.search(search).replace(); - } - - return risonEncoded; - }; - - /** - * Fetches the state from the url - * @returns {void} - */ - State.prototype.fetch = function () { - if (!stateManagementConfig.enabled) { - return; - } - - let stash = this._readFromURL(); - - // nothing to read from the url? save if ordered to persist, but only if it's not on a wrapper route - if (stash === null) { - if (this._persistAcrossApps) { - return this.save(); - } else { - stash = {}; - } - } - - _.defaults(stash, this._defaults); - // apply diff to state from stash, will change state in place via side effect - const diffResults = applyDiff(this, stash); - - if (!isDummyRoute() && diffResults.keys.length) { - this.emit('fetch_with_changes', diffResults.keys); - } - }; - - /** - * Saves the state to the url - * @returns {void} - */ - State.prototype.save = function (replace) { - if (!stateManagementConfig.enabled) { - return; - } - - if (isDummyRoute()) { - return; - } - - let stash = this._readFromURL(); - const state = this.toObject(); - replace = replace || false; - - if (!stash) { - replace = true; - stash = {}; - } - - // apply diff to state from stash, will change state in place via side effect - const diffResults = applyDiff(stash, _.defaults({}, state, this._defaults)); - - if (diffResults.keys.length) { - this.emit('save_with_changes', diffResults.keys); - } - - // persist the state in the URL - const search = $location.search(); - search[this._urlParam] = this.toQueryParam(state); - if (replace) { - $location.search(search).replace(); - } else { - $location.search(search); - } - }; - - /** - * Calls save with a forced replace - * @returns {void} - */ - State.prototype.replace = function () { - if (!stateManagementConfig.enabled) { - return; - } - - this.save(true); - }; - - /** - * Resets the state to the defaults - * - * @returns {void} - */ - State.prototype.reset = function () { - if (!stateManagementConfig.enabled) { - return; - } - - kbnUrl.removeParam(this.getQueryParamName()); - // apply diff to attributes from defaults, this is side effecting so - // it will change the state in place. - const diffResults = applyDiff(this, this._defaults); - if (diffResults.keys.length) { - this.emit('reset_with_changes', diffResults.keys); - } - this.save(); - }; - - /** - * Cleans up the state object - * @returns {void} - */ - State.prototype.destroy = function () { - this.off(); // removes all listeners - - // Removes the $routeUpdate listener - this._cleanUpListeners.forEach((listener) => listener(this)); - }; - - State.prototype.setDefaults = function (defaults) { - this._defaults = defaults || {}; - }; - - /** - * Parse the state hash to it's unserialized value. Hashes are restored - * to their pre-hashed state. - * - * @param {string} stateHash - state hash value from the query string. - * @return {any} - the stored value, or null if hash does not resolve. - */ - State.prototype._parseStateHash = function (stateHash) { - const json = this._hashedItemStore.getItem(stateHash); - if (json === null) { - toastNotifications.addDanger( - i18n.translate('common.ui.stateManagement.unableToRestoreUrlErrorMessage', { - defaultMessage: - 'Unable to completely restore the URL, be sure to use the share functionality.', - }) - ); - } - - return JSON.parse(json); - }; - - /** - * Lookup the value for a hash and return it's value in rison format or just - * return passed argument if it's not recognized as state hash. - * - * @param {string} stateHashOrRison - either state hash value or rison string. - * @return {string} rison - */ - State.prototype.translateHashToRison = function (stateHashOrRison) { - if (isStateHash(stateHashOrRison)) { - return rison.encode(this._parseStateHash(stateHashOrRison)); - } - - return stateHashOrRison; - }; - - State.prototype.isHashingEnabled = function () { - return !!config.get('state:storeInSessionStorage'); - }; - - /** - * Produce the hash version of the state in it's current position - * - * @return {string} - */ - State.prototype.toQueryParam = function (state = this.toObject()) { - if (!this.isHashingEnabled()) { - return rison.encode(state); - } - - // We need to strip out Angular-specific properties. - const json = angular.toJson(state); - const hash = createStateHash(json); - const isItemSet = this._hashedItemStore.setItem(hash, json); - - if (isItemSet) { - return hash; - } - - // If we ran out of space trying to persist the state, notify the user. - const message = i18n.translate( - 'common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage', - { - defaultMessage: - 'Kibana is unable to store history items in your session ' + - `because it is full and there don't seem to be items any items safe ` + - 'to delete.\n\n' + - 'This can usually be fixed by moving to a fresh tab, but could ' + - 'be caused by a larger issue. If you are seeing this message regularly, ' + - 'please file an issue at {gitHubIssuesUrl}.', - values: { gitHubIssuesUrl: 'https://github.com/elastic/kibana/issues' }, - } - ); - fatalError(new Error(message)); - }; - - /** - * Get the query string parameter name where this state writes and reads - * @return {string} - */ - State.prototype.getQueryParamName = function () { - return this._urlParam; - }; - - /** - * Returns an object with each property name and value corresponding to the entries in this collection - * excluding fields started from '$', '_' and all methods - * - * @return {object} - */ - State.prototype.toObject = function () { - return _.omitBy(this, (value, key) => { - return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); - }); - }; - - /** Alias for method 'toObject' - * - * @obsolete Please use 'toObject' method instead - * @return {object} - */ - State.prototype.toJSON = function () { - return this.toObject(); - }; - - return State; -} diff --git a/src/legacy/ui/public/state_management/state_monitor_factory.ts b/src/legacy/ui/public/state_management/state_monitor_factory.ts deleted file mode 100644 index 968ececfe3be5..0000000000000 --- a/src/legacy/ui/public/state_management/state_monitor_factory.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { set } from '@elastic/safer-lodash-set'; -import { cloneDeep, isEqual, isPlainObject } from 'lodash'; -import { State } from './state'; - -export const stateMonitorFactory = { - create: ( - state: State, - customInitialState: TStateDefault - ) => stateMonitor(state, customInitialState), -}; - -interface StateStatus { - clean: boolean; - dirty: boolean; -} - -export interface StateMonitor { - setInitialState: (innerCustomInitialState: TStateDefault) => void; - ignoreProps: (props: string[] | string) => void; - onChange: (callback: ChangeHandlerFn) => StateMonitor; - destroy: () => void; -} - -type ChangeHandlerFn = (status: StateStatus, type: string | null, keys: string[]) => void; - -function stateMonitor( - state: State, - customInitialState: TStateDefault -): StateMonitor { - let destroyed = false; - let ignoredProps: string[] = []; - let changeHandlers: ChangeHandlerFn[] | undefined = []; - let initialState: TStateDefault; - - setInitialState(customInitialState); - - function setInitialState(innerCustomInitialState: TStateDefault) { - // state.toJSON returns a reference, clone so we can mutate it safely - initialState = cloneDeep(innerCustomInitialState) || cloneDeep(state.toJSON()); - } - - function removeIgnoredProps(innerState: TStateDefault) { - ignoredProps.forEach((path) => { - set(innerState, path, true); - }); - return innerState; - } - - function getStatus(): StateStatus { - // state.toJSON returns a reference, clone so we can mutate it safely - const currentState = removeIgnoredProps(cloneDeep(state.toJSON())); - const isClean = isEqual(currentState, initialState); - - return { - clean: isClean, - dirty: !isClean, - }; - } - - function dispatchChange(type: string | null = null, keys: string[] = []) { - const status = getStatus(); - if (!changeHandlers) { - throw new Error('Change handlers is undefined, this object has been destroyed'); - } - changeHandlers.forEach((changeHandler) => { - changeHandler(status, type, keys); - }); - } - - function dispatchFetch(keys: string[]) { - dispatchChange('fetch_with_changes', keys); - } - - function dispatchSave(keys: string[]) { - dispatchChange('save_with_changes', keys); - } - - function dispatchReset(keys: string[]) { - dispatchChange('reset_with_changes', keys); - } - - return { - setInitialState(innerCustomInitialState: TStateDefault) { - if (!isPlainObject(innerCustomInitialState)) { - throw new TypeError('The default state must be an object'); - } - - // check the current status - const previousStatus = getStatus(); - - // update the initialState and apply ignoredProps - setInitialState(innerCustomInitialState); - removeIgnoredProps(initialState); - - // fire the change handler if the status has changed - if (!isEqual(previousStatus, getStatus())) { - dispatchChange(); - } - }, - - ignoreProps(props: string[] | string) { - ignoredProps = ignoredProps.concat(props); - removeIgnoredProps(initialState); - return this; - }, - - onChange(callback: ChangeHandlerFn) { - if (destroyed || !changeHandlers) { - throw new Error('Monitor has been destroyed'); - } - if (typeof callback !== 'function') { - throw new Error('onChange handler must be a function'); - } - - changeHandlers.push(callback); - - // Listen for state events. - state.on('fetch_with_changes', dispatchFetch); - state.on('save_with_changes', dispatchSave); - state.on('reset_with_changes', dispatchReset); - - // if the state is already dirty, fire the change handler immediately - const status = getStatus(); - if (status.dirty) { - dispatchChange(); - } - - return this; - }, - - destroy() { - destroyed = true; - changeHandlers = undefined; - state.off('fetch_with_changes', dispatchFetch); - state.off('save_with_changes', dispatchSave); - state.off('reset_with_changes', dispatchReset); - }, - }; -} diff --git a/src/legacy/ui/public/system_api/__tests__/system_api.js b/src/legacy/ui/public/system_api/__tests__/system_api.js deleted file mode 100644 index 816024f13f8b2..0000000000000 --- a/src/legacy/ui/public/system_api/__tests__/system_api.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { - addSystemApiHeader, - isSystemApiRequest, -} from '../../../../../plugins/kibana_legacy/public'; - -describe('system_api', () => { - describe('#addSystemApiHeader', () => { - it('adds the correct system API header', () => { - const headers = { - 'kbn-version': '4.6.0', - }; - const newHeaders = addSystemApiHeader(headers); - - expect(newHeaders).to.have.property('kbn-system-request'); - expect(newHeaders['kbn-system-request']).to.be(true); - - expect(newHeaders).to.have.property('kbn-version'); - expect(newHeaders['kbn-version']).to.be('4.6.0'); - }); - }); - - describe('#isSystemApiRequest', () => { - it('returns true for a system HTTP request', () => { - const mockRequest = { - headers: { - 'kbn-system-request': true, - }, - }; - expect(isSystemApiRequest(mockRequest)).to.be(true); - }); - - it('returns true for a legacy system API HTTP request', () => { - const mockRequest = { - headers: { - 'kbn-system-api': true, - }, - }; - expect(isSystemApiRequest(mockRequest)).to.be(true); - }); - - it('returns false for a non-system API HTTP request', () => { - const mockRequest = { - headers: {}, - }; - expect(isSystemApiRequest(mockRequest)).to.be(false); - }); - }); -}); diff --git a/src/legacy/ui/public/system_api/index.js b/src/legacy/ui/public/system_api/index.js deleted file mode 100644 index 6361c0ea6c69a..0000000000000 --- a/src/legacy/ui/public/system_api/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { addSystemApiHeader, isSystemApiRequest } from '../../../../plugins/kibana_legacy/public'; diff --git a/src/legacy/ui/public/timefilter/index.ts b/src/legacy/ui/public/timefilter/index.ts deleted file mode 100644 index 83795c73112be..0000000000000 --- a/src/legacy/ui/public/timefilter/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import uiRoutes from 'ui/routes'; -import { npStart } from 'ui/new_platform'; - -import { TimefilterContract, TimeHistoryContract } from '../../../../plugins/data/public'; -import { registerTimefilterWithGlobalState } from './setup_router'; - -export { - getTime, - InputTimeRange, - TimeHistoryContract, - TimefilterContract, -} from '../../../../plugins/data/public'; -export type Timefilter = TimefilterContract; -export type TimeHistory = TimeHistoryContract; -export const timeHistory = npStart.plugins.data.query.timefilter.history; -export const timefilter = npStart.plugins.data.query.timefilter.timefilter; - -uiRoutes.addSetupWork((globalState, $rootScope) => { - return registerTimefilterWithGlobalState(timefilter, globalState, $rootScope); -}); diff --git a/src/legacy/ui/public/timefilter/setup_router.test.js b/src/legacy/ui/public/timefilter/setup_router.test.js deleted file mode 100644 index 2ae9a053ae2db..0000000000000 --- a/src/legacy/ui/public/timefilter/setup_router.test.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { registerTimefilterWithGlobalState } from './setup_router'; - -jest.mock('../../../../plugins/kibana_legacy/public', () => ({ - subscribeWithScope: jest.fn(), -})); - -describe('registerTimefilterWithGlobalState()', () => { - it('should always use iso8601 strings', async () => { - const setTime = jest.fn(); - const timefilter = { - setTime, - setRefreshInterval: jest.fn(), - getRefreshIntervalUpdate$: jest.fn(), - getTimeUpdate$: jest.fn(), - }; - - const globalState = { - time: { - from: '2017-09-07T20:12:04.011Z', - to: '2017-09-07T20:18:55.733Z', - }, - on: (eventName, callback) => { - callback(); - }, - }; - - const rootScope = { - $on: jest.fn(), - }; - - registerTimefilterWithGlobalState(timefilter, globalState, rootScope); - - expect(setTime.mock.calls.length).toBe(2); - expect(setTime.mock.calls[1][0]).toEqual(globalState.time); - }); -}); diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts deleted file mode 100644 index 7c25c6aa3166e..0000000000000 --- a/src/legacy/ui/public/timefilter/setup_router.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { IScope } from 'angular'; -import moment from 'moment'; -import chrome from 'ui/chrome'; -import { Subscription } from 'rxjs'; -import { fatalError } from 'ui/notify/fatal_error'; -import { subscribeWithScope } from '../../../../plugins/kibana_legacy/public'; -import { - RefreshInterval, - TimeRange, - TimefilterContract, - UI_SETTINGS, -} from '../../../../plugins/data/public'; - -// TODO -// remove everything underneath once globalState is no longer an angular service -// and listener can be registered without angular. -function convertISO8601(stringTime: string): string { - const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true); - return obj.isValid() ? obj.toISOString() : stringTime; -} - -export function getTimefilterConfig() { - const settings = chrome.getUiSettingsClient(); - return { - timeDefaults: settings.get('timepicker:timeDefaults'), - refreshIntervalDefaults: settings.get(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS), - }; -} - -export const registerTimefilterWithGlobalStateFactory = ( - timefilter: TimefilterContract, - globalState: any, - $rootScope: IScope -) => { - // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. - const config = getTimefilterConfig(); - timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); - timefilter.setRefreshInterval( - _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) - ); - - globalState.on('fetch_with_changes', () => { - // clone and default to {} in one - const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); - const newRefreshInterval: RefreshInterval = _.defaults( - {}, - globalState.refreshInterval, - config.refreshIntervalDefaults - ); - - if (newTime) { - if (newTime.to) newTime.to = convertISO8601(newTime.to); - if (newTime.from) newTime.from = convertISO8601(newTime.from); - } - - timefilter.setTime(newTime); - timefilter.setRefreshInterval(newRefreshInterval); - }); - - const updateGlobalStateWithTime = () => { - globalState.time = timefilter.getTime(); - globalState.refreshInterval = timefilter.getRefreshInterval(); - globalState.save(); - }; - - const subscriptions = new Subscription(); - subscriptions.add( - subscribeWithScope( - $rootScope, - timefilter.getRefreshIntervalUpdate$(), - { - next: updateGlobalStateWithTime, - }, - fatalError - ) - ); - - subscriptions.add( - subscribeWithScope( - $rootScope, - timefilter.getTimeUpdate$(), - { - next: updateGlobalStateWithTime, - }, - fatalError - ) - ); - - $rootScope.$on('$destroy', () => { - subscriptions.unsubscribe(); - }); -}; - -// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter -// and require it to be executed to properly function. -// This function is exposed for applications that do not use uiRoutes like APM -// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter -export const registerTimefilterWithGlobalState = _.once(registerTimefilterWithGlobalStateFactory); diff --git a/src/legacy/ui/public/url/__tests__/extract_app_path_and_id.js b/src/legacy/ui/public/url/__tests__/extract_app_path_and_id.js deleted file mode 100644 index 965e8f4bc9f38..0000000000000 --- a/src/legacy/ui/public/url/__tests__/extract_app_path_and_id.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { extractAppPathAndId } from '../extract_app_path_and_id'; - -describe('extractAppPathAndId', function () { - describe('from an absolute url with a base path', function () { - describe('with a base path', function () { - const basePath = '/gza'; - const absoluteUrl = 'http://www.test.com:5601/gza/app/appId#appPathIsHere?query=here'; - it('extracts app path', function () { - expect(extractAppPathAndId(absoluteUrl, basePath).appPath).to.be( - 'appPathIsHere?query=here' - ); - }); - - it('extracts app id', function () { - expect(extractAppPathAndId(absoluteUrl, basePath).appId).to.be('appId'); - }); - - it('returns an empty object when there is no app path', function () { - const appPathAndId = extractAppPathAndId('http://www.test.com:5601/gza/noapppath'); - expect(appPathAndId.appId).to.be(undefined); - expect(appPathAndId.appPath).to.be(undefined); - }); - }); - - describe('without a base path', function () { - const absoluteUrl = 'http://www.test.com:5601/app/appId#appPathIsHere?query=here'; - it('extracts app path', function () { - expect(extractAppPathAndId(absoluteUrl).appPath).to.be('appPathIsHere?query=here'); - }); - - it('extracts app id', function () { - expect(extractAppPathAndId(absoluteUrl).appId).to.be('appId'); - }); - - it('returns an empty object when there is no app path', function () { - const appPathAndId = extractAppPathAndId('http://www.test.com:5601/noapppath'); - expect(appPathAndId.appId).to.be(undefined); - expect(appPathAndId.appPath).to.be(undefined); - }); - }); - - describe('when appPath is empty', function () { - const absoluteUrl = 'http://www.test.com:5601/app/appId'; - it('extracts app id', function () { - expect(extractAppPathAndId(absoluteUrl).appId).to.be('appId'); - }); - it('extracts empty appPath', function () { - expect(extractAppPathAndId(absoluteUrl).appPath).to.be(''); - }); - }); - }); - - describe('from a root relative url', function () { - describe('with a base path', function () { - const basePath = '/gza'; - const rootRelativePath = '/gza/app/appId#appPathIsHere?query=here'; - it('extracts app path', function () { - expect(extractAppPathAndId(rootRelativePath, basePath).appPath).to.be( - 'appPathIsHere?query=here' - ); - }); - - it('extracts app id', function () { - expect(extractAppPathAndId(rootRelativePath, basePath).appId).to.be('appId'); - }); - - it('returns an empty object when there is no app path', function () { - const appPathAndId = extractAppPathAndId('/gza/notformattedright'); - expect(appPathAndId.appId).to.be(undefined); - expect(appPathAndId.appPath).to.be(undefined); - }); - }); - - describe('without a base path', function () { - const rootRelativePath = '/app/appId#appPathIsHere?query=here'; - it('extracts app path', function () { - expect(extractAppPathAndId(rootRelativePath).appPath).to.be('appPathIsHere?query=here'); - }); - - it('extracts app id', function () { - expect(extractAppPathAndId(rootRelativePath).appId).to.be('appId'); - }); - - it('returns an empty object when there is no app path', function () { - const appPathAndId = extractAppPathAndId('/notformattedright'); - expect(appPathAndId.appId).to.be(undefined); - expect(appPathAndId.appPath).to.be(undefined); - }); - }); - - describe('when appPath is empty', function () { - const rootRelativePath = '/app/appId'; - it('extracts app id', function () { - expect(extractAppPathAndId(rootRelativePath).appId).to.be('appId'); - }); - it('extracts empty appPath', function () { - expect(extractAppPathAndId(rootRelativePath).appPath).to.be(''); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/url/__tests__/kibana_parsed_url.js b/src/legacy/ui/public/url/__tests__/kibana_parsed_url.js deleted file mode 100644 index 6ea199c3d22cc..0000000000000 --- a/src/legacy/ui/public/url/__tests__/kibana_parsed_url.js +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { KibanaParsedUrl } from '../kibana_parsed_url'; - -describe('KibanaParsedUrl', function () { - it('getHashedAppPath', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - basePath: '/hi', - appId: 'bye', - appPath: 'visualize?hi=there&bye', - }); - expect(kibanaParsedUrl.getHashedAppPath()).to.be('#visualize?hi=there&bye'); - }); - - it('getAppRootPath', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - basePath: '/hi', - appId: 'appId', - appPath: 'dashboard?edit=123', - }); - expect(kibanaParsedUrl.getAppRootPath()).to.be('/app/appId#dashboard?edit=123'); - }); - - describe('when basePath is specified', function () { - it('getRootRelativePath', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - basePath: '/base', - appId: 'appId', - appPath: 'visualize?hi=there&bye', - }); - expect(kibanaParsedUrl.getRootRelativePath()).to.be('/base/app/appId#visualize?hi=there&bye'); - }); - - describe('getAbsolutePath', function () { - const protocol = 'http'; - const hostname = 'www.test.com'; - const port = '5601'; - - it('returns the absolute url when there is a port', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - basePath: '/base', - appId: 'appId', - appPath: 'visualize?hi=there&bye', - hostname, - protocol, - port, - }); - expect(kibanaParsedUrl.getAbsoluteUrl()).to.be( - 'http://www.test.com:5601/base/app/appId#visualize?hi=there&bye' - ); - }); - - it('returns the absolute url when there are no query parameters', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - basePath: '/base', - appId: 'appId', - appPath: 'visualize', - hostname, - protocol, - }); - expect(kibanaParsedUrl.getAbsoluteUrl()).to.be( - 'http://www.test.com/base/app/appId#visualize' - ); - }); - - it('returns the absolute url when the are query parameters', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - basePath: '/base', - appId: 'appId', - appPath: 'visualize?hi=bye&tata', - hostname, - protocol, - }); - expect(kibanaParsedUrl.getAbsoluteUrl()).to.be( - 'http://www.test.com/base/app/appId#visualize?hi=bye&tata' - ); - }); - }); - }); - - describe('when basePath is not specified', function () { - it('getRootRelativePath', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - appId: 'appId', - appPath: 'visualize?hi=there&bye', - }); - expect(kibanaParsedUrl.getRootRelativePath()).to.be('/app/appId#visualize?hi=there&bye'); - }); - - describe('getAbsolutePath', function () { - const protocol = 'http'; - const hostname = 'www.test.com'; - const port = '5601'; - - it('returns the absolute url when there is a port', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - appId: 'appId', - appPath: 'visualize?hi=there&bye', - hostname, - protocol, - port, - }); - expect(kibanaParsedUrl.getAbsoluteUrl()).to.be( - 'http://www.test.com:5601/app/appId#visualize?hi=there&bye' - ); - }); - - it('returns the absolute url when there are no query parameters', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - appId: 'appId', - appPath: 'visualize', - hostname, - protocol, - }); - expect(kibanaParsedUrl.getAbsoluteUrl()).to.be('http://www.test.com/app/appId#visualize'); - }); - - it('returns the absolute url when there are query parameters', function () { - const kibanaParsedUrl = new KibanaParsedUrl({ - appId: 'appId', - appPath: 'visualize?hi=bye&tata', - hostname, - protocol, - }); - expect(kibanaParsedUrl.getAbsoluteUrl()).to.be( - 'http://www.test.com/app/appId#visualize?hi=bye&tata' - ); - }); - }); - }); - - describe('getGlobalState', function () { - const basePath = '/xyz'; - const appId = 'myApp'; - - it('returns an empty string when the KibanaParsedUrl is in an invalid state', function () { - const url = new KibanaParsedUrl({ basePath }); - expect(url.getGlobalState()).to.be(''); - }); - - it('returns an empty string when there is no global state', function () { - const url = new KibanaParsedUrl({ basePath, appId, appPath: '/hi?notg=something' }); - expect(url.getGlobalState()).to.be(''); - }); - - it('returns the global state when it is the last parameter', function () { - const url = new KibanaParsedUrl({ - basePath, - appId, - appPath: '/hi?notg=something&_g=(thisismyglobalstate)', - }); - expect(url.getGlobalState()).to.be('(thisismyglobalstate)'); - }); - - it('returns the global state when it is the first parameter', function () { - const url = new KibanaParsedUrl({ - basePath, - appId, - appPath: '/hi?_g=(thisismyglobalstate)&hi=bye', - }); - expect(url.getGlobalState()).to.be('(thisismyglobalstate)'); - }); - }); - - describe('setGlobalState', function () { - const basePath = '/xyz'; - const appId = 'myApp'; - - it('does nothing when KibanaParsedUrl is in an invalid state', function () { - const url = new KibanaParsedUrl({ basePath }); - url.setGlobalState('newglobalstate'); - expect(url.getGlobalState()).to.be(''); - }); - - it('clears the global state when setting it to an empty string', function () { - const url = new KibanaParsedUrl({ basePath, appId, appPath: '/hi?_g=globalstate' }); - url.setGlobalState(''); - expect(url.getGlobalState()).to.be(''); - }); - - it('updates the global state when a string is passed in', function () { - const url = new KibanaParsedUrl({ - basePath, - appId, - appPath: '/hi?notg=something&_g=oldstate', - }); - url.setGlobalState('newstate'); - expect(url.getGlobalState()).to.be('newstate'); - }); - - it('adds the global state parameters if it did not exist before', function () { - const url = new KibanaParsedUrl({ basePath, appId, appPath: '/hi' }); - url.setGlobalState('newstate'); - expect(url.getGlobalState()).to.be('newstate'); - expect(url.appPath).to.be('/hi?_g=newstate'); - }); - }); -}); diff --git a/src/legacy/ui/public/url/__tests__/prepend_path.js b/src/legacy/ui/public/url/__tests__/prepend_path.js deleted file mode 100644 index 36991b77553e4..0000000000000 --- a/src/legacy/ui/public/url/__tests__/prepend_path.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { prependPath } from '../prepend_path'; - -describe('url prependPath', function () { - describe('returns the relative path unchanged', function () { - it('if it is null', function () { - expect(prependPath(null, 'kittens')).to.be(null); - }); - - it('if it is undefined', function () { - expect(prependPath(undefined, 'kittens')).to.be(undefined); - }); - - it('if it is an absolute url', function () { - expect(prependPath('http://www.hithere.com/howareyou', 'welcome')).to.be( - 'http://www.hithere.com/howareyou' - ); - }); - - it('if it does not start with a /', function () { - expect(prependPath('are/so/cool', 'cats')).to.be('are/so/cool'); - }); - - it('when new path is empty', function () { - expect(prependPath('/are/so/cool', '')).to.be('/are/so/cool'); - }); - - it('when it is only a slash and new path is empty', function () { - expect(prependPath('/', '')).to.be('/'); - }); - }); - - describe('returns an updated relative path', function () { - it('when it starts with a slash', function () { - expect(prependPath('/are/so/cool', 'dinosaurs')).to.be('dinosaurs/are/so/cool'); - }); - - it('when new path starts with a slash', function () { - expect(prependPath('/are/so/cool', '/fish')).to.be('/fish/are/so/cool'); - }); - - it('with two slashes if new path is a slash', function () { - expect(prependPath('/are/so/cool', '/')).to.be('//are/so/cool'); - }); - - it('when there is a slash on the end', function () { - expect(prependPath('/are/delicious/', 'lollipops')).to.be('lollipops/are/delicious/'); - }); - - it('when pathname that ends with a file', function () { - expect(prependPath('/are/delicious/index.html', 'donuts')).to.be( - 'donuts/are/delicious/index.html' - ); - }); - - it('when it is only a slash', function () { - expect(prependPath('/', 'kittens')).to.be('kittens/'); - }); - }); -}); diff --git a/src/legacy/ui/public/url/__tests__/url.js b/src/legacy/ui/public/url/__tests__/url.js deleted file mode 100644 index 8b173482e1bb4..0000000000000 --- a/src/legacy/ui/public/url/__tests__/url.js +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import faker from 'faker'; -import _ from 'lodash'; -import { AppStateProvider } from '../../state_management/app_state'; -import '..'; - -// global vars, injected and mocked in init() -let kbnUrl; -let $route; -let $location; -let $rootScope; -let appState; - -class StubAppState { - constructor() { - this.getQueryParamName = () => '_a'; - this.toQueryParam = () => 'stateQueryParam'; - this.destroy = sinon.stub(); - } -} - -function init() { - ngMock.module('kibana/url', 'kibana', function ($provide, PrivateProvider) { - $provide.service('$route', function () { - return { - reload: _.noop, - }; - }); - - appState = new StubAppState(); - PrivateProvider.swap(AppStateProvider, ($decorate) => { - const AppState = $decorate(); - AppState.getAppState = () => appState; - return AppState; - }); - }); - - ngMock.inject(function ($injector) { - $route = $injector.get('$route'); - $location = $injector.get('$location'); - $rootScope = $injector.get('$rootScope'); - kbnUrl = $injector.get('kbnUrl'); - }); -} - -describe('kbnUrl', function () { - beforeEach(function () { - init(); - }); - - describe('forcing reload', function () { - it('schedules a listener for $locationChangeSuccess on the $rootScope', function () { - $location.url('/url'); - $route.current = { - $$route: { - regex: /.*/, - }, - }; - - sinon.stub($rootScope, '$on'); - - expect($rootScope.$on.callCount).to.be(0); - kbnUrl.change('/url'); - sinon.assert.calledOnce(appState.destroy); - expect($rootScope.$on.callCount).to.be(1); - expect($rootScope.$on.firstCall.args[0]).to.be('$locationChangeSuccess'); - }); - - it('handler unbinds the listener and calls reload', function () { - $location.url('/url'); - $route.current = { - $$route: { - regex: /.*/, - }, - }; - - const unbind = sinon.stub(); - sinon.stub($rootScope, '$on').returns(unbind); - $route.reload = sinon.stub(); - - expect($rootScope.$on.callCount).to.be(0); - kbnUrl.change('/url'); - expect($rootScope.$on.callCount).to.be(1); - - const handler = $rootScope.$on.firstCall.args[1]; - handler(); - expect(unbind.callCount).to.be(1); - expect($route.reload.callCount).to.be(1); - }); - - it('reloads requested before the first are ignored', function () { - $location.url('/url'); - $route.current = { - $$route: { - regex: /.*/, - }, - }; - $route.reload = sinon.stub(); - - sinon.stub($rootScope, '$on').returns(sinon.stub()); - - expect($rootScope.$on.callCount).to.be(0); - kbnUrl.change('/url'); - expect($rootScope.$on.callCount).to.be(1); - - // don't call the first handler - - kbnUrl.change('/url'); - expect($rootScope.$on.callCount).to.be(1); - }); - - it('one reload can happen once the first has completed', function () { - $location.url('/url'); - $route.current = { - $$route: { - regex: /.*/, - }, - }; - $route.reload = sinon.stub(); - - sinon.stub($rootScope, '$on').returns(sinon.stub()); - - expect($rootScope.$on.callCount).to.be(0); - kbnUrl.change('/url'); - expect($rootScope.$on.callCount).to.be(1); - - // call the first handler - $rootScope.$on.firstCall.args[1](); - expect($route.reload.callCount).to.be(1); - - expect($rootScope.$on.callCount).to.be(1); - kbnUrl.change('/url'); - expect($rootScope.$on.callCount).to.be(2); - }); - }); - - describe('remove', function () { - it('removes a parameter with a value from the url', function () { - $location.url('/myurl?exist&WithAParamToRemove=2&anothershouldexist=5'); - kbnUrl.removeParam('WithAParamToRemove'); - expect($location.url()).to.be('/myurl?exist&anothershouldexist=5'); - }); - - it('removes a parameter with no value from the url', function () { - $location.url('/myurl?removeme&hi=5'); - kbnUrl.removeParam('removeme'); - expect($location.url()).to.be('/myurl?hi=5'); - }); - - it('is noop if the given parameter doesn\t exist in the url', function () { - $location.url('/myurl?hi&bye'); - kbnUrl.removeParam('noexist'); - expect($location.url()).to.be('/myurl?hi&bye'); - }); - - it('is noop if given empty string param', function () { - $location.url('/myurl?hi&bye'); - kbnUrl.removeParam(''); - expect($location.url()).to.be('/myurl?hi&bye'); - }); - }); - - describe('change', function () { - it('should set $location.url', function () { - sinon.stub($location, 'url'); - - expect($location.url.callCount).to.be(0); - kbnUrl.change('/some-url'); - expect($location.url.callCount).to.be(1); - }); - - it('should uri encode replaced params', function () { - const url = '/some/path/'; - const params = { replace: faker.Lorem.words(3).join(' ') }; - const check = encodeURIComponent(params.replace); - sinon.stub($location, 'url'); - - kbnUrl.change(url + '{{replace}}', params); - - expect($location.url.firstCall.args[0]).to.be(url + check); - }); - - it('should parse angular expression in substitutions and uri encode the results', function () { - // build url by piecing together these parts - const urlParts = ['/', '/', '?', '&', '#']; - // make sure it can parse templates with weird spacing - const wrappers = [ - ['{{', '}}'], - ['{{ ', ' }}'], - ['{{', ' }}'], - ['{{ ', '}}'], - ['{{ ', ' }}'], - ]; - // make sure filters are evaluated via angular expressions - const objIndex = 4; // used to case one replace as an object - const filters = ['', 'uppercase', '', 'uppercase', '']; - - // the words (template keys) used must all be unique - const words = _.uniq(faker.Lorem.words(10)) - .slice(0, urlParts.length) - .map(function (word, i) { - if (filters[i].length) { - return word + '|' + filters[i]; - } - return word; - }); - - const replacements = faker.Lorem.words(urlParts.length).map(function (word, i) { - // make selected replacement into an object - if (i === objIndex) { - return { replace: word }; - } - - return word; - }); - - // build the url and test url - let url = ''; - let testUrl = ''; - urlParts.forEach(function (part, i) { - url += part + wrappers[i][0] + words[i] + wrappers[i][1]; - const locals = {}; - locals[words[i].split('|')[0]] = replacements[i]; - testUrl += part + encodeURIComponent($rootScope.$eval(words[i], locals)); - }); - - // create the locals replacement object - const params = {}; - replacements.forEach(function (replacement, i) { - const word = words[i].split('|')[0]; - params[word] = replacement; - }); - - sinon.stub($location, 'url'); - - kbnUrl.change(url, params); - - expect($location.url.firstCall.args[0]).to.not.be(url); - expect($location.url.firstCall.args[0]).to.be(testUrl); - }); - - it('should handle dot notation', function () { - const url = '/some/thing/{{that.is.substituted}}'; - - kbnUrl.change(url, { - that: { - is: { - substituted: 'test', - }, - }, - }); - - expect($location.url()).to.be('/some/thing/test'); - }); - - it('should throw when params are missing', function () { - const url = '/{{replace_me}}'; - const params = {}; - - try { - kbnUrl.change(url, params); - throw new Error('this should not run'); - } catch (err) { - expect(err).to.be.an(Error); - expect(err.message).to.match(/replace_me/); - } - }); - - it('should throw when filtered params are missing', function () { - const url = '/{{replace_me|number}}'; - const params = {}; - - try { - kbnUrl.change(url, params); - throw new Error('this should not run'); - } catch (err) { - expect(err).to.be.an(Error); - expect(err.message).to.match(/replace_me\|number/); - } - }); - - it('should change the entire url', function () { - const path = '/test/path'; - const search = { search: 'test' }; - const hash = 'hash'; - const newPath = '/new/location'; - - $location.path(path).search(search).hash(hash); - - // verify the starting state - expect($location.path()).to.be(path); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - - kbnUrl.change(newPath); - - // verify the ending state - expect($location.path()).to.be(newPath); - expect($location.search()).to.eql({}); - expect($location.hash()).to.be(''); - }); - - it('should allow setting app state on the target url', function () { - const path = '/test/path'; - const search = { search: 'test' }; - const hash = 'hash'; - const newPath = '/new/location'; - - $location.path(path).search(search).hash(hash); - - // verify the starting state - expect($location.path()).to.be(path); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - - kbnUrl.change(newPath, null, new StubAppState()); - - // verify the ending state - expect($location.path()).to.be(newPath); - expect($location.search()).to.eql({ _a: 'stateQueryParam' }); - expect($location.hash()).to.be(''); - }); - }); - - describe('changePath', function () { - it('should change just the path', function () { - const path = '/test/path'; - const search = { search: 'test' }; - const hash = 'hash'; - const newPath = '/new/location'; - - $location.path(path).search(search).hash(hash); - - // verify the starting state - expect($location.path()).to.be(path); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - - kbnUrl.changePath(newPath); - - // verify the ending state - expect($location.path()).to.be(newPath); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - }); - }); - - describe('redirect', function () { - it('should change the entire url', function () { - const path = '/test/path'; - const search = { search: 'test' }; - const hash = 'hash'; - const newPath = '/new/location'; - - $location.path(path).search(search).hash(hash); - - // verify the starting state - expect($location.path()).to.be(path); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - - kbnUrl.redirect(newPath); - - // verify the ending state - expect($location.path()).to.be(newPath); - expect($location.search()).to.eql({}); - expect($location.hash()).to.be(''); - }); - - it('should allow setting app state on the target url', function () { - const path = '/test/path'; - const search = { search: 'test' }; - const hash = 'hash'; - const newPath = '/new/location'; - - $location.path(path).search(search).hash(hash); - - // verify the starting state - expect($location.path()).to.be(path); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - - kbnUrl.redirect(newPath, null, new StubAppState()); - - // verify the ending state - expect($location.path()).to.be(newPath); - expect($location.search()).to.eql({ _a: 'stateQueryParam' }); - expect($location.hash()).to.be(''); - }); - - it('should replace the current history entry', function () { - sinon.stub($location, 'replace'); - $location.url('/some/path'); - - expect($location.replace.callCount).to.be(0); - kbnUrl.redirect('/new/path/'); - expect($location.replace.callCount).to.be(1); - }); - - it('should call replace on $location', function () { - sinon.stub(kbnUrl, '_shouldForceReload').returns(false); - sinon.stub($location, 'replace'); - - expect($location.replace.callCount).to.be(0); - kbnUrl.redirect('/poop'); - expect($location.replace.callCount).to.be(1); - }); - }); - - describe('redirectPath', function () { - it('should only change the path', function () { - const path = '/test/path'; - const search = { search: 'test' }; - const hash = 'hash'; - const newPath = '/new/location'; - - $location.path(path).search(search).hash(hash); - - // verify the starting state - expect($location.path()).to.be(path); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - - kbnUrl.redirectPath(newPath); - - // verify the ending state - expect($location.path()).to.be(newPath); - expect($location.search()).to.eql(search); - expect($location.hash()).to.be(hash); - }); - - it('should call replace on $location', function () { - sinon.stub(kbnUrl, '_shouldForceReload').returns(false); - sinon.stub($location, 'replace'); - - expect($location.replace.callCount).to.be(0); - kbnUrl.redirectPath('/poop'); - expect($location.replace.callCount).to.be(1); - }); - }); - - describe('_shouldForceReload', function () { - let next; - let prev; - - beforeEach(function () { - $route.current = { - $$route: { - regexp: /^\/is-current-route\/(\d+)/, - reloadOnSearch: true, - }, - }; - - prev = { path: '/is-current-route/1', search: {} }; - next = { path: '/is-current-route/1', search: {} }; - }); - - it("returns false if the passed url doesn't match the current route", function () { - next.path = '/not current'; - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); - }); - - describe('if the passed url does match the route', function () { - describe('and the route reloads on search', function () { - describe('and the path is the same', function () { - describe('and the search params are the same', function () { - it('returns true', function () { - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(true); - }); - }); - describe('but the search params are different', function () { - it('returns false', function () { - next.search = {}; - prev.search = { q: 'search term' }; - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); - }); - }); - }); - - describe('and the path is different', function () { - beforeEach(function () { - next.path = '/not-same'; - }); - - describe('and the search params are the same', function () { - it('returns false', function () { - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); - }); - }); - describe('but the search params are different', function () { - it('returns false', function () { - next.search = {}; - prev.search = { q: 'search term' }; - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); - }); - }); - }); - }); - - describe('but the route does not reload on search', function () { - beforeEach(function () { - $route.current.$$route.reloadOnSearch = false; - }); - - describe('and the path is the same', function () { - describe('and the search params are the same', function () { - it('returns true', function () { - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(true); - }); - }); - describe('but the search params are different', function () { - it('returns true', function () { - next.search = {}; - prev.search = { q: 'search term' }; - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(true); - }); - }); - }); - - describe('and the path is different', function () { - beforeEach(function () { - next.path = '/not-same'; - }); - - describe('and the search params are the same', function () { - it('returns false', function () { - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); - }); - }); - describe('but the search params are different', function () { - it('returns false', function () { - next.search = {}; - prev.search = { q: 'search term' }; - expect(kbnUrl._shouldForceReload(next, prev, $route)).to.be(false); - }); - }); - }); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/url/absolute_to_parsed_url.ts b/src/legacy/ui/public/url/absolute_to_parsed_url.ts deleted file mode 100644 index 30f493c25776c..0000000000000 --- a/src/legacy/ui/public/url/absolute_to_parsed_url.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { parse } from 'url'; - -import { extractAppPathAndId } from './extract_app_path_and_id'; -import { KibanaParsedUrl } from './kibana_parsed_url'; - -/** - * - * @param absoluteUrl - an absolute url, e.g. https://localhost:5601/gra/app/visualize#/edit/viz_id?hi=bye - * @param basePath - An optional base path for kibana. If supplied, should start with a "/". - * e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id the basePath is - * "/gra". - * @return {KibanaParsedUrl} - */ -export function absoluteToParsedUrl(absoluteUrl: string, basePath = '') { - const { appPath, appId } = extractAppPathAndId(absoluteUrl, basePath); - const { hostname, port, protocol } = parse(absoluteUrl); - return new KibanaParsedUrl({ - basePath, - appId: appId!, - appPath, - hostname, - port, - protocol, - }); -} diff --git a/src/legacy/ui/public/url/extract_app_path_and_id.ts b/src/legacy/ui/public/url/extract_app_path_and_id.ts deleted file mode 100644 index 44bba272e0873..0000000000000 --- a/src/legacy/ui/public/url/extract_app_path_and_id.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { parse } from 'url'; - -/** - * If the url is determined to contain an appId and appPath, it returns those portions. If it is not in the right - * format and an appId and appPath can't be extracted, it returns an empty object. - * @param {string} url - a relative or absolute url which contains an appPath, an appId, and optionally, a basePath. - * @param {string} basePath - optional base path, if given should start with "/". - */ -export function extractAppPathAndId(url: string, basePath = '') { - const parsedUrl = parse(url); - if (!parsedUrl.path) { - return {}; - } - const pathWithoutBase = parsedUrl.path.slice(basePath.length); - - if (!pathWithoutBase.startsWith('/app/')) { - return {}; - } - - const appPath = parsedUrl.hash && parsedUrl.hash.length > 0 ? parsedUrl.hash.slice(1) : ''; - return { appId: pathWithoutBase.slice('/app/'.length), appPath }; -} diff --git a/src/legacy/ui/public/url/index.js b/src/legacy/ui/public/url/index.js deleted file mode 100644 index 8ef267de2890c..0000000000000 --- a/src/legacy/ui/public/url/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { KbnUrlProvider } from './url'; -export { RedirectWhenMissingProvider } from './redirect_when_missing'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { modifyUrl } from '../../../../core/utils'; diff --git a/src/legacy/ui/public/url/kbn_url.ts b/src/legacy/ui/public/url/kbn_url.ts deleted file mode 100644 index 42b6a8f19f9a9..0000000000000 --- a/src/legacy/ui/public/url/kbn_url.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export interface KbnUrl { - change: (url: string) => void; - removeParam: (param: string) => void; -} diff --git a/src/legacy/ui/public/url/kibana_parsed_url.ts b/src/legacy/ui/public/url/kibana_parsed_url.ts deleted file mode 100644 index 1c60e8729e0ff..0000000000000 --- a/src/legacy/ui/public/url/kibana_parsed_url.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { parse } from 'url'; - -import { modifyUrl } from '../../../../core/public'; -import { prependPath } from './prepend_path'; - -interface Options { - /** - * An optional base path for kibana. If supplied, should start with a "/". - * e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id the - * basePath is "/gra" - */ - basePath?: string; - - /** - * The app id. - * e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id the app id is "kibana". - */ - appId: string; - - /** - * The path for a page in the the app. Should start with a "/". Don't include the hash sign. Can - * include all query parameters. - * e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id?g=state the appPath is - * "/edit/viz_id?g=state" - */ - appPath?: string; - - /** - * Optional hostname. Uses current window location's hostname if hostname, port, - * and protocol are undefined. - */ - hostname?: string; - - /** - * Optional port. Uses current window location's port if hostname, port, - * and protocol are undefined. - */ - port?: string; - - /** - * Optional protocol. Uses current window location's protocol if hostname, port, - * and protocol are undefined. - */ - protocol?: string; -} - -/** - * Represents the pieces that make up a url in Kibana, offering some helpful functionality - * for translating those pieces into absolute or relative urls. A Kibana url with a basePath - * looks like this: http://localhost:5601/basePath/app/appId#/an/appPath?with=query¶ms - * - * - basePath is "/basePath" - * - appId is "appId" - * - appPath is "/an/appPath?with=query¶ms" - * - * Almost all urls in Kibana should have this structure, including the "/app" portion in front of the appId - * (one exception is the login link). - */ -export class KibanaParsedUrl { - public appId: string; - public appPath: string; - public basePath: string; - public hostname?: string; - public protocol?: string; - public port?: string; - - constructor(options: Options) { - const { appId, basePath = '', appPath = '', hostname, protocol, port } = options; - - // We'll use window defaults - const hostOrProtocolSpecified = hostname || protocol || port; - - this.basePath = basePath; - this.appId = appId; - this.appPath = appPath; - this.hostname = hostOrProtocolSpecified ? hostname : window.location.hostname; - this.port = hostOrProtocolSpecified ? port : window.location.port; - this.protocol = hostOrProtocolSpecified ? protocol : window.location.protocol; - } - - public getGlobalState() { - if (!this.appPath) { - return ''; - } - const parsedUrl = parse(this.appPath, true); - const query = parsedUrl.query || {}; - return query._g || ''; - } - - public setGlobalState(newGlobalState: string | string[]) { - if (!this.appPath) { - return; - } - - this.appPath = modifyUrl(this.appPath, (parsed) => { - parsed.query._g = newGlobalState; - }); - } - - public addQueryParameter(name: string, val: string) { - this.appPath = modifyUrl(this.appPath, (parsed) => { - parsed.query[name] = val; - }); - } - - public getHashedAppPath() { - return `#${this.appPath}`; - } - - public getAppBasePath() { - return `/${this.appId}`; - } - - public getAppRootPath() { - return `/app${this.getAppBasePath()}${this.getHashedAppPath()}`; - } - - public getRootRelativePath() { - return prependPath(this.getAppRootPath(), this.basePath); - } - - public getAbsoluteUrl() { - return modifyUrl(this.getRootRelativePath(), (parsed) => { - parsed.protocol = this.protocol; - parsed.port = this.port; - parsed.hostname = this.hostname; - }); - } -} diff --git a/src/legacy/ui/public/url/prepend_path.ts b/src/legacy/ui/public/url/prepend_path.ts deleted file mode 100644 index b8a77d5c23bee..0000000000000 --- a/src/legacy/ui/public/url/prepend_path.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { isString } from 'lodash'; -import { format, parse } from 'url'; - -/** - * - * @param {string} relativePath - a relative path that must start with a "/". - * @param {string} newPath - the new path to prefix. ex: 'xyz' - * @return {string} the url with the basePath prepended. ex. '/xyz/app/kibana#/management'. If - * the relative path isn't in the right format (e.g. doesn't start with a "/") the relativePath is returned - * unchanged. - */ -export function prependPath(relativePath: string, newPath = '') { - if (!relativePath || !isString(relativePath)) { - return relativePath; - } - - const parsed = parse(relativePath, true, true); - if (!parsed.host && parsed.pathname) { - if (parsed.pathname[0] === '/') { - parsed.pathname = newPath + parsed.pathname; - } - } - - return format({ - protocol: parsed.protocol, - host: parsed.host, - pathname: parsed.pathname, - query: parsed.query, - hash: parsed.hash, - }); -} diff --git a/src/legacy/ui/public/url/redirect_when_missing.js b/src/legacy/ui/public/url/redirect_when_missing.js deleted file mode 100644 index 85c90a14d9fd7..0000000000000 --- a/src/legacy/ui/public/url/redirect_when_missing.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { MarkdownSimple } from '../../../../plugins/kibana_react/public'; -import { toastNotifications } from 'ui/notify'; -import { SavedObjectNotFound } from '../../../../plugins/kibana_utils/public'; -import { uiModules } from '../modules'; - -uiModules.get('kibana/url').service('redirectWhenMissing', function (Private) { - return Private(RedirectWhenMissingProvider); -}); - -export function RedirectWhenMissingProvider(kbnUrl, Promise) { - /** - * Creates an error handler that will redirect to a url when a SavedObjectNotFound - * error is thrown - * - * @param {string|object} mapping - a mapping of url's to redirect to based on the saved object that - * couldn't be found, or just a string that will be used for all types - * @return {function} - the handler to pass to .catch() - */ - return function (mapping) { - if (typeof mapping === 'string') { - mapping = { '*': mapping }; - } - - return function (error) { - // if this error is not "404", rethrow - const savedObjectNotFound = error instanceof SavedObjectNotFound; - const unknownVisType = error.message.indexOf('Invalid type') === 0; - if (unknownVisType) { - error.savedObjectType = 'visualization'; - } else if (!savedObjectNotFound) { - throw error; - } - - let url = mapping[error.savedObjectType] || mapping['*']; - if (!url) url = '/'; - - url += (url.indexOf('?') >= 0 ? '&' : '?') + `notFound=${error.savedObjectType}`; - - toastNotifications.addWarning({ - title: i18n.translate('common.ui.url.savedObjectIsMissingNotificationMessage', { - defaultMessage: 'Saved object is missing', - }), - text: {error.message}, - }); - - kbnUrl.redirect(url); - return Promise.halt(); - }; - }; -} diff --git a/src/legacy/ui/public/url/relative_to_absolute.ts b/src/legacy/ui/public/url/relative_to_absolute.ts deleted file mode 100644 index 7d0737d145a1b..0000000000000 --- a/src/legacy/ui/public/url/relative_to_absolute.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * - * @param {string} url - a relative or root relative url. If a relative path is given then the - * absolute url returned will depend on the current page where this function is called from. For example - * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get - * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that - * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". - * @return {string} the relative url transformed into an absolute url - */ -export function relativeToAbsolute(url: string) { - // convert all link urls to absolute urls - const a = document.createElement('a'); - a.setAttribute('href', url); - return a.href; -} diff --git a/src/legacy/ui/public/url/url.js b/src/legacy/ui/public/url/url.js deleted file mode 100644 index fb243b02e05c7..0000000000000 --- a/src/legacy/ui/public/url/url.js +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { uiModules } from '../modules'; -import { AppStateProvider } from '../state_management/app_state'; - -uiModules.get('kibana/url').service('kbnUrl', function (Private, $injector) { - //config is not directly used but registers global event listeners to kbnUrl to function - $injector.get('config'); - return Private(KbnUrlProvider); -}); - -export function KbnUrlProvider($injector, $location, $rootScope, $parse, Private) { - /** - * the `kbnUrl` service was created to smooth over some of the - * inconsistent behavior that occurs when modifying the url via - * the `$location` api. In general it is recommended that you use - * the `kbnUrl` service any time you want to modify the url. - * - * "features" that `kbnUrl` does it's best to guarantee, which - * are not guaranteed with the `$location` service: - * - calling `kbnUrl.change()` with a url that resolves to the current - * route will force a full transition (rather than just updating the - * properties of the $route object) - * - * Additional features of `kbnUrl` - * - parameterized urls - * - easily include an app state with the url - * - * @type {KbnUrl} - */ - const self = this; - - /** - * Navigate to a url - * - * @param {String} url - the new url, can be a template. See #eval - * @param {Object} [paramObj] - optional set of parameters for the url template - * @return {undefined} - */ - self.change = function (url, paramObj, appState) { - self._changeLocation('url', url, paramObj, false, appState); - }; - - /** - * Same as #change except only changes the url's path, - * leaving the search string and such intact - * - * @param {String} path - the new path, can be a template. See #eval - * @param {Object} [paramObj] - optional set of parameters for the path template - * @return {undefined} - */ - self.changePath = function (path, paramObj) { - self._changeLocation('path', path, paramObj); - }; - - /** - * Same as #change except that it removes the current url from history - * - * @param {String} url - the new url, can be a template. See #eval - * @param {Object} [paramObj] - optional set of parameters for the url template - * @return {undefined} - */ - self.redirect = function (url, paramObj, appState) { - self._changeLocation('url', url, paramObj, true, appState); - }; - - /** - * Same as #redirect except only changes the url's path, - * leaving the search string and such intact - * - * @param {String} path - the new path, can be a template. See #eval - * @param {Object} [paramObj] - optional set of parameters for the path template - * @return {undefined} - */ - self.redirectPath = function (path, paramObj) { - self._changeLocation('path', path, paramObj, true); - }; - - /** - * Evaluate a url template. templates can contain double-curly wrapped - * expressions that are evaluated in the context of the paramObj - * - * @param {String} template - the url template to evaluate - * @param {Object} [paramObj] - the variables to expose to the template - * @return {String} - the evaluated result - * @throws {Error} If any of the expressions can't be parsed. - */ - self.eval = function (template, paramObj) { - paramObj = paramObj || {}; - - return template.replace(/\{\{([^\}]+)\}\}/g, function (match, expr) { - // remove filters - const key = expr.split('|')[0].trim(); - - // verify that the expression can be evaluated - const p = $parse(key)(paramObj); - - // if evaluation can't be made, throw - if (_.isUndefined(p)) { - throw new Error( - i18n.translate('common.ui.url.replacementFailedErrorMessage', { - defaultMessage: 'Replacement failed, unresolved expression: {expr}', - values: { expr }, - }) - ); - } - - return encodeURIComponent($parse(expr)(paramObj)); - }); - }; - - /** - * convert an object's route to an href, compatible with - * window.location.href= and - * - * @param {Object} obj - any object that list's it's routes at obj.routes{} - * @param {string} route - the route name - * @return {string} - the computed href - */ - self.getRouteHref = function (obj, route) { - return '#' + self.getRouteUrl(obj, route); - }; - - /** - * convert an object's route to a url, compatible with url.change() or $location.url() - * - * @param {Object} obj - any object that list's it's routes at obj.routes{} - * @param {string} route - the route name - * @return {string} - the computed url - */ - self.getRouteUrl = function (obj, route) { - const template = obj && obj.routes && obj.routes[route]; - if (template) return self.eval(template, obj); - }; - - /** - * Similar to getRouteUrl, supports objects which list their routes, - * and redirects to the named route. See #redirect - * - * @param {Object} obj - any object that list's it's routes at obj.routes{} - * @param {string} route - the route name - * @return {undefined} - */ - self.redirectToRoute = function (obj, route) { - self.redirect(self.getRouteUrl(obj, route)); - }; - - /** - * Similar to getRouteUrl, supports objects which list their routes, - * and changes the url to the named route. See #change - * - * @param {Object} obj - any object that list's it's routes at obj.routes{} - * @param {string} route - the route name - * @return {undefined} - */ - self.changeToRoute = function (obj, route) { - self.change(self.getRouteUrl(obj, route)); - }; - - /** - * Removes the given parameter from the url. Does so without modifying the browser - * history. - * @param param - */ - self.removeParam = function (param) { - $location.search(param, null).replace(); - }; - - ///// - // private api - ///// - let reloading; - - self._changeLocation = function (type, url, paramObj, replace, appState) { - const prev = { - path: $location.path(), - search: $location.search(), - }; - - url = self.eval(url, paramObj); - $location[type](url); - if (replace) $location.replace(); - - if (appState) { - $location.search(appState.getQueryParamName(), appState.toQueryParam()); - } - - const next = { - path: $location.path(), - search: $location.search(), - }; - - if ($injector.has('$route')) { - const $route = $injector.get('$route'); - - if (self._shouldForceReload(next, prev, $route)) { - const appState = Private(AppStateProvider).getAppState(); - if (appState) appState.destroy(); - - reloading = $rootScope.$on('$locationChangeSuccess', function () { - // call the "unlisten" function returned by $on - reloading(); - reloading = false; - - $route.reload(); - }); - } - } - }; - - // determine if the router will automatically reload the route - self._shouldForceReload = function (next, prev, $route) { - if (reloading) return false; - - const route = $route.current && $route.current.$$route; - if (!route) return false; - - // for the purposes of determining whether the router will - // automatically be reloading, '' and '/' are equal - const nextPath = next.path || '/'; - const prevPath = prev.path || '/'; - if (nextPath !== prevPath) return false; - - const reloadOnSearch = route.reloadOnSearch; - const searchSame = _.isEqual(next.search, prev.search); - return (reloadOnSearch && searchSame) || !reloadOnSearch; - }; -} diff --git a/src/legacy/ui/public/utils/collection.test.ts b/src/legacy/ui/public/utils/collection.test.ts deleted file mode 100644 index 0841e3554c0d0..0000000000000 --- a/src/legacy/ui/public/utils/collection.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { move } from './collection'; - -describe('collection', () => { - describe('move', () => { - test('accepts previous from->to syntax', () => { - const list = [1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1]; - - expect(list[3]).toBe(1); - expect(list[8]).toBe(8); - - move(list, 8, 3); - - expect(list[8]).toBe(1); - expect(list[3]).toBe(8); - }); - - test('moves an object up based on a function callback', () => { - const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; - - expect(list[4]).toBe(0); - expect(list[5]).toBe(1); - expect(list[6]).toBe(0); - - move(list, 5, false, (v: any) => v === 0); - - expect(list[4]).toBe(1); - expect(list[5]).toBe(0); - expect(list[6]).toBe(0); - }); - - test('moves an object down based on a function callback', () => { - const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; - - expect(list[4]).toBe(0); - expect(list[5]).toBe(1); - expect(list[6]).toBe(0); - - move(list, 5, true, (v: any) => v === 0); - - expect(list[4]).toBe(0); - expect(list[5]).toBe(0); - expect(list[6]).toBe(1); - }); - - test('moves an object up based on a where callback', () => { - const list = [ - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - ]; - - expect(list[4]).toHaveProperty('v', 0); - expect(list[5]).toHaveProperty('v', 1); - expect(list[6]).toHaveProperty('v', 0); - - move(list, 5, false, { v: 0 }); - - expect(list[4]).toHaveProperty('v', 1); - expect(list[5]).toHaveProperty('v', 0); - expect(list[6]).toHaveProperty('v', 0); - }); - - test('moves an object down based on a where callback', () => { - const list = [ - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - ]; - - expect(list[4]).toHaveProperty('v', 0); - expect(list[5]).toHaveProperty('v', 1); - expect(list[6]).toHaveProperty('v', 0); - - move(list, 5, true, { v: 0 }); - - expect(list[4]).toHaveProperty('v', 0); - expect(list[5]).toHaveProperty('v', 0); - expect(list[6]).toHaveProperty('v', 1); - }); - - test('moves an object down based on a pluck callback', () => { - const list = [ - { id: 0, normal: true }, - { id: 1, normal: true }, - { id: 2, normal: true }, - { id: 3, normal: true }, - { id: 4, normal: true }, - { id: 5, normal: false }, - { id: 6, normal: true }, - { id: 7, normal: true }, - { id: 8, normal: true }, - { id: 9, normal: true }, - ]; - - expect(list[4]).toHaveProperty('id', 4); - expect(list[5]).toHaveProperty('id', 5); - expect(list[6]).toHaveProperty('id', 6); - - move(list, 5, true, 'normal'); - - expect(list[4]).toHaveProperty('id', 4); - expect(list[5]).toHaveProperty('id', 6); - expect(list[6]).toHaveProperty('id', 5); - }); - }); -}); diff --git a/src/legacy/ui/public/utils/collection.ts b/src/legacy/ui/public/utils/collection.ts deleted file mode 100644 index b882a2bbe6e5b..0000000000000 --- a/src/legacy/ui/public/utils/collection.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -/** - * move an obj either up or down in the collection by - * injecting it either before/after the prev/next obj that - * satisfied the qualifier - * - * or, just from one index to another... - * - * @param {array} objs - the list to move the object within - * @param {number|any} obj - the object that should be moved, or the index that the object is currently at - * @param {number|boolean} below - the index to move the object to, or whether it should be moved up or down - * @param {function} qualifier - a lodash-y callback, object = _.where, string = _.pluck - * @return {array} - the objs argument - */ -export function move( - objs: any[], - obj: object | number, - below: number | boolean, - qualifier?: ((object: object, index: number) => any) | Record | string -): object[] { - const origI = _.isNumber(obj) ? obj : objs.indexOf(obj); - if (origI === -1) { - return objs; - } - - if (_.isNumber(below)) { - // move to a specific index - objs.splice(below, 0, objs.splice(origI, 1)[0]); - return objs; - } - - below = !!below; - qualifier = qualifier && _.iteratee(qualifier); - - const above = !below; - const finder = below ? _.findIndex : _.findLastIndex; - - // find the index of the next/previous obj that meets the qualifications - const targetI = finder(objs, (otherAgg, otherI) => { - if (below && otherI <= origI) { - return; - } - if (above && otherI >= origI) { - return; - } - return Boolean(_.isFunction(qualifier) && qualifier(otherAgg, otherI)); - }); - - if (targetI === -1) { - return objs; - } - - // place the obj at it's new index - objs.splice(targetI, 0, objs.splice(origI, 1)[0]); - return objs; -} diff --git a/src/legacy/ui/public/utils/legacy_class.js b/src/legacy/ui/public/utils/legacy_class.js deleted file mode 100644 index f47650a77bb6d..0000000000000 --- a/src/legacy/ui/public/utils/legacy_class.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// create a property descriptor for properties -// that won't change -function describeConst(val) { - return { - writable: false, - enumerable: false, - configurable: false, - value: val, - }; -} - -const props = { - inherits: describeConst(function (SuperClass) { - const prototype = Object.create(SuperClass.prototype, { - constructor: describeConst(this), - superConstructor: describeConst(SuperClass), - }); - - Object.defineProperties(this, { - prototype: describeConst(prototype), - Super: describeConst(SuperClass), - }); - - return this; - }), -}; - -/** - * Add class-related behavior to a function, currently this - * only attaches an .inherits() method. - * - * @param {Constructor} ClassConstructor - The function that should be extended - * @return {Constructor} - the constructor passed in; - */ -export function createLegacyClass(ClassConstructor) { - return Object.defineProperties(ClassConstructor, props); -} diff --git a/src/legacy/ui/ui_mixin.js b/src/legacy/ui/ui_mixin.js index 432c4f02bc3e6..54da001d20669 100644 --- a/src/legacy/ui/ui_mixin.js +++ b/src/legacy/ui/ui_mixin.js @@ -19,10 +19,8 @@ import { uiAppsMixin } from './ui_apps'; import { uiRenderMixin } from './ui_render'; -import { uiSettingsMixin } from './ui_settings'; export async function uiMixin(kbnServer) { await kbnServer.mixin(uiAppsMixin); - await kbnServer.mixin(uiSettingsMixin); await kbnServer.mixin(uiRenderMixin); } diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 23fb6028f84db..8cc2cd1321a62 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -21,6 +21,7 @@ import { createHash } from 'crypto'; import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import { KibanaRequest } from '../../../core/server'; import { AppBootstrap } from './bootstrap'; import { getApmConfig } from '../apm'; @@ -79,7 +80,10 @@ export function uiRenderMixin(kbnServer, server, config) { auth: authEnabled ? { mode: 'try' } : false, }, async handler(request, h) { - const uiSettings = request.getUiSettingsService(); + const soClient = kbnServer.newPlatform.start.core.savedObjects.getScopedClient( + KibanaRequest.from(request) + ); + const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(soClient); const darkMode = !authEnabled || request.auth.isAuthenticated diff --git a/src/legacy/ui/ui_settings/index.js b/src/legacy/ui/ui_settings/index.js deleted file mode 100644 index ec3122c4e390e..0000000000000 --- a/src/legacy/ui/ui_settings/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { uiSettingsMixin } from './ui_settings_mixin'; diff --git a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts deleted file mode 100644 index 84a64d3f46f11..0000000000000 --- a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; - -import { savedObjectsClientMock } from '../../../../core/server/mocks'; -import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory'; -import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request'; -// @ts-ignore -import { uiSettingsMixin } from '../ui_settings_mixin'; - -interface Decorators { - server: { [name: string]: any }; - request: { [name: string]: any }; -} - -const uiSettingDefaults = { - application: { - defaultProperty1: 'value1', - }, -}; - -describe('uiSettingsMixin()', () => { - const sandbox = sinon.createSandbox(); - - function setup() { - // maps of decorations passed to `server.decorate()` - const decorations: Decorators = { - server: {}, - request: {}, - }; - - // mock hapi server - const server = { - log: sinon.stub(), - route: sinon.stub(), - addMemoizedFactoryToRequest(name: string, factory: (...args: any[]) => any) { - this.decorate('request', name, function (this: typeof server) { - return factory(this); - }); - }, - decorate: sinon.spy((type: keyof Decorators, name: string, value: any) => { - decorations[type][name] = value; - }), - newPlatform: { - setup: { - core: { - uiSettings: { - register: sinon.stub(), - }, - }, - }, - }, - }; - - // "promise" returned from kbnServer.ready() - const readyPromise = { - then: sinon.stub(), - }; - - const kbnServer = { - server, - uiExports: { uiSettingDefaults }, - ready: sinon.stub().returns(readyPromise), - }; - - uiSettingsMixin(kbnServer, server); - - return { - kbnServer, - server, - decorations, - readyPromise, - }; - } - - afterEach(() => sandbox.restore()); - - it('passes uiSettingsDefaults to the new platform', () => { - const { server } = setup(); - sinon.assert.calledOnce(server.newPlatform.setup.core.uiSettings.register); - sinon.assert.calledWithExactly( - server.newPlatform.setup.core.uiSettings.register, - uiSettingDefaults - ); - }); - - describe('server.uiSettingsServiceFactory()', () => { - it('decorates server with "uiSettingsServiceFactory"', () => { - const { decorations } = setup(); - expect(decorations.server).to.have.property('uiSettingsServiceFactory').a('function'); - - const uiSettingsServiceFactoryStub = sandbox.stub( - uiSettingsServiceFactoryNS, - 'uiSettingsServiceFactory' - ); - sinon.assert.notCalled(uiSettingsServiceFactoryStub); - decorations.server.uiSettingsServiceFactory(); - sinon.assert.calledOnce(uiSettingsServiceFactoryStub); - }); - - it('passes `server` and `options` argument to factory', () => { - const { decorations, server } = setup(); - expect(decorations.server).to.have.property('uiSettingsServiceFactory').a('function'); - - const uiSettingsServiceFactoryStub = sandbox.stub( - uiSettingsServiceFactoryNS, - 'uiSettingsServiceFactory' - ); - - sinon.assert.notCalled(uiSettingsServiceFactoryStub); - - const savedObjectsClient = savedObjectsClientMock.create(); - decorations.server.uiSettingsServiceFactory({ - savedObjectsClient, - }); - sinon.assert.calledOnce(uiSettingsServiceFactoryStub); - sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server as any, { - savedObjectsClient, - }); - }); - }); - - describe('request.getUiSettingsService()', () => { - it('exposes "getUiSettingsService" on requests', () => { - const { decorations } = setup(); - expect(decorations.request).to.have.property('getUiSettingsService').a('function'); - - const getUiSettingsServiceForRequestStub = sandbox.stub( - getUiSettingsServiceForRequestNS, - 'getUiSettingsServiceForRequest' - ); - sinon.assert.notCalled(getUiSettingsServiceForRequestStub); - decorations.request.getUiSettingsService(); - sinon.assert.calledOnce(getUiSettingsServiceForRequestStub); - }); - - it('passes request to getUiSettingsServiceForRequest', () => { - const { server, decorations } = setup(); - expect(decorations.request).to.have.property('getUiSettingsService').a('function'); - - const getUiSettingsServiceForRequestStub = sandbox.stub( - getUiSettingsServiceForRequestNS, - 'getUiSettingsServiceForRequest' - ); - sinon.assert.notCalled(getUiSettingsServiceForRequestStub); - const request = {}; - decorations.request.getUiSettingsService.call(request); - sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server as any, request as any); - }); - }); - - describe('server.uiSettings()', () => { - it('throws an error, links to pr', () => { - const { decorations } = setup(); - expect(decorations.server).to.have.property('uiSettings').a('function'); - expect(() => { - decorations.server.uiSettings(); - }).to.throwError('http://github.com' as any); // incorrect typings - }); - }); -}); diff --git a/src/legacy/ui/ui_settings/ui_exports_consumer.js b/src/legacy/ui/ui_settings/ui_exports_consumer.js deleted file mode 100644 index d2bb3a00ce0ed..0000000000000 --- a/src/legacy/ui/ui_settings/ui_exports_consumer.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * The UiExports class accepts consumer objects that it consults while - * trying to consume all of the `uiExport` declarations provided by - * plugins. - * - * UiExportConsumer is instantiated and passed to UiExports, then for - * every `uiExport` declaration the `exportConsumer(type)` method is - * with the key of the declaration. If this consumer knows how to handle - * that key we return a function that will be called with the plugins - * and values of all declarations using that key. - * - * With this, the consumer merges all of the declarations into the - * _uiSettingDefaults map, ensuring that there are not collisions along - * the way. - * - * @class UiExportsConsumer - */ -export class UiExportsConsumer { - _uiSettingDefaults = {}; - - exportConsumer(type) { - switch (type) { - case 'uiSettingDefaults': - return (plugin, settingDefinitions) => { - Object.keys(settingDefinitions).forEach((key) => { - if (key in this._uiSettingDefaults) { - throw new Error(`uiSettingDefaults for key "${key}" are already defined`); - } - - this._uiSettingDefaults[key] = settingDefinitions[key]; - }); - }; - } - } - - /** - * Get the map of uiSettingNames to "default" specifications - * @return {Object} - */ - getUiSettingDefaults() { - return this._uiSettingDefaults; - } -} diff --git a/src/legacy/ui/ui_settings/ui_settings_mixin.js b/src/legacy/ui/ui_settings/ui_settings_mixin.js deleted file mode 100644 index 8190b67732dac..0000000000000 --- a/src/legacy/ui/ui_settings/ui_settings_mixin.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiSettingsServiceFactory } from './ui_settings_service_factory'; -import { getUiSettingsServiceForRequest } from './ui_settings_service_for_request'; - -export function uiSettingsMixin(kbnServer, server) { - const { uiSettingDefaults = {} } = kbnServer.uiExports; - const mergedUiSettingDefaults = Object.keys(uiSettingDefaults).reduce((acc, currentKey) => { - const defaultSetting = uiSettingDefaults[currentKey]; - const updatedDefaultSetting = { - ...defaultSetting, - }; - if (typeof defaultSetting.options === 'function') { - updatedDefaultSetting.options = defaultSetting.options(server); - } - if (typeof defaultSetting.value === 'function') { - updatedDefaultSetting.value = defaultSetting.value(server); - } - acc[currentKey] = updatedDefaultSetting; - return acc; - }, {}); - - server.newPlatform.setup.core.uiSettings.register(mergedUiSettingDefaults); - - server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => { - return uiSettingsServiceFactory(server, options); - }); - - server.addMemoizedFactoryToRequest('getUiSettingsService', (request) => { - return getUiSettingsServiceForRequest(server, request); - }); - - server.decorate('server', 'uiSettings', () => { - throw new Error(` - server.uiSettings has been removed, see https://github.com/elastic/kibana/pull/12243. - `); - }); -} diff --git a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts deleted file mode 100644 index 6c3c50d175dc5..0000000000000 --- a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Legacy } from 'kibana'; -import { IUiSettingsClient, SavedObjectsClientContract } from 'src/core/server'; - -export interface UiSettingsServiceFactoryOptions { - savedObjectsClient: SavedObjectsClientContract; -} -/** - * Create an instance of UiSettingsClient that will use the - * passed `savedObjectsClient` to communicate with elasticsearch - * - * @return {IUiSettingsClient} - */ -export function uiSettingsServiceFactory( - server: Legacy.Server, - options: UiSettingsServiceFactoryOptions -): IUiSettingsClient { - return server.newPlatform.start.core.uiSettings.asScopedToClient(options.savedObjectsClient); -} diff --git a/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts deleted file mode 100644 index 057fc64c9ebd7..0000000000000 --- a/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Legacy } from 'kibana'; -import { IUiSettingsClient } from 'src/core/server'; -import { uiSettingsServiceFactory } from './ui_settings_service_factory'; - -/** - * Get/create an instance of UiSettingsService bound to a specific request. - * Each call is cached (keyed on the request object itself) and subsequent - * requests will get the first UiSettingsService instance even if the `options` - * have changed. - * - * @param {Hapi.Server} server - * @param {Hapi.Request} request - * @param {Object} [options={}] - - * @return {IUiSettingsClient} - */ -export function getUiSettingsServiceForRequest( - server: Legacy.Server, - request: Legacy.Request -): IUiSettingsClient { - const savedObjectsClient = request.getSavedObjectsClient(); - return uiSettingsServiceFactory(server, { savedObjectsClient }); -} diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx index 6103041cf0a4c..68a21c6a1587c 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx @@ -32,10 +32,6 @@ import { AdvancedSettingsComponent } from './advanced_settings'; import { notificationServiceMock, docLinksServiceMock } from '../../../../core/public/mocks'; import { ComponentRegistry } from '../component_registry'; -jest.mock('ui/new_platform', () => ({ - npStart: mockConfig(), -})); - jest.mock('./components/field', () => ({ Field: () => { return 'field'; diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts index 4343a3409b696..cd32c2025456f 100644 --- a/src/plugins/dashboard/public/application/actions/index.ts +++ b/src/plugins/dashboard/public/application/actions/index.ts @@ -42,3 +42,8 @@ export { UnlinkFromLibraryActionContext, ACTION_UNLINK_FROM_LIBRARY, } from './unlink_from_library_action'; +export { + LibraryNotificationActionContext, + LibraryNotificationAction, + ACTION_LIBRARY_NOTIFICATION, +} from './library_notification_action'; diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx new file mode 100644 index 0000000000000..385f6f14ba94c --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx @@ -0,0 +1,100 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { isErrorEmbeddable, ReferenceOrValueEmbeddable } from '../../embeddable_plugin'; +import { DashboardContainer } from '../embeddable'; +import { getSampleDashboardInput } from '../test_helpers'; +import { + CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddableFactory, + ContactCardEmbeddable, + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, +} from '../../embeddable_plugin_test_samples'; +import { coreMock } from '../../../../../core/public/mocks'; +import { CoreStart } from 'kibana/public'; +import { LibraryNotificationAction } from '.'; +import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; +import { ViewMode } from '../../../../embeddable/public'; + +const { setup, doStart } = embeddablePluginMock.createInstance(); +setup.registerEmbeddableFactory( + CONTACT_CARD_EMBEDDABLE, + new ContactCardEmbeddableFactory((() => null) as any, {} as any) +); +const start = doStart(); + +let container: DashboardContainer; +let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; +let coreStart: CoreStart; +beforeEach(async () => { + coreStart = coreMock.createStart(); + + const containerOptions = { + ExitFullScreenButton: () => null, + SavedObjectFinder: () => null, + application: {} as any, + embeddable: start, + inspector: {} as any, + notifications: {} as any, + overlays: coreStart.overlays, + savedObjectMetaData: {} as any, + uiActions: {} as any, + }; + + container = new DashboardContainer(getSampleDashboardInput(), containerOptions); + + const contactCardEmbeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Kibanana', + }); + + if (isErrorEmbeddable(contactCardEmbeddable)) { + throw new Error('Failed to create embeddable'); + } + embeddable = embeddablePluginMock.mockRefOrValEmbeddable< + ContactCardEmbeddable, + ContactCardEmbeddableInput + >(contactCardEmbeddable, { + mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: contactCardEmbeddable.id }, + mockedByValueInput: { firstName: 'Kibanana', id: contactCardEmbeddable.id }, + }); + embeddable.updateInput({ viewMode: ViewMode.EDIT }); +}); + +test('Notification is shown when embeddable on dashboard has reference type input', async () => { + const action = new LibraryNotificationAction(); + embeddable.updateInput(await embeddable.getInputAsRefType()); + expect(await action.isCompatible({ embeddable })).toBe(true); +}); + +test('Notification is not shown when embeddable input is by value', async () => { + const action = new LibraryNotificationAction(); + embeddable.updateInput(await embeddable.getInputAsValueType()); + expect(await action.isCompatible({ embeddable })).toBe(false); +}); + +test('Notification is not shown when view mode is set to view', async () => { + const action = new LibraryNotificationAction(); + embeddable.updateInput(await embeddable.getInputAsRefType()); + embeddable.updateInput({ viewMode: ViewMode.VIEW }); + expect(await action.isCompatible({ embeddable })).toBe(false); +}); diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx new file mode 100644 index 0000000000000..974b55275ccc1 --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx @@ -0,0 +1,89 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiBadge } from '@elastic/eui'; +import { IEmbeddable, ViewMode, isReferenceOrValueEmbeddable } from '../../embeddable_plugin'; +import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin'; +import { reactToUiComponent } from '../../../../kibana_react/public'; + +export const ACTION_LIBRARY_NOTIFICATION = 'ACTION_LIBRARY_NOTIFICATION'; + +export interface LibraryNotificationActionContext { + embeddable: IEmbeddable; +} + +export class LibraryNotificationAction implements ActionByType { + public readonly id = ACTION_LIBRARY_NOTIFICATION; + public readonly type = ACTION_LIBRARY_NOTIFICATION; + public readonly order = 1; + + private displayName = i18n.translate('dashboard.panel.LibraryNotification', { + defaultMessage: 'Library', + }); + + private icon = 'folderCheck'; + + public readonly MenuItem = reactToUiComponent(() => ( + + {this.displayName} + + )); + + public getDisplayName({ embeddable }: LibraryNotificationActionContext) { + if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { + throw new IncompatibleActionError(); + } + return this.displayName; + } + + public getIconType({ embeddable }: LibraryNotificationActionContext) { + if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { + throw new IncompatibleActionError(); + } + return this.icon; + } + + public getDisplayNameTooltip = ({ embeddable }: LibraryNotificationActionContext) => { + if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { + throw new IncompatibleActionError(); + } + return i18n.translate('dashboard.panel.libraryNotification.toolTip', { + defaultMessage: + 'This panel is linked to a Library item. Editing the panel might affect other dashboards.', + }); + }; + + public isCompatible = async ({ embeddable }: LibraryNotificationActionContext) => { + return ( + embeddable.getInput()?.viewMode !== ViewMode.VIEW && + isReferenceOrValueEmbeddable(embeddable) && + embeddable.inputIsRefType(embeddable.getInput()) + ); + }; + + public execute = async () => {}; +} diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 8b9b92faf9031..3df52f4e7a205 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -40,6 +40,7 @@ import { EmbeddableStart, SavedObjectEmbeddableInput, EmbeddableInput, + PANEL_NOTIFICATION_TRIGGER, } from '../../embeddable/public'; import { DataPublicPluginSetup, DataPublicPluginStart, esFilters } from '../../data/public'; import { SharePluginSetup, SharePluginStart, UrlGeneratorContract } from '../../share/public'; @@ -83,6 +84,12 @@ import { ACTION_UNLINK_FROM_LIBRARY, UnlinkFromLibraryActionContext, UnlinkFromLibraryAction, + ACTION_ADD_TO_LIBRARY, + AddToLibraryActionContext, + AddToLibraryAction, + ACTION_LIBRARY_NOTIFICATION, + LibraryNotificationActionContext, + LibraryNotificationAction, } from './application'; import { createDashboardUrlGenerator, @@ -95,11 +102,6 @@ import { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; import { UrlGeneratorState } from '../../share/public'; import { AttributeService } from '.'; -import { - AddToLibraryAction, - ACTION_ADD_TO_LIBRARY, - AddToLibraryActionContext, -} from './application/actions/add_to_library_action'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -162,6 +164,7 @@ declare module '../../../plugins/ui_actions/public' { [ACTION_CLONE_PANEL]: ClonePanelActionContext; [ACTION_ADD_TO_LIBRARY]: AddToLibraryActionContext; [ACTION_UNLINK_FROM_LIBRARY]: UnlinkFromLibraryActionContext; + [ACTION_LIBRARY_NOTIFICATION]: LibraryNotificationActionContext; } } @@ -437,6 +440,10 @@ export class DashboardPlugin const unlinkFromLibraryAction = new UnlinkFromLibraryAction(); uiActions.registerAction(unlinkFromLibraryAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id); + + const libraryNotificationAction = new LibraryNotificationAction(); + uiActions.registerAction(libraryNotificationAction); + uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, libraryNotificationAction.id); } const savedDashboardLoader = createSavedDashboardLoader({ diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index 5d6ae61a77e00..ea91a9bb14e1f 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -172,12 +172,12 @@ export class IndexPattern implements IIndexPattern { }); } - private async indexFields(forceFieldRefresh: boolean = false, specs?: FieldSpec[]) { + private async indexFields(specs?: FieldSpec[]) { if (!this.id) { return; } - if (forceFieldRefresh || this.isFieldRefreshRequired(specs)) { + if (this.isFieldRefreshRequired(specs)) { await this.refreshFields(); } else { if (specs) { @@ -213,7 +213,7 @@ export class IndexPattern implements IIndexPattern { return this; } - private updateFromElasticSearch(response: any, forceFieldRefresh: boolean = false) { + private updateFromElasticSearch(response: any) { if (!response.found) { throw new SavedObjectNotFound(savedObjectType, this.id, 'management/kibana/indexPatterns'); } @@ -239,7 +239,7 @@ export class IndexPattern implements IIndexPattern { } this.version = response.version; - return this.indexFields(forceFieldRefresh, response.fields); + return this.indexFields(response.fields); } getComputedFields() { @@ -283,7 +283,7 @@ export class IndexPattern implements IIndexPattern { }; } - async init(forceFieldRefresh = false) { + async init() { if (!this.id) { return this; // no id === no elasticsearch document } @@ -307,7 +307,7 @@ export class IndexPattern implements IIndexPattern { }; // Do this before we attempt to update from ES since that call can potentially perform a save this.originalBody = this.prepBody(); - await this.updateFromElasticSearch(response, forceFieldRefresh); + await this.updateFromElasticSearch(response); // Do it after to ensure we have the most up to date information this.originalBody = this.prepBody(); diff --git a/src/plugins/data/common/search/aggs/agg_type.test.ts b/src/plugins/data/common/search/aggs/agg_type.test.ts index 2fcc6b97b1cc6..bf1136159dfe8 100644 --- a/src/plugins/data/common/search/aggs/agg_type.test.ts +++ b/src/plugins/data/common/search/aggs/agg_type.test.ts @@ -99,6 +99,17 @@ describe('AggType Class', () => { expect(aggType.params[1].name).toBe('customLabel'); }); + test('disables json param', () => { + const aggType = new AggType({ + name: 'name', + title: 'title', + json: false, + }); + + expect(aggType.params.length).toBe(1); + expect(aggType.params[0].name).toBe('customLabel'); + }); + test('can disable customLabel', () => { const aggType = new AggType({ name: 'smart agg', diff --git a/src/plugins/data/common/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts index 0ba2bb66e7758..2ee604c1bf25d 100644 --- a/src/plugins/data/common/search/aggs/agg_type.ts +++ b/src/plugins/data/common/search/aggs/agg_type.ts @@ -47,6 +47,7 @@ export interface AggTypeConfig< getRequestAggs?: ((aggConfig: TAggConfig) => TAggConfig[]) | (() => TAggConfig[] | void); getResponseAggs?: ((aggConfig: TAggConfig) => TAggConfig[]) | (() => TAggConfig[] | void); customLabels?: boolean; + json?: boolean; decorateAggConfig?: () => any; postFlightRequest?: ( resp: any, @@ -235,13 +236,17 @@ export class AggType< if (config.params && config.params.length && config.params[0] instanceof BaseParamType) { this.params = config.params as TParam[]; } else { - // always append the raw JSON param + // always append the raw JSON param unless it is configured to false const params: any[] = config.params ? [...config.params] : []; - params.push({ - name: 'json', - type: 'json', - advanced: true, - }); + + if (config.json !== false) { + params.push({ + name: 'json', + type: 'json', + advanced: true, + }); + } + // always append custom label if (config.customLabels !== false) { diff --git a/src/plugins/data/common/search/aggs/buckets/_interval_options.ts b/src/plugins/data/common/search/aggs/buckets/_interval_options.ts index 00cf50c272fa0..f94484a6edc2e 100644 --- a/src/plugins/data/common/search/aggs/buckets/_interval_options.ts +++ b/src/plugins/data/common/search/aggs/buckets/_interval_options.ts @@ -20,12 +20,15 @@ import { i18n } from '@kbn/i18n'; import { IBucketAggConfig } from './bucket_agg_type'; +export const autoInterval = 'auto'; +export const isAutoInterval = (value: unknown) => value === autoInterval; + export const intervalOptions = [ { display: i18n.translate('data.search.aggs.buckets.intervalOptions.autoDisplayName', { defaultMessage: 'Auto', }), - val: 'auto', + val: autoInterval, enabled(agg: IBucketAggConfig) { // not only do we need a time field, but the selected field needs // to be the time field. (see #3028) diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.test.ts index 143d549836900..3d0224b213e8d 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -19,7 +19,7 @@ import moment from 'moment'; import { createFilterDateHistogram } from './date_histogram'; -import { intervalOptions } from '../_interval_options'; +import { intervalOptions, autoInterval } from '../_interval_options'; import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { IBucketDateHistogramAggConfig } from '../date_histogram'; @@ -33,7 +33,10 @@ describe('AggConfig Filters', () => { let bucketStart: any; let field: any; - const init = (interval: string = 'auto', duration: any = moment.duration(15, 'minutes')) => { + const init = ( + interval: string = autoInterval, + duration: any = moment.duration(15, 'minutes') + ) => { field = { name: 'date', }; diff --git a/src/plugins/data/common/search/aggs/buckets/date_histogram.ts b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts index fdf9c456b3876..c273ca53a5fed 100644 --- a/src/plugins/data/common/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES, TimeRange, TimeRangeBounds, UI_SETTINGS } from '../../../../common'; -import { intervalOptions } from './_interval_options'; +import { intervalOptions, autoInterval, isAutoInterval } from './_interval_options'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -44,7 +44,7 @@ const updateTimeBuckets = ( customBuckets?: IBucketDateHistogramAggConfig['buckets'] ) => { const bounds = - agg.params.timeRange && (agg.fieldIsTimeField() || agg.params.interval === 'auto') + agg.params.timeRange && (agg.fieldIsTimeField() || isAutoInterval(agg.params.interval)) ? calculateBounds(agg.params.timeRange) : undefined; const buckets = customBuckets || agg.buckets; @@ -149,7 +149,7 @@ export const getDateHistogramBucketAgg = ({ return agg.getIndexPattern().timeFieldName; }, onChange(agg: IBucketDateHistogramAggConfig) { - if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { + if (isAutoInterval(get(agg, 'params.interval')) && !agg.fieldIsTimeField()) { delete agg.params.interval; } }, @@ -187,7 +187,7 @@ export const getDateHistogramBucketAgg = ({ } return state; }, - default: 'auto', + default: autoInterval, options: intervalOptions, write(agg, output, aggs) { updateTimeBuckets(agg, calculateBounds); diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts index 3727747984d3e..a8ac72c174c72 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts @@ -103,7 +103,28 @@ describe('Histogram Agg', () => { }); }); + describe('maxBars', () => { + test('should not be written to the DSL', () => { + const aggConfigs = getAggConfigs({ + maxBars: 50, + field: { + name: 'field', + }, + }); + const { [BUCKET_TYPES.HISTOGRAM]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params).not.toHaveProperty('maxBars'); + }); + }); + describe('interval', () => { + test('accepts "auto" value', () => { + const params = getParams({ + interval: 'auto', + }); + + expect(params).toHaveProperty('interval', 1); + }); test('accepts a whole number', () => { const params = getParams({ interval: 100, diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.ts b/src/plugins/data/common/search/aggs/buckets/histogram.ts index 2b263013e55a2..4b631e1fd7cd7 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram.ts @@ -28,6 +28,8 @@ import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { BUCKET_TYPES } from './bucket_agg_types'; import { ExtendedBounds } from './lib/extended_bounds'; +import { isAutoInterval, autoInterval } from './_interval_options'; +import { calculateHistogramInterval } from './lib/histogram_calculate_interval'; export interface AutoBounds { min: number; @@ -47,6 +49,7 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { export interface AggParamsHistogram extends BaseAggParams { field: string; interval: string; + maxBars?: number; intervalBase?: number; min_doc_count?: boolean; has_extended_bounds?: boolean; @@ -102,6 +105,7 @@ export const getHistogramBucketAgg = ({ }, { name: 'interval', + default: autoInterval, modifyAggConfigOnSearchRequestStart( aggConfig: IBucketHistogramAggConfig, searchSource: any, @@ -127,9 +131,12 @@ export const getHistogramBucketAgg = ({ return childSearchSource .fetch(options) .then((resp: any) => { + const min = resp.aggregations?.minAgg?.value ?? 0; + const max = resp.aggregations?.maxAgg?.value ?? 0; + aggConfig.setAutoBounds({ - min: get(resp, 'aggregations.minAgg.value'), - max: get(resp, 'aggregations.maxAgg.value'), + min, + max, }); }) .catch((e: Error) => { @@ -143,46 +150,24 @@ export const getHistogramBucketAgg = ({ }); }, write(aggConfig, output) { - let interval = parseFloat(aggConfig.params.interval); - if (interval <= 0) { - interval = 1; - } - const autoBounds = aggConfig.getAutoBounds(); - - // ensure interval does not create too many buckets and crash browser - if (autoBounds) { - const range = autoBounds.max - autoBounds.min; - const bars = range / interval; - - if (bars > getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS)) { - const minInterval = range / getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS); - - // Round interval by order of magnitude to provide clean intervals - // Always round interval up so there will always be less buckets than histogram:maxBars - const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); - let roundInterval = orderOfMagnitude; - - while (roundInterval < minInterval) { - roundInterval += orderOfMagnitude; - } - interval = roundInterval; - } - } - const base = aggConfig.params.intervalBase; - - if (base) { - if (interval < base) { - // In case the specified interval is below the base, just increase it to it's base - interval = base; - } else if (interval % base !== 0) { - // In case the interval is not a multiple of the base round it to the next base - interval = Math.round(interval / base) * base; - } - } - - output.params.interval = interval; + const values = aggConfig.getAutoBounds(); + + output.params.interval = calculateHistogramInterval({ + values, + interval: aggConfig.params.interval, + maxBucketsUiSettings: getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS), + maxBucketsUserInput: aggConfig.params.maxBars, + intervalBase: aggConfig.params.intervalBase, + }); }, }, + { + name: 'maxBars', + shouldShow(agg) { + return isAutoInterval(get(agg, 'params.interval')); + }, + write: () => {}, + }, { name: 'min_doc_count', default: false, diff --git a/src/plugins/data/common/search/aggs/buckets/histogram_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/histogram_fn.test.ts index 34b6fa1a6dcd6..354946f99a2f5 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram_fn.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram_fn.test.ts @@ -43,6 +43,7 @@ describe('agg_expression_functions', () => { "interval": "10", "intervalBase": undefined, "json": undefined, + "maxBars": undefined, "min_doc_count": undefined, }, "schema": undefined, @@ -55,8 +56,9 @@ describe('agg_expression_functions', () => { test('includes optional params when they are provided', () => { const actual = fn({ field: 'field', - interval: '10', + interval: 'auto', intervalBase: 1, + maxBars: 25, min_doc_count: false, has_extended_bounds: false, extended_bounds: JSON.stringify({ @@ -77,9 +79,10 @@ describe('agg_expression_functions', () => { }, "field": "field", "has_extended_bounds": false, - "interval": "10", + "interval": "auto", "intervalBase": 1, "json": undefined, + "maxBars": 25, "min_doc_count": false, }, "schema": undefined, diff --git a/src/plugins/data/common/search/aggs/buckets/histogram_fn.ts b/src/plugins/data/common/search/aggs/buckets/histogram_fn.ts index 877fd13e59f87..2e833bbe0a3eb 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram_fn.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram_fn.ts @@ -85,6 +85,12 @@ export const aggHistogram = (): FunctionDefinition => ({ defaultMessage: 'Specifies whether to use min_doc_count for this aggregation', }), }, + maxBars: { + types: ['number'], + help: i18n.translate('data.search.aggs.buckets.histogram.maxBars.help', { + defaultMessage: 'Calculate interval to get approximately this many bars', + }), + }, has_extended_bounds: { types: ['boolean'], help: i18n.translate('data.search.aggs.buckets.histogram.hasExtendedBounds.help', { diff --git a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts new file mode 100644 index 0000000000000..fd788d3339295 --- /dev/null +++ b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts @@ -0,0 +1,144 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + calculateHistogramInterval, + CalculateHistogramIntervalParams, +} from './histogram_calculate_interval'; + +describe('calculateHistogramInterval', () => { + describe('auto calculating mode', () => { + let params: CalculateHistogramIntervalParams; + + beforeEach(() => { + params = { + interval: 'auto', + intervalBase: undefined, + maxBucketsUiSettings: 100, + maxBucketsUserInput: undefined, + values: { + min: 0, + max: 1, + }, + }; + }); + + describe('maxBucketsUserInput is defined', () => { + test('should not set interval which more than largest possible', () => { + const p = { + ...params, + maxBucketsUserInput: 200, + values: { + min: 150, + max: 250, + }, + }; + expect(calculateHistogramInterval(p)).toEqual(1); + }); + + test('should correctly work for float numbers (small numbers)', () => { + expect( + calculateHistogramInterval({ + ...params, + maxBucketsUserInput: 50, + values: { + min: 0.1, + max: 0.9, + }, + }) + ).toBe(0.02); + }); + + test('should correctly work for float numbers (big numbers)', () => { + expect( + calculateHistogramInterval({ + ...params, + maxBucketsUserInput: 10, + values: { + min: 10.45, + max: 1000.05, + }, + }) + ).toBe(100); + }); + }); + + describe('maxBucketsUserInput is not defined', () => { + test('should not set interval which more than largest possible', () => { + expect( + calculateHistogramInterval({ + ...params, + values: { + min: 0, + max: 100, + }, + }) + ).toEqual(1); + }); + + test('should set intervals for integer numbers (diff less than maxBucketsUiSettings)', () => { + expect( + calculateHistogramInterval({ + ...params, + values: { + min: 1, + max: 10, + }, + }) + ).toEqual(0.1); + }); + + test('should set intervals for integer numbers (diff more than maxBucketsUiSettings)', () => { + // diff === 44445; interval === 500; buckets === 89 + expect( + calculateHistogramInterval({ + ...params, + values: { + min: 45678, + max: 90123, + }, + }) + ).toEqual(500); + }); + + test('should set intervals the same for the same interval', () => { + // both diffs are the same + // diff === 1.655; interval === 0.02; buckets === 82 + expect( + calculateHistogramInterval({ + ...params, + values: { + min: 1.245, + max: 2.9, + }, + }) + ).toEqual(0.02); + expect( + calculateHistogramInterval({ + ...params, + values: { + min: 0.5, + max: 2.3, + }, + }) + ).toEqual(0.02); + }); + }); + }); +}); diff --git a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts new file mode 100644 index 0000000000000..f4e42fa8881ef --- /dev/null +++ b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts @@ -0,0 +1,143 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isAutoInterval } from '../_interval_options'; + +interface IntervalValuesRange { + min: number; + max: number; +} + +export interface CalculateHistogramIntervalParams { + interval: string; + maxBucketsUiSettings: number; + maxBucketsUserInput?: number; + intervalBase?: number; + values?: IntervalValuesRange; +} + +/** + * Round interval by order of magnitude to provide clean intervals + */ +const roundInterval = (minInterval: number) => { + const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); + let interval = orderOfMagnitude; + + while (interval < minInterval) { + interval += orderOfMagnitude; + } + + return interval; +}; + +const calculateForGivenInterval = ( + diff: number, + interval: number, + maxBucketsUiSettings: CalculateHistogramIntervalParams['maxBucketsUiSettings'] +) => { + const bars = diff / interval; + + if (bars > maxBucketsUiSettings) { + const minInterval = diff / maxBucketsUiSettings; + + return roundInterval(minInterval); + } + + return interval; +}; + +/** + * Algorithm for determining auto-interval + + 1. Define maxBars as Math.min(, ) + 2. Find the min and max values in the data + 3. Subtract the min from max to get diff + 4. Set exactInterval to diff / maxBars + 5. Based on exactInterval, find the power of 10 that's lower and higher + 6. Find the number of expected buckets that lowerPower would create: diff / lowerPower + 7. Find the number of expected buckets that higherPower would create: diff / higherPower + 8. There are three possible final intervals, pick the one that's closest to maxBars: + - The lower power of 10 + - The lower power of 10, times 2 + - The lower power of 10, times 5 + **/ +const calculateAutoInterval = ( + diff: number, + maxBucketsUiSettings: CalculateHistogramIntervalParams['maxBucketsUiSettings'], + maxBucketsUserInput: CalculateHistogramIntervalParams['maxBucketsUserInput'] +) => { + const maxBars = Math.min(maxBucketsUiSettings, maxBucketsUserInput ?? maxBucketsUiSettings); + const exactInterval = diff / maxBars; + + const lowerPower = Math.pow(10, Math.floor(Math.log10(exactInterval))); + + const autoBuckets = diff / lowerPower; + + if (autoBuckets > maxBars) { + if (autoBuckets / 2 <= maxBars) { + return lowerPower * 2; + } else if (autoBuckets / 5 <= maxBars) { + return lowerPower * 5; + } else { + return lowerPower * 10; + } + } + + return lowerPower; +}; + +export const calculateHistogramInterval = ({ + interval, + maxBucketsUiSettings, + maxBucketsUserInput, + intervalBase, + values, +}: CalculateHistogramIntervalParams) => { + const isAuto = isAutoInterval(interval); + let calculatedInterval = isAuto ? 0 : parseFloat(interval); + + // should return NaN on non-numeric or invalid values + if (Number.isNaN(calculatedInterval)) { + return calculatedInterval; + } + + if (values) { + const diff = values.max - values.min; + + if (diff) { + calculatedInterval = isAuto + ? calculateAutoInterval(diff, maxBucketsUiSettings, maxBucketsUserInput) + : calculateForGivenInterval(diff, calculatedInterval, maxBucketsUiSettings); + } + } + + if (intervalBase) { + if (calculatedInterval < intervalBase) { + // In case the specified interval is below the base, just increase it to it's base + calculatedInterval = intervalBase; + } else if (calculatedInterval % intervalBase !== 0) { + // In case the interval is not a multiple of the base round it to the next base + calculatedInterval = Math.round(calculatedInterval / intervalBase) * intervalBase; + } + } + + const defaultValueForUnspecifiedInterval = 1; + + return calculatedInterval || defaultValueForUnspecifiedInterval; +}; diff --git a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts index ae7630ecd3dac..04e64233ce196 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts @@ -20,6 +20,7 @@ import moment from 'moment'; import { TimeBuckets, TimeBucketsConfig } from './time_buckets'; +import { autoInterval } from '../../_interval_options'; describe('TimeBuckets', () => { const timeBucketConfig: TimeBucketsConfig = { @@ -103,7 +104,7 @@ describe('TimeBuckets', () => { test('setInterval/getInterval - intreval is a "auto"', () => { const timeBuckets = new TimeBuckets(timeBucketConfig); - timeBuckets.setInterval('auto'); + timeBuckets.setInterval(autoInterval); const interval = timeBuckets.getInterval(); expect(interval.description).toEqual('0 milliseconds'); diff --git a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.ts index 6402a6e83ead9..d054df0c9274e 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.ts @@ -28,6 +28,7 @@ import { convertIntervalToEsInterval, EsInterval, } from './calc_es_interval'; +import { autoInterval } from '../../_interval_options'; interface TimeBucketsInterval extends moment.Duration { // TODO double-check whether all of these are needed @@ -189,8 +190,8 @@ export class TimeBuckets { interval = input.val; } - if (!interval || interval === 'auto') { - this._i = 'auto'; + if (!interval || interval === autoInterval) { + this._i = autoInterval; return; } diff --git a/src/plugins/data/common/search/aggs/metrics/count.ts b/src/plugins/data/common/search/aggs/metrics/count.ts index d990599586e81..9c9f36651f4d2 100644 --- a/src/plugins/data/common/search/aggs/metrics/count.ts +++ b/src/plugins/data/common/search/aggs/metrics/count.ts @@ -28,6 +28,7 @@ export const getCountMetricAgg = () => defaultMessage: 'Count', }), hasNoDsl: true, + json: false, makeLabel() { return i18n.translate('data.search.aggs.metrics.countLabel', { defaultMessage: 'Count', diff --git a/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts index 846feb9296fca..32189f07581e6 100644 --- a/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts @@ -34,7 +34,6 @@ describe('agg_expression_functions', () => { "id": undefined, "params": Object { "customLabel": undefined, - "json": undefined, }, "schema": undefined, "type": "count", @@ -42,18 +41,5 @@ describe('agg_expression_functions', () => { } `); }); - - test('correctly parses json string argument', () => { - const actual = fn({ - json: '{ "foo": true }', - }); - - expect(actual.value.params.json).toEqual({ foo: true }); - expect(() => { - fn({ - json: '/// intentionally malformed json ///', - }); - }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); - }); }); }); diff --git a/src/plugins/data/common/search/aggs/metrics/count_fn.ts b/src/plugins/data/common/search/aggs/metrics/count_fn.ts index 338ca18209299..7d4616ffdc619 100644 --- a/src/plugins/data/common/search/aggs/metrics/count_fn.ts +++ b/src/plugins/data/common/search/aggs/metrics/count_fn.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { AggExpressionType, AggExpressionFunctionArgs, METRIC_TYPES } from '../'; -import { getParsedValue } from '../utils/get_parsed_value'; const fnName = 'aggCount'; @@ -55,12 +54,6 @@ export const aggCount = (): FunctionDefinition => ({ defaultMessage: 'Schema to use for this aggregation', }), }, - json: { - types: ['string'], - help: i18n.translate('data.search.aggs.metrics.count.json.help', { - defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', - }), - }, customLabel: { types: ['string'], help: i18n.translate('data.search.aggs.metrics.count.customLabel.help', { @@ -78,10 +71,7 @@ export const aggCount = (): FunctionDefinition => ({ enabled, schema, type: METRIC_TYPES.COUNT, - params: { - ...rest, - json: getParsedValue(args, 'json'), - }, + params: rest, }, }; }, diff --git a/src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts b/src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts index 622e8101f34ab..3637ded44c50a 100644 --- a/src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts +++ b/src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts @@ -21,6 +21,7 @@ import { UI_SETTINGS } from '../../../../common/constants'; import { TimeRange } from '../../../../common/query'; import { TimeBuckets } from '../buckets/lib/time_buckets'; import { toAbsoluteDates } from './date_interval_utils'; +import { autoInterval } from '../buckets/_interval_options'; export function getCalculateAutoTimeExpression(getConfig: (key: string) => any) { return function calculateAutoTimeExpression(range: TimeRange) { @@ -36,7 +37,7 @@ export function getCalculateAutoTimeExpression(getConfig: (key: string) => any) 'dateFormat:scaled': getConfig('dateFormat:scaled'), }); - buckets.setInterval('auto'); + buckets.setInterval(autoInterval); buckets.setBounds({ min: moment(dates.from), max: moment(dates.to), diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts index 51f4fc7ce94b9..5e0eeaee3c0d0 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts @@ -22,5 +22,3 @@ import { setup } from 'test_utils/http_test_setup'; export const { http } = setup((injectedMetadata) => { injectedMetadata.getBasePath.mockReturnValue('/hola/daro/'); }); - -jest.doMock('ui/new_platform', () => ({ npSetup: { core: { http } } })); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index de4ec58dfdab3..0c4465ae7f4b9 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1006,7 +1006,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) id?: string; // (undocumented) - init(forceFieldRefresh?: boolean): Promise; + init(): Promise; // Warning: (ae-forgotten-export) The symbol "IndexPatternSpec" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1508,7 +1508,7 @@ export interface QueryState { // Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const QueryStringInput: React.FC>; +export const QueryStringInput: React.FC>; // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 86ee98b7af9d8..2d311fd88eb39 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -17,8 +17,7 @@ * under the License. */ -import { Component } from 'react'; -import React from 'react'; +import React, { Component, RefObject, createRef } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -30,6 +29,7 @@ import { EuiButton, EuiLink, htmlIdGenerator, + EuiPortal, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -42,6 +42,7 @@ import { withKibana, KibanaReactContextValue, toMountPoint } from '../../../../k import { fetchIndexPatterns } from './fetch_index_patterns'; import { QueryLanguageSwitcher } from './language_switcher'; import { PersistedLog, getQueryLog, matchPairs, toUser, fromUser } from '../../query'; +import { SuggestionsListSize } from '../typeahead/suggestions_component'; import { SuggestionsComponent } from '..'; interface Props { @@ -60,6 +61,7 @@ interface Props { onChangeQueryInputFocus?: (isFocused: boolean) => void; onSubmit?: (query: Query) => void; dataTestSubj?: string; + size?: SuggestionsListSize; } interface State { @@ -70,6 +72,7 @@ interface State { selectionStart: number | null; selectionEnd: number | null; indexPatterns: IIndexPattern[]; + queryBarRect: DOMRect | undefined; } const KEY_CODES = { @@ -93,6 +96,7 @@ export class QueryStringInputUI extends Component { selectionStart: null, selectionEnd: null, indexPatterns: [], + queryBarRect: undefined, }; public inputRef: HTMLTextAreaElement | null = null; @@ -101,6 +105,7 @@ export class QueryStringInputUI extends Component { private abortController?: AbortController; private services = this.props.kibana.services; private componentIsUnmounting = false; + private queryBarInputDivRefInstance: RefObject = createRef(); private getQueryString = () => { return toUser(this.props.query.query); @@ -494,8 +499,13 @@ export class QueryStringInputUI extends Component { this.initPersistedLog(); this.fetchIndexPatterns().then(this.updateSuggestions); + this.handleListUpdate(); window.addEventListener('resize', this.handleAutoHeight); + window.addEventListener('scroll', this.handleListUpdate, { + passive: true, // for better performance as we won't call preventDefault + capture: true, // scroll events don't bubble, they must be captured instead + }); } public componentDidUpdate(prevProps: Props) { @@ -533,12 +543,19 @@ export class QueryStringInputUI extends Component { this.updateSuggestions.cancel(); this.componentIsUnmounting = true; window.removeEventListener('resize', this.handleAutoHeight); + window.removeEventListener('scroll', this.handleListUpdate); } + handleListUpdate = () => + this.setState({ + queryBarRect: this.queryBarInputDivRefInstance.current?.getBoundingClientRect(), + }); + handleAutoHeight = () => { if (this.inputRef !== null && document.activeElement === this.inputRef) { this.inputRef.style.setProperty('height', `${this.inputRef.scrollHeight}px`, 'important'); } + this.handleListUpdate(); }; handleRemoveHeight = () => { @@ -587,6 +604,7 @@ export class QueryStringInputUI extends Component {

- - + + + diff --git a/src/plugins/data/public/ui/typeahead/__snapshots__/suggestions_component.test.tsx.snap b/src/plugins/data/public/ui/typeahead/__snapshots__/suggestions_component.test.tsx.snap index 3b51c1db50d00..2fa7834872f6b 100644 --- a/src/plugins/data/public/ui/typeahead/__snapshots__/suggestions_component.test.tsx.snap +++ b/src/plugins/data/public/ui/typeahead/__snapshots__/suggestions_component.test.tsx.snap @@ -1,17 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SuggestionsComponent Passing the index should control which suggestion is selected 1`] = ` -
-
+ `; exports[`SuggestionsComponent Should display given suggestions if the show prop is true 1`] = ` -
-
+ `; diff --git a/src/plugins/data/public/ui/typeahead/_suggestion.scss b/src/plugins/data/public/ui/typeahead/_suggestion.scss index 81c05f1a8a78c..67ff17d017053 100644 --- a/src/plugins/data/public/ui/typeahead/_suggestion.scss +++ b/src/plugins/data/public/ui/typeahead/_suggestion.scss @@ -6,28 +6,36 @@ $kbnTypeaheadTypes: ( conjunction: $euiColorVis3, ); +.kbnTypeahead.kbnTypeahead--small { + max-height: 20vh; +} + +.kbnTypeahead__popover--top { + @include euiBottomShadowFlat; + border-top-left-radius: $euiBorderRadius; + border-top-right-radius: $euiBorderRadius; +} + +.kbnTypeahead__popover--bottom { + @include euiBottomShadow($adjustBorders: true); + border-bottom-left-radius: $euiBorderRadius; + border-bottom-right-radius: $euiBorderRadius; +} + .kbnTypeahead { - position: relative; + max-height: 60vh; .kbnTypeahead__popover { - @include euiBottomShadow($adjustBorders: true); + max-height: inherit; + @include euiScrollBar; border: 1px solid; border-color: $euiBorderColor; color: $euiTextColor; background-color: $euiColorEmptyShade; - position: absolute; - top: -2px; + position: relative; z-index: $euiZContentMenu; width: 100%; - border-bottom-left-radius: $euiBorderRadius; - border-bottom-right-radius: $euiBorderRadius; - - .kbnTypeahead__items { - @include euiScrollBar; - - max-height: 60vh; - overflow-y: auto; - } + overflow-y: auto; .kbnTypeahead__item { height: $euiSizeXL; diff --git a/src/legacy/ui/public/notify/toasts/toast_notifications.ts b/src/plugins/data/public/ui/typeahead/constants.ts similarity index 64% rename from src/legacy/ui/public/notify/toasts/toast_notifications.ts rename to src/plugins/data/public/ui/typeahead/constants.ts index d3ec8edb5d73a..08f9bd23e16f3 100644 --- a/src/legacy/ui/public/notify/toasts/toast_notifications.ts +++ b/src/plugins/data/public/ui/typeahead/constants.ts @@ -16,7 +16,21 @@ * specific language governing permissions and limitations * under the License. */ + /** - * ToastNotifications is deprecated! Please use npSetup.core.notifications.toasts instead + * Minimum width in px to display suggestion description correctly + * @public */ -export { ToastNotifications } from '../../../../../plugins/kibana_legacy/public'; +export const SUGGESTIONS_LIST_REQUIRED_WIDTH = 600; + +/** + * Minimum bottom distance in px to display list of suggestions + * @public + */ +export const SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE = 250; + +/** + * A distance in px to display suggestions list right under the query input without a gap + * @public + */ +export const SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET = 2; diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx index 9fe33b003527e..ba78bdd802601 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx @@ -44,6 +44,7 @@ describe('SuggestionComponent', () => { suggestion={mockSuggestion} innerRef={noop} ariaId={'suggestion-1'} + shouldDisplayDescription={true} /> ); @@ -59,6 +60,7 @@ describe('SuggestionComponent', () => { suggestion={mockSuggestion} innerRef={noop} ariaId={'suggestion-1'} + shouldDisplayDescription={true} /> ); @@ -79,6 +81,7 @@ describe('SuggestionComponent', () => { suggestion={mockSuggestion} innerRef={innerRefCallback} ariaId={'suggestion-1'} + shouldDisplayDescription={true} /> ); }); @@ -94,6 +97,7 @@ describe('SuggestionComponent', () => { suggestion={mockSuggestion} innerRef={noop} ariaId={'suggestion-1'} + shouldDisplayDescription={true} /> ); @@ -113,6 +117,7 @@ describe('SuggestionComponent', () => { suggestion={mockSuggestion} innerRef={noop} ariaId={'suggestion-1'} + shouldDisplayDescription={true} /> ); diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx index b859428e6ed7e..724287b874bf7 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx @@ -46,6 +46,7 @@ interface Props { suggestion: QuerySuggestion; innerRef: (node: HTMLDivElement) => void; ariaId: string; + shouldDisplayDescription: boolean; } export function SuggestionComponent(props: Props) { @@ -72,7 +73,9 @@ export function SuggestionComponent(props: Props) {
{props.suggestion.text}
-
{props.suggestion.description}
+ {props.shouldDisplayDescription && ( +
{props.suggestion.description}
+ )}
); diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx index 011a729c6a616..583940015c152 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx @@ -54,6 +54,7 @@ describe('SuggestionsComponent', () => { show={false} suggestions={mockSuggestions} loadMore={noop} + queryBarRect={{ top: 0 } as DOMRect} /> ); @@ -69,6 +70,7 @@ describe('SuggestionsComponent', () => { show={true} suggestions={[]} loadMore={noop} + queryBarRect={{ top: 0 } as DOMRect} /> ); @@ -84,6 +86,7 @@ describe('SuggestionsComponent', () => { show={true} suggestions={mockSuggestions} loadMore={noop} + queryBarRect={{ top: 0 } as DOMRect} /> ); @@ -100,6 +103,7 @@ describe('SuggestionsComponent', () => { show={true} suggestions={mockSuggestions} loadMore={noop} + queryBarRect={{ top: 0 } as DOMRect} /> ); @@ -116,6 +120,7 @@ describe('SuggestionsComponent', () => { show={true} suggestions={mockSuggestions} loadMore={noop} + queryBarRect={{ top: 0 } as DOMRect} /> ); @@ -134,6 +139,7 @@ describe('SuggestionsComponent', () => { show={true} suggestions={mockSuggestions} loadMore={noop} + queryBarRect={{ top: 0 } as DOMRect} /> ); diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index 77dd7dcec01ee..dc7c55374f1d5 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -19,8 +19,15 @@ import { isEmpty } from 'lodash'; import React, { Component } from 'react'; +import classNames from 'classnames'; +import styled from 'styled-components'; import { QuerySuggestion } from '../../autocomplete'; import { SuggestionComponent } from './suggestion_component'; +import { + SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE, + SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET, + SUGGESTIONS_LIST_REQUIRED_WIDTH, +} from './constants'; interface Props { index: number | null; @@ -29,18 +36,24 @@ interface Props { show: boolean; suggestions: QuerySuggestion[]; loadMore: () => void; + queryBarRect?: DOMRect; + size?: SuggestionsListSize; } +export type SuggestionsListSize = 's' | 'l'; + export class SuggestionsComponent extends Component { private childNodes: HTMLDivElement[] = []; private parentNode: HTMLDivElement | null = null; public render() { - if (!this.props.show || isEmpty(this.props.suggestions)) { + if (!this.props.queryBarRect || !this.props.show || isEmpty(this.props.suggestions)) { return null; } const suggestions = this.props.suggestions.map((suggestion, index) => { + const isDescriptionFittable = + this.props.queryBarRect!.width >= SUGGESTIONS_LIST_REQUIRED_WIDTH; return ( (this.childNodes[index] = node)} @@ -50,17 +63,38 @@ export class SuggestionsComponent extends Component { onMouseEnter={() => this.props.onMouseEnter(index)} ariaId={'suggestion-' + index} key={`${suggestion.type} - ${suggestion.text}`} + shouldDisplayDescription={isDescriptionFittable} /> ); }); + const documentHeight = document.documentElement.clientHeight || window.innerHeight; + const { queryBarRect } = this.props; + + // reflects if the suggestions list has enough space below to be opened down + const isSuggestionsListFittable = + documentHeight - (queryBarRect.top + queryBarRect.height) > + SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE; + const verticalListPosition = isSuggestionsListFittable + ? `top: ${window.scrollY + queryBarRect.bottom - SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET}px;` + : `bottom: ${documentHeight - (window.scrollY + queryBarRect.top)}px;`; + return ( -
-
-
+ +
+
(this.parentNode = node)} onScroll={this.handleScroll} @@ -69,7 +103,7 @@ export class SuggestionsComponent extends Component {
-
+ ); } @@ -116,3 +150,11 @@ export class SuggestionsComponent extends Component { } }; } + +const StyledSuggestionsListDiv = styled.div` + ${(props: { queryBarRect: DOMRect; verticalListPosition: string }) => ` + position: absolute; + left: ${props.queryBarRect.left}px; + width: ${props.queryBarRect.width}px; + ${props.verticalListPosition}`} +`; diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index 46f09a8ebb879..d3a54627b0240 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -90,6 +90,7 @@ function DevToolsWrapper({ devTools, activeDevTool, updateRoute }: DevToolsWrapp element, appBasePath: '', onAppLeave: () => undefined, + setHeaderActionMenu: () => undefined, // TODO: adapt to use Core's ScopedHistory history: {} as any, }; diff --git a/src/plugins/discover/public/application/components/field_name/field_name.test.tsx b/src/plugins/discover/public/application/components/field_name/field_name.test.tsx index 46be1044c0d4d..e6cf8a57686f1 100644 --- a/src/plugins/discover/public/application/components/field_name/field_name.test.tsx +++ b/src/plugins/discover/public/application/components/field_name/field_name.test.tsx @@ -20,8 +20,6 @@ import React from 'react'; import { render } from 'enzyme'; import { FieldName } from './field_name'; -jest.mock('ui/new_platform'); - // Note that it currently provides just 2 basic tests, there should be more, but // the components involved will soon change test('FieldName renders a string field by providing fieldType and fieldName', () => { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index a0d9e3c541e47..6d1238e02c7fb 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -80,11 +80,10 @@ function getComponent(selected = false, showDetails = false, useShortDots = fals const props = { indexPattern, field, - getDetails: jest.fn(), + getDetails: jest.fn(() => ({ buckets: [], error: '', exists: 1, total: true, columns: [] })), onAddFilter: jest.fn(), onAddField: jest.fn(), onRemoveField: jest.fn(), - onShowDetails: jest.fn(), showDetails, selected, useShortDots, @@ -104,4 +103,9 @@ describe('discover sidebar field', function () { findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); expect(props.onRemoveField).toHaveBeenCalledWith('bytes'); }); + it('should trigger getDetails', function () { + const { comp, props } = getComponent(true); + findTestSubject(comp, 'field-bytes-showDetails').simulate('click'); + expect(props.getDetails).toHaveBeenCalledWith(props.field); + }); }); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index 5d7daaa7217ed..f3c4cae720193 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -31,6 +31,7 @@ import { Action } from 'src/plugins/ui_actions/public'; import { PanelOptionsMenu } from './panel_options_menu'; import { IEmbeddable } from '../../embeddables'; import { EmbeddableContext, panelBadgeTrigger, panelNotificationTrigger } from '../../triggers'; +import { uiToReactComponent } from '../../../../../kibana_react/public'; export interface PanelHeaderProps { title?: string; @@ -65,7 +66,9 @@ function renderNotifications( return notifications.map((notification) => { const context = { embeddable }; - let badge = ( + let badge = notification.MenuItem ? ( + React.createElement(uiToReactComponent(notification.MenuItem)) + ) : ( { Array.from(getContents()).forEach(removeContent); } }; + // https://github.com/elastic/kibana/issues/73970 + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [removeContent]); return { ...ctx, addContent }; diff --git a/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx index 8c63cc8494a8b..7d21722781d60 100644 --- a/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx +++ b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx @@ -52,6 +52,8 @@ export const JsonEditor = React.memo( isControlled, }); + // https://github.com/elastic/kibana/issues/73971 + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const debouncedSetContent = useCallback(debounce(setContent, 300), [setContent]); // We let the consumer control the validation and the error message. @@ -76,6 +78,7 @@ export const JsonEditor = React.memo( debouncedSetContent(updated); } }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [isControlled] ); diff --git a/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts b/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts index 1b5ca5d7f4384..0ba39f5f05fe6 100644 --- a/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts +++ b/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts @@ -84,6 +84,8 @@ export const useJson = ({ } else { didMount.current = true; } + // https://github.com/elastic/kibana/issues/73971 + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [content]); return { diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index bdea5ccf5fe26..995ae0ba42837 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -39,7 +39,7 @@ export { UseRequestResponse, sendRequest, useRequest, -} from './request/np_ready_request'; +} from './request'; export { indices } from './indices'; diff --git a/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts b/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts index a6792543cd726..b28cac1cb76f5 100644 --- a/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts +++ b/src/plugins/es_ui_shared/public/indices/validate/validate_index.test.ts @@ -17,8 +17,6 @@ * under the License. */ -jest.mock('ui/new_platform'); - import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from '../constants'; import { diff --git a/src/plugins/es_ui_shared/public/request/index.ts b/src/plugins/es_ui_shared/public/request/index.ts index f942a9cc3932b..9e38bfe5ee16b 100644 --- a/src/plugins/es_ui_shared/public/request/index.ts +++ b/src/plugins/es_ui_shared/public/request/index.ts @@ -17,11 +17,5 @@ * under the License. */ -export { - SendRequestConfig, - SendRequestResponse, - UseRequestConfig, - UseRequestResponse, - sendRequest, - useRequest, -} from './request'; +export { SendRequestConfig, SendRequestResponse, sendRequest } from './send_request'; +export { UseRequestConfig, UseRequestResponse, useRequest } from './use_request'; diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts deleted file mode 100644 index 06af698f2ce02..0000000000000 --- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { useEffect, useState, useRef, useMemo } from 'react'; - -import { HttpSetup, HttpFetchQuery } from '../../../../../src/core/public'; - -export interface SendRequestConfig { - path: string; - method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; - query?: HttpFetchQuery; - body?: any; -} - -export interface SendRequestResponse { - data: D | null; - error: E | null; -} - -export interface UseRequestConfig extends SendRequestConfig { - pollIntervalMs?: number; - initialData?: any; - deserializer?: (data: any) => any; -} - -export interface UseRequestResponse { - isInitialRequest: boolean; - isLoading: boolean; - error: E | null; - data?: D | null; - sendRequest: (...args: any[]) => Promise>; -} - -export const sendRequest = async ( - httpClient: HttpSetup, - { path, method, body, query }: SendRequestConfig -): Promise> => { - try { - const stringifiedBody = typeof body === 'string' ? body : JSON.stringify(body); - const response = await httpClient[method](path, { body: stringifiedBody, query }); - - return { - data: response.data ? response.data : response, - error: null, - }; - } catch (e) { - return { - data: null, - error: e.response && e.response.data ? e.response.data : e.body, - }; - } -}; - -export const useRequest = ( - httpClient: HttpSetup, - { - path, - method, - query, - body, - pollIntervalMs, - initialData, - deserializer = (data: any): any => data, - }: UseRequestConfig -): UseRequestResponse => { - const sendRequestRef = useRef<() => Promise>>(); - // Main states for tracking request status and data - const [error, setError] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [data, setData] = useState(initialData); - - // Consumers can use isInitialRequest to implement a polling UX. - const [isInitialRequest, setIsInitialRequest] = useState(true); - const pollInterval = useRef(null); - const pollIntervalId = useRef(null); - - // We always want to use the most recently-set interval in scheduleRequest. - pollInterval.current = pollIntervalMs; - - // Tied to every render and bound to each request. - let isOutdatedRequest = false; - - const scheduleRequest = () => { - // Clear current interval - if (pollIntervalId.current) { - clearTimeout(pollIntervalId.current); - } - - // Set new interval - if (pollInterval.current) { - pollIntervalId.current = setTimeout( - () => (sendRequestRef.current ?? _sendRequest)(), - pollInterval.current - ); - } - }; - - const _sendRequest = async () => { - // We don't clear error or data, so it's up to the consumer to decide whether to display the - // "old" error/data or loading state when a new request is in-flight. - setIsLoading(true); - - const requestBody = { - path, - method, - query, - body, - }; - - const response = await sendRequest(httpClient, requestBody); - const { data: serializedResponseData, error: responseError } = response; - - // If an outdated request has resolved, DON'T update state, but DO allow the processData handler - // to execute side effects like update telemetry. - if (isOutdatedRequest) { - return { data: null, error: null }; - } - - setError(responseError); - - if (!responseError) { - const responseData = deserializer(serializedResponseData); - setData(responseData); - } - - setIsLoading(false); - setIsInitialRequest(false); - - // If we're on an interval, we need to schedule the next request. This also allows us to reset - // the interval if the user has manually requested the data, to avoid doubled-up requests. - scheduleRequest(); - - return { data: serializedResponseData, error: responseError }; - }; - - useEffect(() => { - sendRequestRef.current = _sendRequest; - }, [_sendRequest]); - - const stringifiedQuery = useMemo(() => JSON.stringify(query), [query]); - - useEffect(() => { - (sendRequestRef.current ?? _sendRequest)(); - // To be functionally correct we'd send a new request if the method, path, query or body changes. - // But it doesn't seem likely that the method will change and body is likely to be a new - // object even if its shape hasn't changed, so for now we're just watching the path and the query. - }, [path, stringifiedQuery]); - - useEffect(() => { - scheduleRequest(); - - // Clean up intervals and inflight requests and corresponding state changes - return () => { - isOutdatedRequest = true; - if (pollIntervalId.current) { - clearTimeout(pollIntervalId.current); - } - }; - }, [pollIntervalMs]); - - return { - isInitialRequest, - isLoading, - error, - data, - sendRequest: sendRequestRef.current ?? _sendRequest, // Gives the user the ability to manually request data - }; -}; diff --git a/src/plugins/es_ui_shared/public/request/request.test.js b/src/plugins/es_ui_shared/public/request/request.test.js deleted file mode 100644 index 190c32517eefe..0000000000000 --- a/src/plugins/es_ui_shared/public/request/request.test.js +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -// import { sendRequest as sendRequestUnbound, useRequest as useRequestUnbound } from './request'; - -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { mount } from 'enzyme'; - -const TestHook = ({ callback }) => { - callback(); - return null; -}; - -let element; - -const testHook = (callback) => { - element = mount(); -}; - -const wait = async (wait) => new Promise((resolve) => setTimeout(resolve, wait || 1)); - -// FLAKY: -// - https://github.com/elastic/kibana/issues/42561 -// - https://github.com/elastic/kibana/issues/42562 -// - https://github.com/elastic/kibana/issues/42563 -// - https://github.com/elastic/kibana/issues/42225 -describe.skip('request lib', () => { - const successRequest = { path: '/success', method: 'post', body: {} }; - const errorRequest = { path: '/error', method: 'post', body: {} }; - const successResponse = { statusCode: 200, data: { message: 'Success message' } }; - const errorResponse = { statusCode: 400, statusText: 'Error message' }; - - let sendPost; - let sendRequest; - let useRequest; - - /** - * - * commented out due to hooks being called regardless of skip - * https://github.com/facebook/jest/issues/8379 - - beforeEach(() => { - sendPost = sinon.stub(); - sendPost.withArgs(successRequest.path, successRequest.body).returns(successResponse); - sendPost.withArgs(errorRequest.path, errorRequest.body).throws(errorResponse); - - const httpClient = { - post: (...args) => { - return sendPost(...args); - }, - }; - - sendRequest = sendRequestUnbound.bind(null, httpClient); - useRequest = useRequestUnbound.bind(null, httpClient); - }); - - */ - - describe('sendRequest function', () => { - it('uses the provided path, method, and body to send the request', async () => { - const response = await sendRequest({ ...successRequest }); - sinon.assert.calledOnce(sendPost); - expect(response).toEqual({ data: successResponse.data, error: null }); - }); - - it('surfaces errors', async () => { - try { - await sendRequest({ ...errorRequest }); - } catch (e) { - sinon.assert.calledOnce(sendPost); - expect(e).toBe(errorResponse.error); - } - }); - }); - - describe('useRequest hook', () => { - let hook; - - function initUseRequest(config) { - act(() => { - testHook(() => { - hook = useRequest(config); - }); - }); - } - - describe('parameters', () => { - describe('path, method, body', () => { - it('is used to send the request', async () => { - initUseRequest({ ...successRequest }); - await wait(50); - expect(hook.data).toBe(successResponse.data); - }); - }); - - describe('pollIntervalMs', () => { - it('sends another request after the specified time has elapsed', async () => { - initUseRequest({ ...successRequest, pollIntervalMs: 10 }); - await wait(50); - // We just care that multiple requests have been sent out. We don't check the specific - // timing because that risks introducing flakiness into the tests, and it's unlikely - // we could break the implementation by getting the exact timing wrong. - expect(sendPost.callCount).toBeGreaterThan(1); - - // We have to manually clean up or else the interval will continue to fire requests, - // interfering with other tests. - element.unmount(); - }); - }); - - describe('initialData', () => { - it('sets the initial data value', () => { - initUseRequest({ ...successRequest, initialData: 'initialData' }); - expect(hook.data).toBe('initialData'); - }); - }); - - describe('deserializer', () => { - it('is called once the request resolves', async () => { - const deserializer = sinon.stub(); - initUseRequest({ ...successRequest, deserializer }); - sinon.assert.notCalled(deserializer); - - await wait(50); - sinon.assert.calledOnce(deserializer); - sinon.assert.calledWith(deserializer, successResponse.data); - }); - - it('processes data', async () => { - initUseRequest({ ...successRequest, deserializer: () => 'intercepted' }); - await wait(50); - expect(hook.data).toBe('intercepted'); - }); - }); - }); - - describe('state', () => { - describe('isInitialRequest', () => { - it('is true for the first request and false for subsequent requests', async () => { - initUseRequest({ ...successRequest }); - expect(hook.isInitialRequest).toBe(true); - - hook.sendRequest(); - await wait(50); - expect(hook.isInitialRequest).toBe(false); - }); - }); - - describe('isLoading', () => { - it('represents in-flight request status', async () => { - initUseRequest({ ...successRequest }); - expect(hook.isLoading).toBe(true); - - await wait(50); - expect(hook.isLoading).toBe(false); - }); - }); - - describe('error', () => { - it('surfaces errors from requests', async () => { - initUseRequest({ ...errorRequest }); - await wait(50); - expect(hook.error).toBe(errorResponse); - }); - - it('persists while a request is in-flight', async () => { - initUseRequest({ ...errorRequest }); - await wait(50); - hook.sendRequest(); - expect(hook.isLoading).toBe(true); - expect(hook.error).toBe(errorResponse); - }); - - it('is null when the request is successful', async () => { - initUseRequest({ ...successRequest }); - await wait(50); - expect(hook.isLoading).toBe(false); - expect(hook.error).toBeNull(); - }); - }); - - describe('data', () => { - it('surfaces payloads from requests', async () => { - initUseRequest({ ...successRequest }); - await wait(50); - expect(hook.data).toBe(successResponse.data); - }); - - it('persists while a request is in-flight', async () => { - initUseRequest({ ...successRequest }); - await wait(50); - hook.sendRequest(); - expect(hook.isLoading).toBe(true); - expect(hook.data).toBe(successResponse.data); - }); - - it('is null when the request fails', async () => { - initUseRequest({ ...errorRequest }); - await wait(50); - expect(hook.isLoading).toBe(false); - expect(hook.data).toBeNull(); - }); - }); - }); - - describe('callbacks', () => { - describe('sendRequest', () => { - it('sends the request', () => { - initUseRequest({ ...successRequest }); - sinon.assert.calledOnce(sendPost); - hook.sendRequest(); - sinon.assert.calledTwice(sendPost); - }); - - it('resets the pollIntervalMs', async () => { - initUseRequest({ ...successRequest, pollIntervalMs: 800 }); - await wait(200); // 200ms - hook.sendRequest(); - expect(sendPost.callCount).toBe(2); - - await wait(200); // 400ms - hook.sendRequest(); - - await wait(200); // 600ms - hook.sendRequest(); - - await wait(200); // 800ms - hook.sendRequest(); - - await wait(200); // 1000ms - hook.sendRequest(); - - // If sendRequest didn't reset the interval, the interval would have triggered another - // request by now, and the callCount would be 7. - expect(sendPost.callCount).toBe(6); - - // We have to manually clean up or else the interval will continue to fire requests, - // interfering with other tests. - element.unmount(); - }); - }); - }); - }); -}); diff --git a/src/plugins/es_ui_shared/public/request/request.ts b/src/plugins/es_ui_shared/public/request/request.ts deleted file mode 100644 index fd6980367136e..0000000000000 --- a/src/plugins/es_ui_shared/public/request/request.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { useEffect, useState, useRef } from 'react'; - -export interface SendRequestConfig { - path: string; - method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; - body?: any; -} - -export interface SendRequestResponse { - data: any; - error: Error | null; -} - -export interface UseRequestConfig extends SendRequestConfig { - pollIntervalMs?: number; - initialData?: any; - deserializer?: (data: any) => any; -} - -export interface UseRequestResponse { - isInitialRequest: boolean; - isLoading: boolean; - error: null | unknown; - data: any; - sendRequest: (...args: any[]) => Promise; -} - -export const sendRequest = async ( - httpClient: ng.IHttpService, - { path, method, body }: SendRequestConfig -): Promise => { - try { - const response = await (httpClient as any)[method](path, body); - - if (typeof response.data === 'undefined') { - throw new Error(response.statusText); - } - - return { data: response.data, error: null }; - } catch (e) { - return { - data: null, - error: e.response ? e.response : e, - }; - } -}; - -export const useRequest = ( - httpClient: ng.IHttpService, - { - path, - method, - body, - pollIntervalMs, - initialData, - deserializer = (data: any): any => data, - }: UseRequestConfig -): UseRequestResponse => { - // Main states for tracking request status and data - const [error, setError] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [data, setData] = useState(initialData); - - // Consumers can use isInitialRequest to implement a polling UX. - const [isInitialRequest, setIsInitialRequest] = useState(true); - const pollInterval = useRef(null); - const pollIntervalId = useRef(null); - - // We always want to use the most recently-set interval in scheduleRequest. - pollInterval.current = pollIntervalMs; - - // Tied to every render and bound to each request. - let isOutdatedRequest = false; - - const scheduleRequest = () => { - // Clear current interval - if (pollIntervalId.current) { - clearTimeout(pollIntervalId.current); - } - - // Set new interval - if (pollInterval.current) { - pollIntervalId.current = setTimeout(_sendRequest, pollInterval.current); - } - }; - - const _sendRequest = async () => { - // We don't clear error or data, so it's up to the consumer to decide whether to display the - // "old" error/data or loading state when a new request is in-flight. - setIsLoading(true); - - const requestBody = { - path, - method, - body, - }; - - const response = await sendRequest(httpClient, requestBody); - const { data: serializedResponseData, error: responseError } = response; - const responseData = deserializer(serializedResponseData); - - // If an outdated request has resolved, DON'T update state, but DO allow the processData handler - // to execute side effects like update telemetry. - if (isOutdatedRequest) { - return { data: null, error: null }; - } - - setError(responseError); - setData(responseData); - setIsLoading(false); - setIsInitialRequest(false); - - // If we're on an interval, we need to schedule the next request. This also allows us to reset - // the interval if the user has manually requested the data, to avoid doubled-up requests. - scheduleRequest(); - - return { data: serializedResponseData, error: responseError }; - }; - - useEffect(() => { - _sendRequest(); - // To be functionally correct we'd send a new request if the method, path, or body changes. - // But it doesn't seem likely that the method will change and body is likely to be a new - // object even if its shape hasn't changed, so for now we're just watching the path. - }, [path]); - - useEffect(() => { - scheduleRequest(); - - // Clean up intervals and inflight requests and corresponding state changes - return () => { - isOutdatedRequest = true; - if (pollIntervalId.current) { - clearTimeout(pollIntervalId.current); - } - }; - }, [pollIntervalMs]); - - return { - isInitialRequest, - isLoading, - error, - data, - sendRequest: _sendRequest, // Gives the user the ability to manually request data - }; -}; diff --git a/src/plugins/es_ui_shared/public/request/send_request.test.helpers.ts b/src/plugins/es_ui_shared/public/request/send_request.test.helpers.ts new file mode 100644 index 0000000000000..4312780e74c0f --- /dev/null +++ b/src/plugins/es_ui_shared/public/request/send_request.test.helpers.ts @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import sinon from 'sinon'; + +import { HttpSetup, HttpFetchOptions } from '../../../../../src/core/public'; +import { + SendRequestConfig, + SendRequestResponse, + sendRequest as originalSendRequest, +} from './send_request'; + +export interface SendRequestHelpers { + getSendRequestSpy: () => sinon.SinonStub; + sendSuccessRequest: () => Promise; + getSuccessResponse: () => SendRequestResponse; + sendErrorRequest: () => Promise; + getErrorResponse: () => SendRequestResponse; +} + +const successRequest: SendRequestConfig = { method: 'post', path: '/success', body: {} }; +const successResponse = { statusCode: 200, data: { message: 'Success message' } }; + +const errorValue = { statusCode: 400, statusText: 'Error message' }; +const errorRequest: SendRequestConfig = { method: 'post', path: '/error', body: {} }; +const errorResponse = { response: { data: errorValue } }; + +export const createSendRequestHelpers = (): SendRequestHelpers => { + const sendRequestSpy = sinon.stub(); + const httpClient = { + post: (path: string, options: HttpFetchOptions) => sendRequestSpy(path, options), + }; + const sendRequest = originalSendRequest.bind(null, httpClient as HttpSetup) as ( + config: SendRequestConfig + ) => Promise>; + + // Set up successful request helpers. + sendRequestSpy + .withArgs(successRequest.path, { + body: JSON.stringify(successRequest.body), + query: undefined, + }) + .resolves(successResponse); + const sendSuccessRequest = () => sendRequest({ ...successRequest }); + const getSuccessResponse = () => ({ data: successResponse.data, error: null }); + + // Set up failed request helpers. + sendRequestSpy + .withArgs(errorRequest.path, { + body: JSON.stringify(errorRequest.body), + query: undefined, + }) + .rejects(errorResponse); + const sendErrorRequest = () => sendRequest({ ...errorRequest }); + const getErrorResponse = () => ({ + data: null, + error: errorResponse.response.data, + }); + + return { + getSendRequestSpy: () => sendRequestSpy, + sendSuccessRequest, + getSuccessResponse, + sendErrorRequest, + getErrorResponse, + }; +}; diff --git a/src/plugins/es_ui_shared/public/request/send_request.test.ts b/src/plugins/es_ui_shared/public/request/send_request.test.ts new file mode 100644 index 0000000000000..e4deaeaba817e --- /dev/null +++ b/src/plugins/es_ui_shared/public/request/send_request.test.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import sinon from 'sinon'; + +import { SendRequestHelpers, createSendRequestHelpers } from './send_request.test.helpers'; + +describe('sendRequest function', () => { + let helpers: SendRequestHelpers; + + beforeEach(() => { + helpers = createSendRequestHelpers(); + }); + + it('uses the provided path, method, and body to send the request', async () => { + const { sendSuccessRequest, getSendRequestSpy, getSuccessResponse } = helpers; + + const response = await sendSuccessRequest(); + sinon.assert.calledOnce(getSendRequestSpy()); + expect(response).toEqual(getSuccessResponse()); + }); + + it('surfaces errors', async () => { + const { sendErrorRequest, getSendRequestSpy, getErrorResponse } = helpers; + + // For some reason sinon isn't throwing an error on rejection, as an awaited Promise normally would. + const error = await sendErrorRequest(); + sinon.assert.calledOnce(getSendRequestSpy()); + expect(error).toEqual(getErrorResponse()); + }); +}); diff --git a/src/plugins/es_ui_shared/public/request/send_request.ts b/src/plugins/es_ui_shared/public/request/send_request.ts new file mode 100644 index 0000000000000..453e91570cd85 --- /dev/null +++ b/src/plugins/es_ui_shared/public/request/send_request.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { HttpSetup, HttpFetchQuery } from '../../../../../src/core/public'; + +export interface SendRequestConfig { + path: string; + method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; + query?: HttpFetchQuery; + body?: any; +} + +export interface SendRequestResponse { + data: D | null; + error: E | null; +} + +export const sendRequest = async ( + httpClient: HttpSetup, + { path, method, body, query }: SendRequestConfig +): Promise> => { + try { + const stringifiedBody = typeof body === 'string' ? body : JSON.stringify(body); + const response = await httpClient[method](path, { body: stringifiedBody, query }); + + return { + data: response.data ? response.data : response, + error: null, + }; + } catch (e) { + return { + data: null, + error: e.response?.data ?? e.body, + }; + } +}; diff --git a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx new file mode 100644 index 0000000000000..0d6fd122ad22c --- /dev/null +++ b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx @@ -0,0 +1,184 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mount, ReactWrapper } from 'enzyme'; +import sinon from 'sinon'; + +import { HttpSetup, HttpFetchOptions } from '../../../../../src/core/public'; +import { SendRequestConfig, SendRequestResponse } from './send_request'; +import { useRequest, UseRequestResponse, UseRequestConfig } from './use_request'; + +export interface UseRequestHelpers { + advanceTime: (ms: number) => Promise; + completeRequest: () => Promise; + hookResult: UseRequestResponse; + getSendRequestSpy: () => sinon.SinonStub; + setupSuccessRequest: (overrides?: {}, requestTimings?: number[]) => void; + getSuccessResponse: () => SendRequestResponse; + setupErrorRequest: (overrides?: {}, requestTimings?: number[]) => void; + getErrorResponse: () => SendRequestResponse; + setErrorResponse: (overrides?: {}) => void; + setupErrorWithBodyRequest: (overrides?: {}) => void; + getErrorWithBodyResponse: () => SendRequestResponse; +} + +// Each request will take 1s to resolve. +export const REQUEST_TIME = 1000; + +const successRequest: SendRequestConfig = { method: 'post', path: '/success', body: {} }; +const successResponse = { statusCode: 200, data: { message: 'Success message' } }; + +const errorValue = { statusCode: 400, statusText: 'Error message' }; +const errorRequest: SendRequestConfig = { method: 'post', path: '/error', body: {} }; +const errorResponse = { response: { data: errorValue } }; + +const errorWithBodyRequest: SendRequestConfig = { + method: 'post', + path: '/errorWithBody', + body: {}, +}; +const errorWithBodyResponse = { body: errorValue }; + +export const createUseRequestHelpers = (): UseRequestHelpers => { + // The behavior we're testing involves state changes over time, so we need finer control over + // timing. + jest.useFakeTimers(); + + const flushPromiseJobQueue = async () => { + // See https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function + await Promise.resolve(); + }; + + const completeRequest = async () => { + await act(async () => { + jest.runAllTimers(); + await flushPromiseJobQueue(); + }); + }; + + const advanceTime = async (ms: number) => { + await act(async () => { + jest.advanceTimersByTime(ms); + await flushPromiseJobQueue(); + }); + }; + + let element: ReactWrapper; + // We'll use this object to observe the state of the hook and access its callback(s). + const hookResult = {} as UseRequestResponse; + const sendRequestSpy = sinon.stub(); + + const setupUseRequest = (config: UseRequestConfig, requestTimings?: number[]) => { + let requestCount = 0; + + const httpClient = { + post: (path: string, options: HttpFetchOptions) => { + return new Promise((resolve, reject) => { + // Increase the time it takes to resolve a request so we have time to inspect the hook + // as it goes through various states. + setTimeout(() => { + try { + resolve(sendRequestSpy(path, options)); + } catch (e) { + reject(e); + } + }, (requestTimings && requestTimings[requestCount++]) || REQUEST_TIME); + }); + }, + }; + + const TestComponent = ({ requestConfig }: { requestConfig: UseRequestConfig }) => { + const { isInitialRequest, isLoading, error, data, sendRequest } = useRequest( + httpClient as HttpSetup, + requestConfig + ); + + hookResult.isInitialRequest = isInitialRequest; + hookResult.isLoading = isLoading; + hookResult.error = error; + hookResult.data = data; + hookResult.sendRequest = sendRequest; + + return null; + }; + + act(() => { + element = mount(); + }); + }; + + // Set up successful request helpers. + sendRequestSpy + .withArgs(successRequest.path, { + body: JSON.stringify(successRequest.body), + query: undefined, + }) + .resolves(successResponse); + const setupSuccessRequest = (overrides = {}, requestTimings?: number[]) => + setupUseRequest({ ...successRequest, ...overrides }, requestTimings); + const getSuccessResponse = () => ({ data: successResponse.data, error: null }); + + // Set up failed request helpers. + sendRequestSpy + .withArgs(errorRequest.path, { + body: JSON.stringify(errorRequest.body), + query: undefined, + }) + .rejects(errorResponse); + const setupErrorRequest = (overrides = {}, requestTimings?: number[]) => + setupUseRequest({ ...errorRequest, ...overrides }, requestTimings); + const getErrorResponse = () => ({ + data: null, + error: errorResponse.response.data, + }); + // We'll use this to change a success response to an error response, to test how the state changes. + const setErrorResponse = (overrides = {}) => { + element.setProps({ requestConfig: { ...errorRequest, ...overrides } }); + }; + + // Set up failed request helpers with the alternative error shape. + sendRequestSpy + .withArgs(errorWithBodyRequest.path, { + body: JSON.stringify(errorWithBodyRequest.body), + query: undefined, + }) + .rejects(errorWithBodyResponse); + const setupErrorWithBodyRequest = (overrides = {}) => + setupUseRequest({ ...errorWithBodyRequest, ...overrides }); + const getErrorWithBodyResponse = () => ({ + data: null, + error: errorWithBodyResponse.body, + }); + + return { + advanceTime, + completeRequest, + hookResult, + getSendRequestSpy: () => sendRequestSpy, + setupSuccessRequest, + getSuccessResponse, + setupErrorRequest, + getErrorResponse, + setErrorResponse, + setupErrorWithBodyRequest, + getErrorWithBodyResponse, + }; +}; diff --git a/src/plugins/es_ui_shared/public/request/use_request.test.ts b/src/plugins/es_ui_shared/public/request/use_request.test.ts new file mode 100644 index 0000000000000..f7902218d9314 --- /dev/null +++ b/src/plugins/es_ui_shared/public/request/use_request.test.ts @@ -0,0 +1,353 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { act } from 'react-dom/test-utils'; +import sinon from 'sinon'; + +import { + UseRequestHelpers, + REQUEST_TIME, + createUseRequestHelpers, +} from './use_request.test.helpers'; + +describe('useRequest hook', () => { + let helpers: UseRequestHelpers; + + beforeEach(() => { + helpers = createUseRequestHelpers(); + }); + + describe('parameters', () => { + describe('path, method, body', () => { + it('is used to send the request', async () => { + const { setupSuccessRequest, completeRequest, hookResult, getSuccessResponse } = helpers; + setupSuccessRequest(); + await completeRequest(); + expect(hookResult.data).toBe(getSuccessResponse().data); + }); + }); + + describe('pollIntervalMs', () => { + it('sends another request after the specified time has elapsed', async () => { + const { setupSuccessRequest, advanceTime, getSendRequestSpy } = helpers; + setupSuccessRequest({ pollIntervalMs: REQUEST_TIME }); + + await advanceTime(REQUEST_TIME); + expect(getSendRequestSpy().callCount).toBe(1); + + // We need to advance (1) the pollIntervalMs and (2) the request time. + await advanceTime(REQUEST_TIME * 2); + expect(getSendRequestSpy().callCount).toBe(2); + + // We need to advance (1) the pollIntervalMs and (2) the request time. + await advanceTime(REQUEST_TIME * 2); + expect(getSendRequestSpy().callCount).toBe(3); + }); + }); + + describe('initialData', () => { + it('sets the initial data value', async () => { + const { setupSuccessRequest, completeRequest, hookResult, getSuccessResponse } = helpers; + setupSuccessRequest({ initialData: 'initialData' }); + expect(hookResult.data).toBe('initialData'); + + // The initial data value will be overwritten once the request resolves. + await completeRequest(); + expect(hookResult.data).toBe(getSuccessResponse().data); + }); + }); + + describe('deserializer', () => { + it('is called with the response once the request resolves', async () => { + const { setupSuccessRequest, completeRequest, getSuccessResponse } = helpers; + + const deserializer = sinon.stub(); + setupSuccessRequest({ deserializer }); + sinon.assert.notCalled(deserializer); + await completeRequest(); + + sinon.assert.calledOnce(deserializer); + sinon.assert.calledWith(deserializer, getSuccessResponse().data); + }); + + it('provides the data return value', async () => { + const { setupSuccessRequest, completeRequest, hookResult } = helpers; + setupSuccessRequest({ deserializer: () => 'intercepted' }); + await completeRequest(); + expect(hookResult.data).toBe('intercepted'); + }); + }); + }); + + describe('state', () => { + describe('isInitialRequest', () => { + it('is true for the first request and false for subsequent requests', async () => { + const { setupSuccessRequest, completeRequest, hookResult } = helpers; + setupSuccessRequest(); + expect(hookResult.isInitialRequest).toBe(true); + + hookResult.sendRequest(); + await completeRequest(); + expect(hookResult.isInitialRequest).toBe(false); + }); + }); + + describe('isLoading', () => { + it('represents in-flight request status', async () => { + const { setupSuccessRequest, completeRequest, hookResult } = helpers; + setupSuccessRequest(); + expect(hookResult.isLoading).toBe(true); + + await completeRequest(); + expect(hookResult.isLoading).toBe(false); + }); + }); + + describe('error', () => { + it('surfaces errors from requests', async () => { + const { setupErrorRequest, completeRequest, hookResult, getErrorResponse } = helpers; + setupErrorRequest(); + await completeRequest(); + expect(hookResult.error).toBe(getErrorResponse().error); + }); + + it('surfaces body-shaped errors from requests', async () => { + const { + setupErrorWithBodyRequest, + completeRequest, + hookResult, + getErrorWithBodyResponse, + } = helpers; + + setupErrorWithBodyRequest(); + await completeRequest(); + expect(hookResult.error).toBe(getErrorWithBodyResponse().error); + }); + + it('persists while a request is in-flight', async () => { + const { setupErrorRequest, completeRequest, hookResult, getErrorResponse } = helpers; + setupErrorRequest(); + await completeRequest(); + expect(hookResult.isLoading).toBe(false); + expect(hookResult.error).toBe(getErrorResponse().error); + + act(() => { + hookResult.sendRequest(); + }); + expect(hookResult.isLoading).toBe(true); + expect(hookResult.error).toBe(getErrorResponse().error); + }); + + it('is null when the request is successful', async () => { + const { setupSuccessRequest, completeRequest, hookResult } = helpers; + setupSuccessRequest(); + expect(hookResult.error).toBeNull(); + + await completeRequest(); + expect(hookResult.isLoading).toBe(false); + expect(hookResult.error).toBeNull(); + }); + }); + + describe('data', () => { + it('surfaces payloads from requests', async () => { + const { setupSuccessRequest, completeRequest, hookResult, getSuccessResponse } = helpers; + setupSuccessRequest(); + expect(hookResult.data).toBeUndefined(); + + await completeRequest(); + expect(hookResult.data).toBe(getSuccessResponse().data); + }); + + it('persists while a request is in-flight', async () => { + const { setupSuccessRequest, completeRequest, hookResult, getSuccessResponse } = helpers; + setupSuccessRequest(); + await completeRequest(); + expect(hookResult.isLoading).toBe(false); + expect(hookResult.data).toBe(getSuccessResponse().data); + + act(() => { + hookResult.sendRequest(); + }); + expect(hookResult.isLoading).toBe(true); + expect(hookResult.data).toBe(getSuccessResponse().data); + }); + + it('persists from last successful request when the next request fails', async () => { + const { + setupSuccessRequest, + completeRequest, + hookResult, + getErrorResponse, + setErrorResponse, + getSuccessResponse, + } = helpers; + + setupSuccessRequest(); + await completeRequest(); + expect(hookResult.isLoading).toBe(false); + expect(hookResult.error).toBeNull(); + expect(hookResult.data).toBe(getSuccessResponse().data); + + setErrorResponse(); + await completeRequest(); + expect(hookResult.isLoading).toBe(false); + expect(hookResult.error).toBe(getErrorResponse().error); + expect(hookResult.data).toBe(getSuccessResponse().data); + }); + }); + }); + + describe('callbacks', () => { + describe('sendRequest', () => { + it('sends the request', async () => { + const { setupSuccessRequest, completeRequest, hookResult, getSendRequestSpy } = helpers; + setupSuccessRequest(); + + await completeRequest(); + expect(getSendRequestSpy().callCount).toBe(1); + + await act(async () => { + hookResult.sendRequest(); + await completeRequest(); + }); + expect(getSendRequestSpy().callCount).toBe(2); + }); + + it('resets the pollIntervalMs', async () => { + const { setupSuccessRequest, advanceTime, hookResult, getSendRequestSpy } = helpers; + const DOUBLE_REQUEST_TIME = REQUEST_TIME * 2; + setupSuccessRequest({ pollIntervalMs: DOUBLE_REQUEST_TIME }); + + // The initial request resolves, and then we'll immediately send a new one manually... + await advanceTime(REQUEST_TIME); + expect(getSendRequestSpy().callCount).toBe(1); + act(() => { + hookResult.sendRequest(); + }); + + // The manual request resolves, and we'll send yet another one... + await advanceTime(REQUEST_TIME); + expect(getSendRequestSpy().callCount).toBe(2); + act(() => { + hookResult.sendRequest(); + }); + + // At this point, we've moved forward 3s. The poll is set at 2s. If sendRequest didn't + // reset the poll, the request call count would be 4, not 3. + await advanceTime(REQUEST_TIME); + expect(getSendRequestSpy().callCount).toBe(3); + }); + }); + }); + + describe('request behavior', () => { + it('outdated responses are ignored by poll requests', async () => { + const { + setupSuccessRequest, + setErrorResponse, + completeRequest, + hookResult, + getErrorResponse, + getSendRequestSpy, + } = helpers; + const DOUBLE_REQUEST_TIME = REQUEST_TIME * 2; + // Send initial request, which will have a longer round-trip time. + setupSuccessRequest({}, [DOUBLE_REQUEST_TIME]); + + // Send a new request, which will have a shorter round-trip time. + setErrorResponse(); + + // Complete both requests. + await completeRequest(); + + // Two requests were sent... + expect(getSendRequestSpy().callCount).toBe(2); + // ...but the error response is the one that takes precedence because it was *sent* more + // recently, despite the success response *returning* more recently. + expect(hookResult.error).toBe(getErrorResponse().error); + expect(hookResult.data).toBeUndefined(); + }); + + it(`outdated responses are ignored if there's a more recently-sent manual request`, async () => { + const { setupSuccessRequest, advanceTime, hookResult, getSendRequestSpy } = helpers; + + const HALF_REQUEST_TIME = REQUEST_TIME * 0.5; + setupSuccessRequest({ pollIntervalMs: REQUEST_TIME }); + + // Before the original request resolves, we make a manual sendRequest call. + await advanceTime(HALF_REQUEST_TIME); + expect(getSendRequestSpy().callCount).toBe(0); + act(() => { + hookResult.sendRequest(); + }); + + // The original quest resolves but it's been marked as outdated by the the manual sendRequest + // call "interrupts", so data is left undefined. + await advanceTime(HALF_REQUEST_TIME); + expect(getSendRequestSpy().callCount).toBe(1); + expect(hookResult.data).toBeUndefined(); + }); + + it(`changing pollIntervalMs doesn't trigger a new request`, async () => { + const { setupErrorRequest, setErrorResponse, completeRequest, getSendRequestSpy } = helpers; + const DOUBLE_REQUEST_TIME = REQUEST_TIME * 2; + // Send initial request. + setupErrorRequest({ pollIntervalMs: REQUEST_TIME }); + + // Setting a new poll will schedule a second request, but not send one immediately. + setErrorResponse({ pollIntervalMs: DOUBLE_REQUEST_TIME }); + + // Complete initial request. + await completeRequest(); + + // Complete scheduled poll request. + await completeRequest(); + expect(getSendRequestSpy().callCount).toBe(2); + }); + + it('when the path changes after a request is scheduled, the scheduled request is sent with that path', async () => { + const { + setupSuccessRequest, + completeRequest, + hookResult, + getErrorResponse, + setErrorResponse, + getSendRequestSpy, + } = helpers; + const DOUBLE_REQUEST_TIME = REQUEST_TIME * 2; + + // Sned first request and schedule a request, both with the success path. + setupSuccessRequest({ pollIntervalMs: DOUBLE_REQUEST_TIME }); + + // Change the path to the error path, sending a second request. pollIntervalMs is the same + // so the originally scheduled poll remains cheduled. + setErrorResponse({ pollIntervalMs: DOUBLE_REQUEST_TIME }); + + // Complete the initial request, the requests by the path change, and the scheduled poll request. + await completeRequest(); + await completeRequest(); + + // If the scheduled poll request was sent to the success path, we wouldn't have an error result. + // But we do, because it was sent to the error path. + expect(getSendRequestSpy().callCount).toBe(3); + expect(hookResult.error).toBe(getErrorResponse().error); + }); + }); +}); diff --git a/src/plugins/es_ui_shared/public/request/use_request.ts b/src/plugins/es_ui_shared/public/request/use_request.ts new file mode 100644 index 0000000000000..481843bf40e88 --- /dev/null +++ b/src/plugins/es_ui_shared/public/request/use_request.ts @@ -0,0 +1,161 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useEffect, useCallback, useState, useRef, useMemo } from 'react'; + +import { HttpSetup } from '../../../../../src/core/public'; +import { + sendRequest as sendStatelessRequest, + SendRequestConfig, + SendRequestResponse, +} from './send_request'; + +export interface UseRequestConfig extends SendRequestConfig { + pollIntervalMs?: number; + initialData?: any; + deserializer?: (data: any) => any; +} + +export interface UseRequestResponse { + isInitialRequest: boolean; + isLoading: boolean; + error: E | null; + data?: D | null; + sendRequest: () => Promise>; +} + +export const useRequest = ( + httpClient: HttpSetup, + { path, method, query, body, pollIntervalMs, initialData, deserializer }: UseRequestConfig +): UseRequestResponse => { + const isMounted = useRef(false); + + // Main states for tracking request status and data + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [data, setData] = useState(initialData); + + // Consumers can use isInitialRequest to implement a polling UX. + const requestCountRef = useRef(0); + const isInitialRequest = requestCountRef.current === 0; + const pollIntervalIdRef = useRef(null); + + const clearPollInterval = useCallback(() => { + if (pollIntervalIdRef.current) { + clearTimeout(pollIntervalIdRef.current); + pollIntervalIdRef.current = null; + } + }, []); + + // Convert our object to string to be able to compare them in our useMemo, + // allowing the consumer to freely passed new objects to the hook on each + // render without requiring them to be memoized. + const queryStringified = query ? JSON.stringify(query) : undefined; + const bodyStringified = body ? JSON.stringify(body) : undefined; + + const requestBody = useMemo(() => { + return { + path, + method, + query: queryStringified ? query : undefined, + body: bodyStringified ? body : undefined, + }; + // queryStringified and bodyStringified stand in for query and body as dependencies. + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [path, method, queryStringified, bodyStringified]); + + const sendRequest = useCallback(async () => { + // If we're on an interval, this allows us to reset it if the user has manually requested the + // data, to avoid doubled-up requests. + clearPollInterval(); + + const requestId = ++requestCountRef.current; + + // We don't clear error or data, so it's up to the consumer to decide whether to display the + // "old" error/data or loading state when a new request is in-flight. + setIsLoading(true); + + const response = await sendStatelessRequest(httpClient, requestBody); + const { data: serializedResponseData, error: responseError } = response; + + const isOutdatedRequest = requestId !== requestCountRef.current; + const isUnmounted = isMounted.current === false; + + // Ignore outdated or irrelevant data. + if (isOutdatedRequest || isUnmounted) { + return { data: null, error: null }; + } + + setError(responseError); + // If there's an error, keep the data from the last request in case it's still useful to the user. + if (!responseError) { + const responseData = deserializer + ? deserializer(serializedResponseData) + : serializedResponseData; + setData(responseData); + } + // Setting isLoading to false also acts as a signal for scheduling the next poll request. + setIsLoading(false); + + return { data: serializedResponseData, error: responseError }; + }, [requestBody, httpClient, deserializer, clearPollInterval]); + + const scheduleRequest = useCallback(() => { + // If there's a scheduled poll request, this new one will supersede it. + clearPollInterval(); + + if (pollIntervalMs) { + pollIntervalIdRef.current = setTimeout(sendRequest, pollIntervalMs); + } + }, [pollIntervalMs, sendRequest, clearPollInterval]); + + // Send the request on component mount and whenever the dependencies of sendRequest() change. + useEffect(() => { + sendRequest(); + }, [sendRequest]); + + // Schedule the next poll request when the previous one completes. + useEffect(() => { + // When a request completes, attempt to schedule the next one. Note that we aren't re-scheduling + // a request whenever sendRequest's dependencies change. isLoading isn't set to false until the + // initial request has completed, so we won't schedule a request on mount. + if (!isLoading) { + scheduleRequest(); + } + }, [isLoading, scheduleRequest]); + + useEffect(() => { + isMounted.current = true; + + return () => { + isMounted.current = false; + + // Clean up on unmount. + clearPollInterval(); + }; + }, [clearPollInterval]); + + return { + isInitialRequest, + isLoading, + error, + data, + sendRequest, // Gives the user the ability to manually request data + }; +}; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx index 9fb804eb7fafa..b2f1a70341315 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/combobox_field.tsx @@ -74,7 +74,7 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, ...rest }: Props) => }; const onSearchComboChange = (value: string) => { - if (value) { + if (value !== undefined) { field.clearErrors(VALIDATION_TYPES.ARRAY_ITEM); } }; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx index e02c9b6c18615..b83b0af5f97c6 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx @@ -39,6 +39,8 @@ export const RangeField = ({ field, euiFieldProps = {}, ...rest }: Props) => { }>; field.onChange(event); }, + // https://github.com/elastic/kibana/issues/73972 + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [field.onChange] ); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form.tsx index b3a15fea8b187..287ac56243446 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form.tsx @@ -21,6 +21,7 @@ import React, { ReactNode } from 'react'; import { EuiForm } from '@elastic/eui'; import { FormProvider } from '../form_context'; +import { FormDataContextProvider } from '../form_data_context'; import { FormHook } from '../types'; interface Props { @@ -30,8 +31,14 @@ interface Props { [key: string]: any; } -export const Form = ({ form, FormWrapper = EuiForm, ...rest }: Props) => ( - - - -); +export const Form = ({ form, FormWrapper = EuiForm, ...rest }: Props) => { + const { getFormData, __getFormData$ } = form; + + return ( + + + + + + ); +}; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx index 25448dff18e8a..d9095944eaa33 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx @@ -75,16 +75,7 @@ describe('', () => { setInputValue('lastNameField', 'updated value'); }); - /** - * The children will be rendered three times: - * - Twice for each input value that has changed - * - once because after updating both fields, the **form** isValid state changes (from "undefined" to "true") - * causing a new "form" object to be returned and thus a re-render. - * - * When the form object will be memoized (in a future PR), te bellow call count should only be 2 as listening - * to form data changes should not receive updates when the "isValid" state of the form changes. - */ - expect(onFormData.mock.calls.length).toBe(3); + expect(onFormData).toBeCalledTimes(2); const [formDataUpdated] = onFormData.mock.calls[onFormData.mock.calls.length - 1] as Parameters< OnUpdateHandler @@ -130,7 +121,7 @@ describe('', () => { find, } = setup() as TestBed; - expect(onFormData.mock.calls.length).toBe(0); // Not present in the DOM yet + expect(onFormData).toBeCalledTimes(0); // Not present in the DOM yet // Make some changes to the form fields await act(async () => { @@ -188,7 +179,7 @@ describe('', () => { setInputValue('lastNameField', 'updated value'); }); - expect(onFormData.mock.calls.length).toBe(0); + expect(onFormData).toBeCalledTimes(0); }); test('props.pathsToWatch (Array): should not re-render the children when the field that changed is not in the watch list', async () => { @@ -228,14 +219,14 @@ describe('', () => { }); // No re-render - expect(onFormData.mock.calls.length).toBe(0); + expect(onFormData).toBeCalledTimes(0); // Make some changes to fields in the watch list await act(async () => { setInputValue('nameField', 'updated value'); }); - expect(onFormData.mock.calls.length).toBe(1); + expect(onFormData).toBeCalledTimes(1); onFormData.mockReset(); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts index 3630b902f0564..ac141baf8fc71 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts @@ -17,10 +17,10 @@ * under the License. */ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React from 'react'; import { FormData } from '../types'; -import { useFormContext } from '../form_context'; +import { useFormData } from '../hooks'; interface Props { children: (formData: FormData) => JSX.Element | null; @@ -28,46 +28,9 @@ interface Props { } export const FormDataProvider = React.memo(({ children, pathsToWatch }: Props) => { - const form = useFormContext(); - const { subscribe } = form; - const previousRawData = useRef(form.__getFormData$().value); - const isMounted = useRef(false); - const [formData, setFormData] = useState(previousRawData.current); + const { 0: formData, 2: isReady } = useFormData({ watch: pathsToWatch }); - const onFormData = useCallback( - ({ data: { raw } }) => { - // To avoid re-rendering the children for updates on the form data - // that we are **not** interested in, we can specify one or multiple path(s) - // to watch. - if (pathsToWatch) { - const valuesToWatchArray = Array.isArray(pathsToWatch) - ? (pathsToWatch as string[]) - : ([pathsToWatch] as string[]); - - if (valuesToWatchArray.some((value) => previousRawData.current[value] !== raw[value])) { - previousRawData.current = raw; - setFormData(raw); - } - } else { - setFormData(raw); - } - }, - [pathsToWatch] - ); - - useEffect(() => { - const subscription = subscribe(onFormData); - return subscription.unsubscribe; - }, [subscribe, onFormData]); - - useEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); - - if (!isMounted.current && Object.keys(formData).length === 0) { + if (!isReady) { // No field has mounted yet, don't render anything return null; } diff --git a/src/legacy/ui/public/new_platform/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_data_context.tsx similarity index 51% rename from src/legacy/ui/public/new_platform/index.ts rename to src/plugins/es_ui_shared/static/forms/hook_form_lib/form_data_context.tsx index ca5890854f3aa..0e6a75e9c5065 100644 --- a/src/legacy/ui/public/new_platform/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_data_context.tsx @@ -16,4 +16,35 @@ * specific language governing permissions and limitations * under the License. */ -export { __setup__, __start__, npSetup, npStart } from './new_platform'; + +import React, { createContext, useContext, useMemo } from 'react'; + +import { FormData, FormHook } from './types'; +import { Subject } from './lib'; + +export interface Context { + getFormData$: () => Subject; + getFormData: FormHook['getFormData']; +} + +const FormDataContext = createContext | undefined>(undefined); + +interface Props extends Context { + children: React.ReactNode; +} + +export const FormDataContextProvider = ({ children, getFormData$, getFormData }: Props) => { + const value = useMemo( + () => ({ + getFormData, + getFormData$, + }), + [getFormData, getFormData$] + ); + + return {children}; +}; + +export function useFormDataContext() { + return useContext | undefined>(FormDataContext); +} diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts index 6a04a592227f9..45c11dd6272e4 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts @@ -19,3 +19,4 @@ export { useField } from './use_field'; export { useForm } from './use_form'; +export { useFormData } from './use_form_data'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index 9d22e4eb2ee5e..fa29f900af2ef 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -254,6 +254,8 @@ export const useField = ( validationErrors.push({ ...validationResult, + // See comment below that explains why we add "__isBlocking__". + __isBlocking__: validationResult.__isBlocking__ ?? validation.isBlocking, validationType: validationType || VALIDATION_TYPES.FIELD, }); @@ -306,6 +308,11 @@ export const useField = ( validationErrors.push({ ...(validationResult as ValidationError), + // We add an "__isBlocking__" property to know if this error is a blocker or no. + // Most validation errors are blockers but in some cases a validation is more a warning than an error + // like with the ComboBox items when they are added. + __isBlocking__: + (validationResult as ValidationError).__isBlocking__ ?? validation.isBlocking, validationType: validationType || VALIDATION_TYPES.FIELD, }); @@ -394,7 +401,13 @@ export const useField = ( ); const _setErrors: FieldHook['setErrors'] = useCallback((_errors) => { - setErrors(_errors.map((error) => ({ validationType: VALIDATION_TYPES.FIELD, ...error }))); + setErrors( + _errors.map((error) => ({ + validationType: VALIDATION_TYPES.FIELD, + __isBlocking__: true, + ...error, + })) + ); }, []); /** @@ -463,7 +476,8 @@ export const useField = ( [setValue, deserializeValue, defaultValue] ); - const isValid = errors.length === 0; + // Don't take into account non blocker validation. Some are just warning (like trying to add a wrong ComboBox item) + const isValid = errors.filter((e) => e.__isBlocking__ !== false).length === 0; const field = useMemo>(() => { return { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx index 007e492243bac..4a880415b6d22 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx @@ -39,7 +39,7 @@ const onFormHook = (_form: FormHook) => { formHook = _form; }; -describe('use_form() hook', () => { +describe('useForm() hook', () => { beforeEach(() => { formHook = null; }); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index 35bac5b9a58c6..7b72a9eeacf7b 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -240,6 +240,12 @@ export function useForm( if (!field.isValidated) { setIsValid(undefined); + + // When we submit the form (and set "isSubmitted" to "true"), we validate **all fields**. + // If a field is added and it is not validated it means that we have swapped fields and added new ones: + // --> we have basically have a new form in front of us. + // For that reason we make sure that the "isSubmitted" state is false. + setIsSubmitted(false); } }, [updateFormDataAt] @@ -389,6 +395,7 @@ export function useForm( isValid, id, submit: submitForm, + validate: validateAllFields, subscribe, setFieldValue, setFieldErrors, @@ -428,6 +435,7 @@ export function useForm( addField, removeField, validateFields, + validateAllFields, ]); useEffect(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.test.tsx new file mode 100644 index 0000000000000..0fb65daecf2f4 --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.test.tsx @@ -0,0 +1,234 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect } from 'react'; +import { act } from 'react-dom/test-utils'; + +import { registerTestBed, TestBed } from '../shared_imports'; +import { Form, UseField } from '../components'; +import { useForm } from './use_form'; +import { useFormData, HookReturn } from './use_form_data'; + +interface Props { + onChange(data: HookReturn): void; + watch?: string | string[]; +} + +describe('useFormData() hook', () => { + const HookListenerComp = React.memo(({ onChange, watch }: Props) => { + const hookValue = useFormData({ watch }); + + useEffect(() => { + onChange(hookValue); + }, [hookValue, onChange]); + + return null; + }); + + describe('form data updates', () => { + let testBed: TestBed; + let onChangeSpy: jest.Mock; + + const getLastMockValue = () => { + return onChangeSpy.mock.calls[onChangeSpy.mock.calls.length - 1][0] as HookReturn; + }; + + const TestComp = (props: Props) => { + const { form } = useForm(); + + return ( +
+ + + + ); + }; + + const setup = registerTestBed(TestComp, { + memoryRouter: { wrapComponent: false }, + }); + + beforeEach(() => { + onChangeSpy = jest.fn(); + testBed = setup({ onChange: onChangeSpy }) as TestBed; + }); + + test('should return the form data', () => { + // Called twice: + // once when the hook is called and once when the fields have mounted and updated the form data + expect(onChangeSpy).toBeCalledTimes(2); + const [data] = getLastMockValue(); + expect(data).toEqual({ title: 'titleInitialValue' }); + }); + + test('should listen to field changes', async () => { + const { + form: { setInputValue }, + } = testBed; + + await act(async () => { + setInputValue('titleField', 'titleChanged'); + }); + + expect(onChangeSpy).toBeCalledTimes(3); + const [data] = getLastMockValue(); + expect(data).toEqual({ title: 'titleChanged' }); + }); + }); + + describe('format form data', () => { + let onChangeSpy: jest.Mock; + + const getLastMockValue = () => { + return onChangeSpy.mock.calls[onChangeSpy.mock.calls.length - 1][0] as HookReturn; + }; + + const TestComp = (props: Props) => { + const { form } = useForm(); + + return ( +
+ + + + + ); + }; + + const setup = registerTestBed(TestComp, { + memoryRouter: { wrapComponent: false }, + }); + + beforeEach(() => { + onChangeSpy = jest.fn(); + setup({ onChange: onChangeSpy }); + }); + + test('should expose a handler to build the form data', () => { + const { 1: format } = getLastMockValue(); + expect(format()).toEqual({ + user: { + firstName: 'John', + lastName: 'Snow', + }, + }); + }); + }); + + describe('options', () => { + describe('watch', () => { + let testBed: TestBed; + let onChangeSpy: jest.Mock; + + const getLastMockValue = () => { + return onChangeSpy.mock.calls[onChangeSpy.mock.calls.length - 1][0] as HookReturn; + }; + + const TestComp = (props: Props) => { + const { form } = useForm(); + + return ( +
+ + + + + ); + }; + + const setup = registerTestBed(TestComp, { + memoryRouter: { wrapComponent: false }, + }); + + beforeEach(() => { + onChangeSpy = jest.fn(); + testBed = setup({ watch: 'title', onChange: onChangeSpy }) as TestBed; + }); + + test('should not listen to changes on fields we are not interested in', async () => { + const { + form: { setInputValue }, + } = testBed; + + await act(async () => { + // Changing a field we are **not** interested in + setInputValue('subTitleField', 'subTitleChanged'); + // Changing a field we **are** interested in + setInputValue('titleField', 'titleChanged'); + }); + + const [data] = getLastMockValue(); + expect(data).toEqual({ title: 'titleChanged', subTitle: 'subTitleInitialValue' }); + }); + }); + + describe('form', () => { + let testBed: TestBed; + let onChangeSpy: jest.Mock; + + const getLastMockValue = () => { + return onChangeSpy.mock.calls[onChangeSpy.mock.calls.length - 1][0] as HookReturn; + }; + + const TestComp = ({ onChange }: Props) => { + const { form } = useForm(); + const hookValue = useFormData({ form }); + + useEffect(() => { + onChange(hookValue); + }, [hookValue, onChange]); + + return ( +
+ + + ); + }; + + const setup = registerTestBed(TestComp, { + memoryRouter: { wrapComponent: false }, + }); + + beforeEach(() => { + onChangeSpy = jest.fn(); + testBed = setup({ onChange: onChangeSpy }) as TestBed; + }); + + test('should allow a form to be provided when the hook is called outside of the FormDataContext', async () => { + const { + form: { setInputValue }, + } = testBed; + + const [initialData] = getLastMockValue(); + expect(initialData).toEqual({ title: 'titleInitialValue' }); + + await act(async () => { + setInputValue('titleField', 'titleChanged'); + }); + + const [updatedData] = getLastMockValue(); + expect(updatedData).toEqual({ title: 'titleChanged' }); + }); + }); + }); +}); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.ts new file mode 100644 index 0000000000000..fb4a0984438ad --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useState, useEffect, useRef, useCallback } from 'react'; + +import { FormData, FormHook } from '../types'; +import { useFormDataContext, Context } from '../form_data_context'; + +interface Options { + watch?: string | string[]; + form?: FormHook; +} + +export type HookReturn = [FormData, () => T, boolean]; + +export const useFormData = (options: Options = {}): HookReturn => { + const { watch, form } = options; + const ctx = useFormDataContext(); + + let getFormData: Context['getFormData']; + let getFormData$: Context['getFormData$']; + + if (form !== undefined) { + getFormData = form.getFormData; + getFormData$ = form.__getFormData$; + } else if (ctx !== undefined) { + ({ getFormData, getFormData$ } = ctx); + } else { + throw new Error( + 'useFormData() must be used within a or you need to pass FormHook object in the options.' + ); + } + + const initialValue = getFormData$().value; + + const previousRawData = useRef(initialValue); + const isMounted = useRef(false); + const [formData, setFormData] = useState(previousRawData.current); + + const formatFormData = useCallback(() => { + return getFormData({ unflatten: true }); + }, [getFormData]); + + useEffect(() => { + const subscription = getFormData$().subscribe((raw) => { + if (watch) { + const valuesToWatchArray = Array.isArray(watch) + ? (watch as string[]) + : ([watch] as string[]); + + if (valuesToWatchArray.some((value) => previousRawData.current[value] !== raw[value])) { + previousRawData.current = raw; + // Only update the state if one of the field we watch has changed. + setFormData(raw); + } + } else { + setFormData(raw); + } + }); + return subscription.unsubscribe; + }, [getFormData$, watch]); + + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); + + if (!isMounted.current && Object.keys(formData).length === 0) { + // No field has mounted yet + return [formData, formatFormData, false]; + } + + return [formData, formatFormData, true]; +}; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/index.ts index 3079814c9ad14..8d6b57fbeb315 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/index.ts @@ -19,7 +19,7 @@ // Only export the useForm hook. The "useField" hook is for internal use // as the consumer of the library must use the component -export { useForm } from './hooks'; +export { useForm, useFormData } from './hooks'; export { getFieldValidityAndErrorMessage } from './helpers'; export * from './form_context'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts index dc495f6eb56b4..4b343ec5e9f2e 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts @@ -30,6 +30,7 @@ export interface FormHook { readonly isValid: boolean | undefined; readonly id: string; submit: (e?: FormEvent | MouseEvent) => Promise<{ data: T; isValid: boolean }>; + validate: () => Promise; subscribe: (handler: OnUpdateHandler) => Subscription; setFieldValue: (fieldName: string, value: FieldValue) => void; setFieldErrors: (fieldName: string, errors: ValidationError[]) => void; @@ -147,6 +148,7 @@ export interface ValidationError { message: string; code?: T; validationType?: string; + __isBlocking__?: boolean; [key: string]: any; } @@ -185,5 +187,11 @@ type FieldValue = unknown; export interface ValidationConfig { validator: ValidationFunc; type?: string; + /** + * By default all validation are blockers, which means that if they fail, the field is invalid. + * In some cases, like when trying to add an item to the ComboBox, if the item is not valid we want + * to show a validation error. But this validation is **not** blocking. Simply, the item has not been added. + */ + isBlocking?: boolean; exitOnFail?: boolean; } diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.js b/src/plugins/home/public/application/components/tutorial/tutorial.js index 8139bc6d38ab1..f55462bdc9e28 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.js @@ -201,26 +201,18 @@ class TutorialUi extends React.Component { * @return {Promise} */ fetchEsHitsStatus = async (esHitsCheckConfig) => { - const searchHeader = JSON.stringify({ index: esHitsCheckConfig.index }); - const searchBody = JSON.stringify({ query: esHitsCheckConfig.query, size: 1 }); - const response = await fetch(this.props.addBasePath('/elasticsearch/_msearch'), { - method: 'post', - body: `${searchHeader}\n${searchBody}\n`, - headers: { - accept: 'application/json', - 'content-type': 'application/x-ndjson', - 'kbn-xsrf': 'kibana', - }, - credentials: 'same-origin', - }); - - if (response.status > 300) { + const { http } = getServices(); + try { + const response = await http.post('/api/home/hits_status', { + body: JSON.stringify({ + index: esHitsCheckConfig.index, + query: esHitsCheckConfig.query, + }), + }); + return response.count > 0 ? StatusCheckStates.HAS_DATA : StatusCheckStates.NO_DATA; + } catch (e) { return StatusCheckStates.ERROR; } - - const results = await response.json(); - const numHits = _.get(results, 'responses.[0].hits.hits.length', 0); - return numHits === 0 ? StatusCheckStates.NO_DATA : StatusCheckStates.HAS_DATA; }; renderInstructionSetsToggle = () => { diff --git a/src/plugins/home/server/plugin.test.ts b/src/plugins/home/server/plugin.test.ts index 33d907315e512..58103430b4d7c 100644 --- a/src/plugins/home/server/plugin.test.ts +++ b/src/plugins/home/server/plugin.test.ts @@ -19,10 +19,7 @@ import { registryForTutorialsMock, registryForSampleDataMock } from './plugin.test.mocks'; import { HomeServerPlugin } from './plugin'; -import { coreMock } from '../../../core/server/mocks'; -import { CoreSetup } from '../../../core/server'; - -type MockedKeys = { [P in keyof T]: jest.Mocked }; +import { coreMock, httpServiceMock } from '../../../core/server/mocks'; describe('HomeServerPlugin', () => { beforeEach(() => { @@ -33,8 +30,16 @@ describe('HomeServerPlugin', () => { }); describe('setup', () => { - const mockCoreSetup: MockedKeys = coreMock.createSetup(); - const initContext = coreMock.createPluginInitializerContext(); + let mockCoreSetup: ReturnType; + let initContext: ReturnType; + let routerMock: ReturnType; + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + routerMock = httpServiceMock.createRouter(); + mockCoreSetup.http.createRouter.mockReturnValue(routerMock); + initContext = coreMock.createPluginInitializerContext(); + }); test('wires up tutorials provider service and returns registerTutorial and addScopedTutorialContextFactory', () => { const setup = new HomeServerPlugin(initContext).setup(mockCoreSetup, {}); @@ -52,6 +57,18 @@ describe('HomeServerPlugin', () => { expect(setup.sampleData).toHaveProperty('addAppLinksToSampleDataset'); expect(setup.sampleData).toHaveProperty('replacePanelInSampleDatasetDashboard'); }); + + test('registers the `/api/home/hits_status` route', () => { + new HomeServerPlugin(initContext).setup(mockCoreSetup, {}); + + expect(routerMock.post).toHaveBeenCalledTimes(1); + expect(routerMock.post).toHaveBeenCalledWith( + expect.objectContaining({ + path: '/api/home/hits_status', + }), + expect.any(Function) + ); + }); }); describe('start', () => { diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index 1050c19362ae1..a2f8eec686b21 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -28,6 +28,7 @@ import { import { UsageCollectionSetup } from '../../usage_collection/server'; import { capabilitiesProvider } from './capabilities_provider'; import { sampleDataTelemetry } from './saved_objects'; +import { registerRoutes } from './routes'; interface HomeServerPluginSetupDependencies { usageCollection?: UsageCollectionSetup; @@ -41,6 +42,10 @@ export class HomeServerPlugin implements Plugin { + router.post( + { + path: '/api/home/hits_status', + validate: { + body: schema.object({ + index: schema.string(), + query: schema.recordOf(schema.string(), schema.any()), + }), + }, + }, + router.handleLegacyErrors(async (context, req, res) => { + const { index, query } = req.body; + const client = context.core.elasticsearch.client; + + try { + const { body } = await client.asCurrentUser.search({ + index, + size: 1, + body: { + query, + }, + }); + const count = body.hits.hits.length; + + return res.ok({ + body: { + count, + }, + }); + } catch (e) { + return res.badRequest({ + body: e, + }); + } + }) + ); +}; diff --git a/src/legacy/ui/public/__mocks__/metadata.ts b/src/plugins/home/server/routes/index.ts similarity index 80% rename from src/legacy/ui/public/__mocks__/metadata.ts rename to src/plugins/home/server/routes/index.ts index b7f944de27463..bf492051cdedf 100644 --- a/src/legacy/ui/public/__mocks__/metadata.ts +++ b/src/plugins/home/server/routes/index.ts @@ -17,9 +17,9 @@ * under the License. */ -import { metadata as metadataImpl } from '../metadata'; +import { IRouter } from 'src/core/server'; +import { registerHitsStatusRoute } from './fetch_es_hits_status'; -export const metadata: typeof metadataImpl = { - branch: 'jest-metadata-mock-branch', - version: '42.23.26', +export const registerRoutes = (router: IRouter) => { + registerHitsStatusRoute(router); }; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index 987e8f0dae3a0..a0eecef66ff93 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -121,7 +121,7 @@ export const EditIndexPattern = withRouter( const refreshFields = () => { overlays.openConfirm(confirmMessage, confirmModalOptionsRefresh).then(async (isConfirmed) => { if (isConfirmed) { - await indexPattern.init(true); + await indexPattern.refreshFields(); setFields(indexPattern.getNonScriptedFields()); } }); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx index 80132167b7f58..ed50317aed6a0 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx @@ -46,14 +46,6 @@ jest.mock('./components/table', () => ({ }, })); -jest.mock('ui/documentation_links', () => ({ - documentationLinks: { - scriptedFields: { - painless: 'painlessDocs', - }, - }, -})); - const helpers = { redirectToRoute: () => {}, getRouteHref: () => '#', diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 34140703fd8ae..9a9486da892e4 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -32,7 +32,7 @@ export * from './notifications'; export { Markdown, MarkdownSimple } from './markdown'; export { reactToUiComponent, uiToReactComponent } from './adapters'; export { useUrlTracker } from './use_url_tracker'; -export { toMountPoint } from './util'; +export { toMountPoint, MountPointPortal } from './util'; export { RedirectAppLinks } from './app_links'; /** dummy plugin, we just want kibanaReact to have its own bundle */ diff --git a/src/plugins/kibana_react/public/util/index.ts b/src/plugins/kibana_react/public/util/index.ts index 71a281dbdaad3..a6f3f87535f46 100644 --- a/src/plugins/kibana_react/public/util/index.ts +++ b/src/plugins/kibana_react/public/util/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from './react_mount'; +export { toMountPoint } from './to_mount_point'; +export { MountPointPortal } from './mount_point_portal'; diff --git a/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx b/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx new file mode 100644 index 0000000000000..c13b8eae26221 --- /dev/null +++ b/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx @@ -0,0 +1,210 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FC } from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { MountPoint, UnmountCallback } from 'kibana/public'; +import { MountPointPortal } from './mount_point_portal'; +import { act } from 'react-dom/test-utils'; + +describe('MountPointPortal', () => { + let portalTarget: HTMLElement; + let mountPoint: MountPoint; + let setMountPoint: jest.Mock<(mountPoint: MountPoint) => void>; + let dom: ReactWrapper; + + const refresh = () => { + new Promise(async (resolve) => { + if (dom) { + act(() => { + dom.update(); + }); + } + setImmediate(() => resolve(dom)); // flushes any pending promises + }); + }; + + beforeEach(() => { + portalTarget = document.createElement('div'); + document.body.append(portalTarget); + setMountPoint = jest.fn().mockImplementation((mp) => (mountPoint = mp)); + }); + + afterEach(() => { + if (portalTarget) { + portalTarget.remove(); + } + }); + + it('calls the provided `setMountPoint` during render', async () => { + dom = mount( + + portal content + + ); + + await refresh(); + + expect(setMountPoint).toHaveBeenCalledTimes(1); + }); + + it('renders the portal content when calling the mountPoint ', async () => { + dom = mount( + + portal content + + ); + + await refresh(); + + expect(mountPoint).toBeDefined(); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe('portal content'); + }); + + it('cleanup the portal content when the component is unmounted', async () => { + dom = mount( + + portal content + + ); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe('portal content'); + + dom.unmount(); + + await refresh(); + + expect(portalTarget.innerHTML).toBe(''); + }); + + it('cleanup the portal content when unmounting the MountPoint from outside', async () => { + dom = mount( + + portal content + + ); + + let unmount: UnmountCallback; + act(() => { + unmount = mountPoint(portalTarget); + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe('portal content'); + + act(() => { + unmount(); + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe(''); + }); + + it('updates the content of the portal element when the content of MountPointPortal changes', async () => { + const Wrapper: FC<{ + setMount: (mountPoint: MountPoint) => void; + portalContent: string; + }> = ({ setMount, portalContent }) => { + return ( + +
{portalContent}
+
+ ); + }; + + dom = mount(); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe('
before
'); + + dom.setProps({ + portalContent: 'after', + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe('
after
'); + }); + + it('cleanup the previous portal content when setMountPoint changes', async () => { + dom = mount( + + portal content + + ); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe('portal content'); + + const newSetMountPoint = jest.fn(); + + dom.setProps({ + setMountPoint: newSetMountPoint, + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe(''); + }); + + it('intercepts errors and display an error message', async () => { + const CrashTest = () => { + throw new Error('crash'); + }; + + dom = mount( + + + + ); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + expect(portalTarget.innerHTML).toBe('

Error rendering portal content

'); + }); +}); diff --git a/src/plugins/kibana_react/public/util/mount_point_portal.tsx b/src/plugins/kibana_react/public/util/mount_point_portal.tsx new file mode 100644 index 0000000000000..b762fba88791e --- /dev/null +++ b/src/plugins/kibana_react/public/util/mount_point_portal.tsx @@ -0,0 +1,88 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useRef, useEffect, useState, Component } from 'react'; +import ReactDOM from 'react-dom'; +import { MountPoint } from 'kibana/public'; + +interface MountPointPortalProps { + setMountPoint: (mountPoint: MountPoint) => void; +} + +/** + * Utility component to portal a part of a react application into the provided `MountPoint`. + */ +export const MountPointPortal: React.FC = ({ children, setMountPoint }) => { + // state used to force re-renders when the element changes + const [shouldRender, setShouldRender] = useState(false); + const el = useRef(); + + useEffect(() => { + setMountPoint((element) => { + el.current = element; + setShouldRender(true); + return () => { + setShouldRender(false); + el.current = undefined; + }; + }); + + return () => { + setShouldRender(false); + el.current = undefined; + }; + }, [setMountPoint]); + + if (shouldRender && el.current) { + return ReactDOM.createPortal( + {children}, + el.current + ); + } else { + return null; + } +}; + +class MountPointPortalErrorBoundary extends Component<{}, { error?: any }> { + state = { + error: undefined, + }; + + static getDerivedStateFromError(error: any) { + return { error }; + } + + componentDidCatch() { + // nothing, will just rerender to display the error message + } + + render() { + if (this.state.error) { + return ( +

+ {i18n.translate('kibana-react.mountPointPortal.errorMessage', { + defaultMessage: 'Error rendering portal content', + })} +

+ ); + } + return this.props.children; + } +} diff --git a/src/plugins/kibana_react/public/util/react_mount.tsx b/src/plugins/kibana_react/public/util/to_mount_point.tsx similarity index 100% rename from src/plugins/kibana_react/public/util/react_mount.tsx rename to src/plugins/kibana_react/public/util/to_mount_point.tsx diff --git a/src/plugins/maps_legacy/config.ts b/src/plugins/maps_legacy/config.ts index 67e46d2270583..46d4a8fb6cb90 100644 --- a/src/plugins/maps_legacy/config.ts +++ b/src/plugins/maps_legacy/config.ts @@ -40,4 +40,4 @@ export const configSchema = schema.object({ }), }); -export type ConfigSchema = TypeOf; +export type MapsLegacyConfig = TypeOf; diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts index 14612ab1b2a57..8f14cd1b15e2c 100644 --- a/src/plugins/maps_legacy/public/index.ts +++ b/src/plugins/maps_legacy/public/index.ts @@ -42,18 +42,6 @@ import { mapTooltipProvider } from './tooltip_provider'; import './map/index.scss'; -export interface MapsLegacyConfigType { - regionmap: any; - emsTileLayerId: string; - includeElasticMapsService: boolean; - proxyElasticMapsServiceInMaps: boolean; - tilemap: any; - emsFontLibraryUrl: string; - emsFileApiUrl: string; - emsTileApiUrl: string; - emsLandingPageUrl: string; -} - export function plugin(initializerContext: PluginInitializerContext) { return new MapsLegacyPlugin(initializerContext); } diff --git a/src/plugins/maps_legacy/public/plugin.ts b/src/plugins/maps_legacy/public/plugin.ts index 6b4e06fec9ccc..8c9f1e9cef194 100644 --- a/src/plugins/maps_legacy/public/plugin.ts +++ b/src/plugins/maps_legacy/public/plugin.ts @@ -27,8 +27,8 @@ import { ServiceSettings } from './map/service_settings'; import { getPrecision, getZoomPrecision } from './map/precision'; // @ts-ignore import { KibanaMap } from './map/kibana_map'; -import { MapsLegacyConfigType, MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; -import { ConfigSchema } from '../config'; +import { MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; +import { MapsLegacyConfig } from '../config'; // @ts-ignore import { BaseMapsVisualizationProvider } from './map/base_maps_visualization'; @@ -40,7 +40,7 @@ import { BaseMapsVisualizationProvider } from './map/base_maps_visualization'; export const bindSetupCoreAndPlugins = ( core: CoreSetup, - config: MapsLegacyConfigType, + config: MapsLegacyConfig, kibanaVersion: string ) => { setToasts(core.notifications.toasts); @@ -55,14 +55,14 @@ export interface MapsLegacySetupDependencies {} export interface MapsLegacyStartDependencies {} export class MapsLegacyPlugin implements Plugin { - readonly _initializerContext: PluginInitializerContext; + readonly _initializerContext: PluginInitializerContext; - constructor(initializerContext: PluginInitializerContext) { + constructor(initializerContext: PluginInitializerContext) { this._initializerContext = initializerContext; } public setup(core: CoreSetup, plugins: MapsLegacySetupDependencies) { - const config = this._initializerContext.config.get(); + const config = this._initializerContext.config.get(); const kibanaVersion = this._initializerContext.env.packageInfo.version; bindSetupCoreAndPlugins(core, config, kibanaVersion); diff --git a/src/plugins/maps_legacy/server/index.ts b/src/plugins/maps_legacy/server/index.ts index 79ecbb238314a..665b3b8986ef0 100644 --- a/src/plugins/maps_legacy/server/index.ts +++ b/src/plugins/maps_legacy/server/index.ts @@ -20,10 +20,10 @@ import { Plugin, PluginConfigDescriptor } from 'kibana/server'; import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { Observable } from 'rxjs'; -import { configSchema, ConfigSchema } from '../config'; +import { configSchema, MapsLegacyConfig } from '../config'; import { getUiSettings } from './ui_settings'; -export const config: PluginConfigDescriptor = { +export const config: PluginConfigDescriptor = { exposeToBrowser: { includeElasticMapsService: true, proxyElasticMapsServiceInMaps: true, @@ -40,13 +40,13 @@ export const config: PluginConfigDescriptor = { }; export interface MapsLegacyPluginSetup { - config$: Observable; + config$: Observable; } export class MapsLegacyPlugin implements Plugin { - readonly _initializerContext: PluginInitializerContext; + readonly _initializerContext: PluginInitializerContext; - constructor(initializerContext: PluginInitializerContext) { + constructor(initializerContext: PluginInitializerContext) { this._initializerContext = initializerContext; } diff --git a/src/plugins/navigation/kibana.json b/src/plugins/navigation/kibana.json index 000d5acf2635f..85d2049a34be0 100644 --- a/src/plugins/navigation/kibana.json +++ b/src/plugins/navigation/kibana.json @@ -3,5 +3,6 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["data"] -} \ No newline at end of file + "requiredPlugins": ["data"], + "requiredBundles": ["kibanaReact"] +} diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 46384fb3f27d5..f21e5680e8f61 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -18,9 +18,12 @@ */ import React from 'react'; +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { MountPoint } from 'kibana/public'; import { TopNavMenu } from './top_nav_menu'; import { TopNavMenuData } from './top_nav_menu_data'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; const dataShim = { ui: { @@ -109,4 +112,59 @@ describe('TopNavMenu', () => { expect(component.find('.kbnTopNavMenu').length).toBe(1); expect(component.find('.myCoolClass').length).toBeTruthy(); }); + + describe('when setMenuMountPoint is provided', () => { + let portalTarget: HTMLElement; + let mountPoint: MountPoint; + let setMountPoint: jest.Mock<(mountPoint: MountPoint) => void>; + let dom: ReactWrapper; + + const refresh = () => { + new Promise(async (resolve) => { + if (dom) { + act(() => { + dom.update(); + }); + } + setImmediate(() => resolve(dom)); // flushes any pending promises + }); + }; + + beforeEach(() => { + portalTarget = document.createElement('div'); + document.body.append(portalTarget); + setMountPoint = jest.fn().mockImplementation((mp) => (mountPoint = mp)); + }); + + afterEach(() => { + if (portalTarget) { + portalTarget.remove(); + } + }); + + it('mounts the menu inside the provided mountPoint', async () => { + const component = mountWithIntl( + + ); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); + + // menu is rendered outside of the component + expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); + expect(portalTarget.getElementsByTagName('BUTTON').length).toBe(menuItems.length); + }); + }); }); diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index 2cfca332effb0..a1a40b49cc8f0 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -18,13 +18,14 @@ */ import React, { ReactElement } from 'react'; - import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - import classNames from 'classnames'; + +import { MountPoint } from '../../../../core/public'; +import { MountPointPortal } from '../../../kibana_react/public'; +import { StatefulSearchBarProps, DataPublicPluginStart } from '../../../data/public'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; -import { StatefulSearchBarProps, DataPublicPluginStart } from '../../../data/public'; export type TopNavMenuProps = StatefulSearchBarProps & { config?: TopNavMenuData[]; @@ -35,6 +36,25 @@ export type TopNavMenuProps = StatefulSearchBarProps & { showFilterBar?: boolean; data?: DataPublicPluginStart; className?: string; + /** + * If provided, the menu part of the component will be rendered as a portal inside the given mount point. + * + * This is meant to be used with the `setHeaderActionMenu` core API. + * + * @example + * ```ts + * export renderApp = ({ element, history, setHeaderActionMenu }: AppMountParameters) => { + * const topNavConfig = ...; // TopNavMenuProps + * return ( + * + * + * + * + * ) + * } + * ``` + */ + setMenuMountPoint?: (menuMount: MountPoint | undefined) => void; }; /* @@ -92,13 +112,26 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { } function renderLayout() { - const className = classNames('kbnTopNavMenu', props.className); - return ( - - {renderMenu(className)} - {renderSearchBar()} - - ); + const { setMenuMountPoint } = props; + const menuClassName = classNames('kbnTopNavMenu', props.className); + const wrapperClassName = 'kbnTopNavMenu__wrapper'; + if (setMenuMountPoint) { + return ( + <> + + {renderMenu(menuClassName)} + + {renderSearchBar()} + + ); + } else { + return ( + + {renderMenu(menuClassName)} + {renderSearchBar()} + + ); + } } return renderLayout(); diff --git a/src/plugins/region_map/public/plugin.ts b/src/plugins/region_map/public/plugin.ts index 04a2ba2f23f4e..ec9ee94310578 100644 --- a/src/plugins/region_map/public/plugin.ts +++ b/src/plugins/region_map/public/plugin.ts @@ -34,7 +34,7 @@ import { IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/publi import { setFormatService, setNotifications, setKibanaLegacy } from './kibana_services'; import { DataPublicPluginStart } from '../../data/public'; import { RegionMapsConfigType } from './index'; -import { ConfigSchema } from '../../maps_legacy/config'; +import { MapsLegacyConfig } from '../../maps_legacy/config'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; /** @private */ @@ -73,7 +73,7 @@ export interface RegionMapPluginStart {} /** @internal */ export class RegionMapPlugin implements Plugin { - readonly _initializerContext: PluginInitializerContext; + readonly _initializerContext: PluginInitializerContext; constructor(initializerContext: PluginInitializerContext) { this._initializerContext = initializerContext; diff --git a/src/plugins/timelion/public/directives/_saved_object_finder.scss b/src/plugins/timelion/public/directives/_saved_object_finder.scss index b97dace5e9e00..e1a055a5f49e9 100644 --- a/src/plugins/timelion/public/directives/_saved_object_finder.scss +++ b/src/plugins/timelion/public/directives/_saved_object_finder.scss @@ -23,3 +23,74 @@ } } } + + +saved-object-finder { + + .list-sort-button { + border-top-left-radius: 0; + border-top-right-radius: 0; + border: none; + padding: $euiSizeS $euiSize; + font-weight: $euiFontWeightRegular; + background-color: $euiColorLightestShade; + } + + .li-striped { + li { + border: none; + } + + li:nth-child(even) { + background-color: $euiColorLightestShade; + } + + li:nth-child(odd) { + background-color: $euiColorEmptyShade; + } + + .paginate-heading { + font-weight: $euiFontWeightRegular; + color: $euiColorDarkestShade; + } + + .list-group-item { + padding: $euiSizeS $euiSize; + + ul { + padding: 0; + display: flex; + flex-direction: row; + + .finder-type { + margin-right: $euiSizeS; + } + } + + a { + display: block; + color: $euiColorPrimary; + + i { + color: shade($euiColorPrimary, 10%); + margin-right: $euiSizeS; + } + } + + &:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + &.list-group-no-results p { + margin-bottom: 0; + } + } + } + + paginate { + paginate-controls { + margin: $euiSize; + } + } +} diff --git a/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap b/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap index 5200fee45d6b3..2a521bc01219c 100644 --- a/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap +++ b/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap @@ -33,6 +33,8 @@ exports[`DefaultEditorAgg component should init with the default set of props 1` } id="visEditorAggAccordion1" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} onToggle={[Function]} paddingSize="none" > diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts index ef2f937c8547c..b13ca32601aa9 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts @@ -26,7 +26,7 @@ import { IAggType, IndexPattern, IndexPatternField, -} from 'src/plugins/data/public'; +} from '../../../data/public'; import { filterAggTypes, filterAggTypeFields } from '../agg_filters'; import { groupAndSortBy, ComboBoxGroupedOptions } from '../utils'; import { AggTypeState, AggParamsState } from './agg_params_state'; diff --git a/src/plugins/vis_default_editor/public/components/agg_params_map.ts b/src/plugins/vis_default_editor/public/components/agg_params_map.ts index 9bc3146b9903b..e9019e479f92f 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_map.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_map.ts @@ -43,6 +43,7 @@ const buckets = { }, [BUCKET_TYPES.HISTOGRAM]: { interval: controls.NumberIntervalParamEditor, + maxBars: controls.MaxBarsParamEditor, min_doc_count: controls.MinDocCountParamEditor, has_extended_bounds: controls.HasExtendedBoundsParamEditor, extended_bounds: controls.ExtendedBoundsParamEditor, diff --git a/src/plugins/vis_default_editor/public/components/controls/filter.tsx b/src/plugins/vis_default_editor/public/components/controls/filter.tsx index 101ca3e8ad457..c373f9331c9a7 100644 --- a/src/plugins/vis_default_editor/public/components/controls/filter.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filter.tsx @@ -115,6 +115,7 @@ function FilterRow({ dataTestSubj={dataTestSubj} bubbleSubmitEvent={true} languageSwitcherPopoverAnchorPosition="leftDown" + size="s" /> {showCustomLabel ? ( diff --git a/src/plugins/vis_default_editor/public/components/controls/index.ts b/src/plugins/vis_default_editor/public/components/controls/index.ts index cfb236e5e22e3..26e6609c7711d 100644 --- a/src/plugins/vis_default_editor/public/components/controls/index.ts +++ b/src/plugins/vis_default_editor/public/components/controls/index.ts @@ -52,3 +52,4 @@ export { TopSizeParamEditor } from './top_size'; export { TopSortFieldParamEditor } from './top_sort_field'; export { OrderParamEditor } from './order'; export { UseGeocentroidParamEditor } from './use_geocentroid'; +export { MaxBarsParamEditor } from './max_bars'; diff --git a/src/plugins/vis_default_editor/public/components/controls/max_bars.tsx b/src/plugins/vis_default_editor/public/components/controls/max_bars.tsx new file mode 100644 index 0000000000000..b0d517e0928df --- /dev/null +++ b/src/plugins/vis_default_editor/public/components/controls/max_bars.tsx @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useCallback, useEffect } from 'react'; +import { EuiFormRow, EuiFieldNumber, EuiFieldNumberProps, EuiIconTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../../../../kibana_react/public'; +import { AggParamEditorProps } from '../agg_param_props'; +import { UI_SETTINGS } from '../../../../data/public'; + +export interface SizeParamEditorProps extends AggParamEditorProps { + iconTip?: React.ReactNode; + disabled?: boolean; +} + +const autoPlaceholder = i18n.translate('visDefaultEditor.controls.maxBars.autoPlaceholder', { + defaultMessage: 'Auto', +}); + +const label = ( + <> + {' '} + + } + type="questionInCircle" + /> + +); + +function MaxBarsParamEditor({ + disabled, + iconTip, + value, + setValue, + showValidation, + setValidity, + setTouched, +}: SizeParamEditorProps) { + const { services } = useKibana(); + const uiSettingMaxBars = services.uiSettings?.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); + const isValid = + disabled || + value === undefined || + value === '' || + Number(value) > 0 || + value < uiSettingMaxBars; + + useEffect(() => { + setValidity(isValid); + }, [isValid, setValidity]); + + const onChange: EuiFieldNumberProps['onChange'] = useCallback( + (ev) => setValue(ev.target.value === '' ? '' : parseFloat(ev.target.value)), + [setValue] + ); + + return ( + + + + ); +} + +export { MaxBarsParamEditor }; diff --git a/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx b/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx index f6354027ab01b..8cdc92581cefb 100644 --- a/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx @@ -20,7 +20,16 @@ import { get } from 'lodash'; import React, { useEffect, useCallback } from 'react'; -import { EuiFieldNumber, EuiFormRow, EuiIconTip } from '@elastic/eui'; +import { + EuiFieldNumber, + EuiFormRow, + EuiIconTip, + EuiSwitch, + EuiSwitchProps, + EuiFieldNumberProps, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { UI_SETTINGS } from '../../../../data/public'; @@ -47,6 +56,25 @@ const label = ( ); +const autoInterval = 'auto'; +const isAutoInterval = (value: unknown) => value === autoInterval; + +const selectIntervalPlaceholder = i18n.translate( + 'visDefaultEditor.controls.numberInterval.selectIntervalPlaceholder', + { + defaultMessage: 'Enter an interval', + } +); +const autoIntervalIsUsedPlaceholder = i18n.translate( + 'visDefaultEditor.controls.numberInterval.autoInteralIsUsed', + { + defaultMessage: 'Auto interval is used', + } +); +const useAutoIntervalLabel = i18n.translate('visDefaultEditor.controls.useAutoInterval', { + defaultMessage: 'Use auto interval', +}); + function NumberIntervalParamEditor({ agg, editorConfig, @@ -55,18 +83,28 @@ function NumberIntervalParamEditor({ setTouched, setValidity, setValue, -}: AggParamEditorProps) { +}: AggParamEditorProps) { + const isAutoChecked = isAutoInterval(value); const base: number = get(editorConfig, 'interval.base') as number; const min = base || 0; - const isValid = value !== undefined && value >= min; + const isValid = + value !== '' && value !== undefined && (isAutoInterval(value) || Number(value) >= min); useEffect(() => { setValidity(isValid); }, [isValid, setValidity]); - const onChange = useCallback( - ({ target }: React.ChangeEvent) => - setValue(isNaN(target.valueAsNumber) ? undefined : target.valueAsNumber), + const onChange: EuiFieldNumberProps['onChange'] = useCallback( + ({ target }) => setValue(isNaN(target.valueAsNumber) ? '' : target.valueAsNumber), + [setValue] + ); + + const onAutoSwitchChange: EuiSwitchProps['onChange'] = useCallback( + (e) => { + const isAutoSwitchChecked = e.target.checked; + + setValue(isAutoSwitchChecked ? autoInterval : ''); + }, [setValue] ); @@ -78,23 +116,32 @@ function NumberIntervalParamEditor({ isInvalid={showValidation && !isValid} helpText={get(editorConfig, 'interval.help')} > - + + + + + + + + ); } diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js index cfbd5ecb7bf65..068e3fea8b768 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_axis_label_string.test.js @@ -19,8 +19,6 @@ import { getAxisLabelString } from './get_axis_label_string'; -jest.mock('ui/new_platform'); - describe('getAxisLabelString(interval)', () => { test('should return a valid label for 10 seconds', () => { expect(getAxisLabelString(10000)).toEqual('per 10 seconds'); diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config.js index c950c39f06a66..3b081d8eb7db9 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config.js @@ -67,7 +67,9 @@ export function PanelConfig(props) { return ( - +
+ +
); diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js index 18380680283ef..7f26701e38277 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.js @@ -330,7 +330,7 @@ class GaugePanelConfigUi extends Component { ); } return ( -
+ <> this.switchTab('data')}> {view} -
+ ); } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js index 03d84d67ea787..36d0e3a80e227 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.js @@ -294,7 +294,7 @@ class MarkdownPanelConfigUi extends Component { ); } return ( -
+ <> {view} -
+ ); } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js index feb809f20995a..568194c2e0918 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.js @@ -167,7 +167,7 @@ export class MetricPanelConfig extends Component { ); } return ( -
+ <> this.switchTab('data')}> {view} -
+ ); } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js index 2b729e403898e..bb3f0041abca7 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.js @@ -269,7 +269,7 @@ export class TablePanelConfig extends Component { ); } return ( -
+ <> this.switchTab('data')}> {view} -
+ ); } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js index 3e5e335a9ea39..03da52b10f08b 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.js @@ -44,6 +44,7 @@ import { import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { getDefaultQueryLanguage } from '../lib/get_default_query_language'; import { QueryBarWrapper } from '../query_bar_wrapper'; + class TimeseriesPanelConfigUi extends Component { constructor(props) { super(props); @@ -401,7 +402,7 @@ class TimeseriesPanelConfigUi extends Component { ); } return ( -
+ <> this.switchTab('data')}> {view} -
+ ); } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js index 4752e0ee7de65..14c4400180280 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.js @@ -227,7 +227,7 @@ export class TopNPanelConfig extends Component { ); } return ( -
+ <> this.switchTab('data')}> {view} -
+ ); } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap index 3bf8b77621cf5..daff68e40dbae 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap @@ -44,6 +44,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js labelType="label" > { - if (filterQuery && filterQuery.language === 'kuery') { - try { - const queryOptions = this.coreContext.uiSettings.get( - UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS - ); - esKuery.fromKueryExpression(filterQuery.query, { allowLeadingWildcards: queryOptions }); - } catch (error) { - return false; - } - } - return true; - }; - handleChange = (partialModel) => { if (isEmpty(partialModel)) { return; @@ -134,6 +119,14 @@ export class VisEditor extends Component { }); }; + updateModel = () => { + const { params } = this.props.vis.clone(); + + this.setState({ + model: params, + }); + }; + handleCommit = () => { this.updateVisState(); this.setState({ dirty: false }); @@ -219,6 +212,10 @@ export class VisEditor extends Component { componentDidMount() { this.props.renderComplete(); + + if (this.props.isEditorMode && this.props.eventEmitter) { + this.props.eventEmitter.on('updateEditor', this.updateModel); + } } componentDidUpdate() { @@ -227,6 +224,10 @@ export class VisEditor extends Component { componentWillUnmount() { this.updateVisState.cancel(); + + if (this.props.isEditorMode && this.props.eventEmitter) { + this.props.eventEmitter.off('updateEditor', this.updateModel); + } } } diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap index 6506b838ff288..7ded8e2254aa9 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap @@ -42,7 +42,6 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ histogramModeAlignment="center" id="61ca57f1-469d-11e7-af02-69e470af7417:Rome" name="Rome" - stackAsPercentage={false} timeZone="local" xAccessor={0} xScaleType="time" diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap index c337c0dc77a2f..02c7fd811e583 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap @@ -34,7 +34,6 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ histogramModeAlignment="center" id="61ca57f1-469d-11e7-af02-69e470af7417:Rome" name="Rome" - stackAsPercentage={false} timeZone="local" xAccessor={0} xScaleType="time" diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js index 0afe773266a61..300af551e5020 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.js @@ -32,7 +32,7 @@ export function AreaSeriesDecorator({ lines, color, stackAccessors, - stackAsPercentage, + stackMode, points, xScaleType, yScaleType, @@ -60,7 +60,7 @@ export function AreaSeriesDecorator({ y1AccessorFormat, y0AccessorFormat, stackAccessors, - stackAsPercentage, + stackMode, xScaleType, yScaleType, timeZone, diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js index 80ffcaa66c7a0..1fa88ff4b991f 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/area_decorator.test.js @@ -43,7 +43,7 @@ describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries [1557003600000, 9], ], hideInLegend: false, - stackAsPercentage: false, + stackMode: undefined, seriesId: '61ca57f1-469d-11e7-af02-69e470af7417:Rome', seriesGroupId: 'yaxis_main_group', name: 'Rome', diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js index c979920caac6d..239f1d4f1838e 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.js @@ -32,7 +32,7 @@ export function BarSeriesDecorator({ bars, color, stackAccessors, - stackAsPercentage, + stackMode, xScaleType, yScaleType, timeZone, @@ -59,7 +59,7 @@ export function BarSeriesDecorator({ y1AccessorFormat, y0AccessorFormat, stackAccessors, - stackAsPercentage, + stackMode, xScaleType, yScaleType, timeZone, diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js index ab33524fc5b77..94ec53b887dda 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/bar_decorator.test.js @@ -33,7 +33,7 @@ describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries [1557003600000, 9], ], hideInLegend: false, - stackAsPercentage: false, + stackMode: undefined, seriesId: '61ca57f1-469d-11e7-af02-69e470af7417:Rome', seriesGroupId: 'yaxis_main_group', name: 'Rome', diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index 6b5d84dc56981..5f0cc5188b1fd 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -29,6 +29,7 @@ import { AnnotationDomainTypes, LineAnnotation, TooltipType, + StackMode, } from '@elastic/charts'; import { EuiIcon } from '@elastic/eui'; import { getTimezone } from '../../../lib/get_timezone'; @@ -197,7 +198,7 @@ export const TimeSeries = ({ bars={bars} color={finalColor} stackAccessors={stackAccessors} - stackAsPercentage={isPercentage} + stackMode={isPercentage ? StackMode.Percentage : undefined} xScaleType={xScaleType} yScaleType={yScaleType} timeZone={timeZone} @@ -222,7 +223,7 @@ export const TimeSeries = ({ lines={lines} color={finalColor} stackAccessors={stackAccessors} - stackAsPercentage={isPercentage} + stackMode={isPercentage ? StackMode.Percentage : undefined} points={points} xScaleType={xScaleType} yScaleType={yScaleType} @@ -248,8 +249,10 @@ export const TimeSeries = ({ position={position} domain={domain} hide={hide} - showGridLines={showGrid} - gridLineStyle={GRID_LINE_CONFIG} + gridLine={{ + ...GRID_LINE_CONFIG, + visible: showGrid, + }} tickFormat={tickFormatter} /> ))} @@ -259,8 +262,10 @@ export const TimeSeries = ({ position={Position.Bottom} title={xAxisLabel} tickFormat={xAxisFormatter} - showGridLines={showGrid} - gridLineStyle={GRID_LINE_CONFIG} + gridLine={{ + ...GRID_LINE_CONFIG, + visible: showGrid, + }} /> ); diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap index 541265c05057a..4acb3985320da 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap @@ -14,7 +14,7 @@ Object { "seriesId": [Function], "sortIndex": [Function], "stackAccessors": [Function], - "stackAsPercentage": [Function], + "stackMode": [Function], "timeZone": [Function], "useDefaultGroupDomain": [Function], "xScaleType": [Function], @@ -31,7 +31,7 @@ Object { "seriesId": [Function], "sortIndex": [Function], "stackAccessors": [Function], - "stackAsPercentage": [Function], + "stackMode": [Function], "timeZone": [Function], "useDefaultGroupDomain": [Function], "xScaleType": [Function], diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js index b14b84dcd1fe4..29b1e9f4f20e3 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/model/charts.js @@ -30,7 +30,7 @@ const Chart = { data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, hideInLegend: PropTypes.bool.isRequired, color: PropTypes.string.isRequired, - stackAsPercentage: PropTypes.bool.isRequired, + stackMode: PropTypes.oneOf(['percentage', 'wiggle', 'silhouette']), stackAccessors: PropTypes.arrayOf(PropTypes.number), xScaleType: PropTypes.string, yScaleType: PropTypes.string, diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts index d7e6560a8dc97..8e96037b9947d 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts @@ -33,14 +33,12 @@ describe('TSVB theme', () => { }); it('should return a highcontrast color theme for a different background', () => { // red use a near full-black color - expect(getBaseTheme(LIGHT_THEME, 'red').axes.axisTitleStyle.fill).toEqual('rgb(23,23,23)'); + expect(getBaseTheme(LIGHT_THEME, 'red').axes.axisTitle.fill).toEqual('rgb(23,23,23)'); // violet increased the text color to full white for higer contrast - expect(getBaseTheme(LIGHT_THEME, '#ba26ff').axes.axisTitleStyle.fill).toEqual( - 'rgb(255,255,255)' - ); + expect(getBaseTheme(LIGHT_THEME, '#ba26ff').axes.axisTitle.fill).toEqual('rgb(255,255,255)'); // light yellow, prefer the LIGHT_THEME fill color because already with a good contrast - expect(getBaseTheme(LIGHT_THEME, '#fff49f').axes.axisTitleStyle.fill).toEqual('#333'); + expect(getBaseTheme(LIGHT_THEME, '#fff49f').axes.axisTitle.fill).toEqual('#333'); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts index 0e13fd7ef68f9..030c54fec888c 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts @@ -109,27 +109,27 @@ export function getBaseTheme(baseTheme: Theme, bgColor?: string | null): Theme { const mainTheme = bgLuminosity <= 0.179 ? DARK_THEME : LIGHT_THEME; const color = findBestContrastColor( bgColor, - LIGHT_THEME.axes.axisTitleStyle.fill, - DARK_THEME.axes.axisTitleStyle.fill + LIGHT_THEME.axes.axisTitle.fill, + DARK_THEME.axes.axisTitle.fill ); return { ...mainTheme, axes: { ...mainTheme.axes, - axisTitleStyle: { - ...mainTheme.axes.axisTitleStyle, + axisTitle: { + ...mainTheme.axes.axisTitle, fill: color, }, - tickLabelStyle: { - ...mainTheme.axes.tickLabelStyle, + tickLabel: { + ...mainTheme.axes.tickLabel, fill: color, }, - axisLineStyle: { - ...mainTheme.axes.axisLineStyle, + axisLine: { + ...mainTheme.axes.axisLine, stroke: color, }, - tickLineStyle: { - ...mainTheme.axes.tickLineStyle, + tickLine: { + ...mainTheme.axes.tickLine, stroke: color, }, }, diff --git a/src/plugins/vis_type_vega/public/services.ts b/src/plugins/vis_type_vega/public/services.ts index 7d988d464b52b..acd02a6dd42f8 100644 --- a/src/plugins/vis_type_vega/public/services.ts +++ b/src/plugins/vis_type_vega/public/services.ts @@ -26,7 +26,7 @@ import { import { DataPublicPluginStart } from '../../data/public'; import { createGetterSetter } from '../../kibana_utils/public'; -import { MapsLegacyConfigType } from '../../maps_legacy/public'; +import { MapsLegacyConfig } from '../../maps_legacy/config'; export const [getData, setData] = createGetterSetter('Data'); @@ -53,7 +53,7 @@ export const [getInjectedVars, setInjectedVars] = createGetterSetter<{ emsTileLayerId: unknown; }>('InjectedVars'); -export const [getMapsLegacyConfig, setMapsLegacyConfig] = createGetterSetter( +export const [getMapsLegacyConfig, setMapsLegacyConfig] = createGetterSetter( 'MapsLegacyConfig' ); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap index ab3e273f99c05..ed7ae45eed3a5 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap +++ b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap @@ -83,6 +83,8 @@ exports[`ValueAxesPanel component should init with the default set of props 1`] } id="yAxisAccordionValueAxis-1" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} key="ValueAxis-1" paddingSize="none" > @@ -264,6 +266,8 @@ exports[`ValueAxesPanel component should init with the default set of props 1`] } id="yAxisAccordionValueAxis-2" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} key="ValueAxis-2" paddingSize="none" > diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap index 36f5480e85406..c4142fb487b6a 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap +++ b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap @@ -124,6 +124,8 @@ exports[`ValueAxisOptions component should init with the default set of props 1` className="visEditorSidebar__section visEditorSidebar__collapsible" id="yAxisOptionsAccordionValueAxis-1" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > { - sinon.stub(chrome, 'dangerouslyGetActiveInjector').returns(Promise.resolve($injector)); - }) - ); -} - -/** - * This methods tears down the stub for chrome.dangerouslyGetActiveInjector. You must call it - * in a place where afterEach is allowed to be called. - */ -export function teardownInjectorStub() { - afterEach(() => { - chrome.dangerouslyGetActiveInjector.restore(); - }); -} - -/** - * This method combines setupInjectorStub and teardownInjectorStub in one method. - * It can be used if you can call the other two methods directly behind each other. - */ -export function setupAndTeardownInjectorStub() { - setupInjectorStub(); - teardownInjectorStub(); -} diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js index 73c36c7562e8b..b7b795ae11c96 100644 --- a/test/functional/apps/dashboard/embeddable_rendering.js +++ b/test/functional/apps/dashboard/embeddable_rendering.js @@ -99,7 +99,8 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.vegaTextsDoNotExist(['5,000']); }; - describe('dashboard embeddable rendering', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/76245 + describe.skip('dashboard embeddable rendering', function describeIndexTests() { before(async () => { await security.testUser.setRoles(['kibana_admin', 'animals', 'test_logstash_reader']); await esArchiver.load('dashboard/current/kibana'); diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index d08bfe3b90913..c88670ee8b741 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }) { const retry = getService('retry'); const kibanaServer = getService('kibanaServer'); const browser = getService('browser'); + const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects([ 'visualize', 'header', @@ -148,6 +149,10 @@ export default function ({ getService, getPageObjects }) { }); }); + it('should not show advanced json for count agg', async function () { + await testSubjects.missingOrFail('advancedParams-1'); + }); + it('should put secondary axis on the right', async function () { const length = await PageObjects.visChart.getRightValueAxes(); expect(length).to.be(1); diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index 74d5798d127c3..06828e8e98cce 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); const esArchiver = getService('esArchiver'); const log = getService('log'); const inspector = getService('inspector'); @@ -146,5 +147,79 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(newValue).to.eql('10'); }); }); + + describe('browser history changes', () => { + it('should activate previous/next chart tab and panel config', async () => { + await PageObjects.visualBuilder.resetPage(); + + log.debug('Click metric chart'); + await PageObjects.visualBuilder.clickMetric(); + await PageObjects.visualBuilder.checkMetricTabIsPresent(); + await PageObjects.visualBuilder.checkTabIsSelected('metric'); + + log.debug('Click Top N chart'); + await PageObjects.visualBuilder.clickTopN(); + await PageObjects.visualBuilder.checkTopNTabIsPresent(); + await PageObjects.visualBuilder.checkTabIsSelected('top_n'); + + log.debug('Go back in browser history'); + await browser.goBack(); + + log.debug('Check metric chart and panel config is rendered'); + await PageObjects.visualBuilder.checkMetricTabIsPresent(); + await PageObjects.visualBuilder.checkTabIsSelected('metric'); + await PageObjects.visualBuilder.checkPanelConfigIsPresent('metric'); + + log.debug('Go back in browser history'); + await browser.goBack(); + + log.debug('Check timeseries chart and panel config is rendered'); + await PageObjects.visualBuilder.checkTimeSeriesChartIsPresent(); + await PageObjects.visualBuilder.checkTabIsSelected('timeseries'); + await PageObjects.visualBuilder.checkPanelConfigIsPresent('timeseries'); + + log.debug('Go forward in browser history'); + await browser.goForward(); + + log.debug('Check metric chart and panel config is rendered'); + await PageObjects.visualBuilder.checkMetricTabIsPresent(); + await PageObjects.visualBuilder.checkTabIsSelected('metric'); + await PageObjects.visualBuilder.checkPanelConfigIsPresent('metric'); + }); + + it('should update panel config', async () => { + await PageObjects.visualBuilder.resetPage(); + + const initialLegendItems = ['Count: 156']; + const finalLegendItems = ['jpg: 106', 'css: 22', 'png: 14', 'gif: 8', 'php: 6']; + + log.debug('Group metrics by terms: extension.raw'); + await PageObjects.visualBuilder.setMetricsGroupByTerms('extension.raw'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + const legendItems1 = await PageObjects.visualBuilder.getLegendItemsContent(); + expect(legendItems1).to.eql(finalLegendItems); + + log.debug('Go back in browser history'); + await browser.goBack(); + const isTermsSelected = await PageObjects.visualBuilder.checkSelectedMetricsGroupByValue( + 'Terms' + ); + expect(isTermsSelected).to.be(true); + + log.debug('Go back in browser history'); + await browser.goBack(); + await PageObjects.visualBuilder.checkSelectedMetricsGroupByValue('Everything'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + const legendItems2 = await PageObjects.visualBuilder.getLegendItemsContent(); + expect(legendItems2).to.eql(initialLegendItems); + + log.debug('Go forward twice in browser history'); + await browser.goForward(); + await browser.goForward(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + const legendItems3 = await PageObjects.visualBuilder.getLegendItemsContent(); + expect(legendItems3).to.eql(finalLegendItems); + }); + }); }); } diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index f376c39ff67bb..a95d535281fa3 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -64,6 +64,19 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro } } + public async checkTabIsSelected(chartType: string) { + const chartTypeBtn = await testSubjects.find(`${chartType}TsvbTypeBtn`); + const isSelected = await chartTypeBtn.getAttribute('aria-selected'); + + if (isSelected !== 'true') { + throw new Error(`TSVB ${chartType} tab is not selected`); + } + } + + public async checkPanelConfigIsPresent(chartType: string) { + await testSubjects.existOrFail(`tvbPanelConfig__${chartType}`); + } + public async checkVisualBuilderIsPresent() { await this.checkTabIsLoaded('tvbVisEditor', 'Time Series'); } @@ -558,9 +571,40 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro return await find.allByCssSelector('.echLegendItem'); } + public async getLegendItemsContent(): Promise { + const legendList = await find.byCssSelector('.echLegendList'); + const $ = await legendList.parseDomContent(); + + return $('li') + .toArray() + .map((li) => { + const label = $(li).find('.echLegendItem__label').text(); + const value = $(li).find('.echLegendItem__extra').text(); + + return `${label}: ${value}`; + }); + } + public async getSeries(): Promise { return await find.allByCssSelector('.tvbSeriesEditor'); } + + public async setMetricsGroupByTerms(field: string) { + const groupBy = await find.byCssSelector( + '.tvbAggRow--split [data-test-subj="comboBoxInput"]' + ); + await comboBox.setElement(groupBy, 'Terms', { clickWithMouse: true }); + await PageObjects.common.sleep(1000); + const byField = await testSubjects.find('groupByField'); + await comboBox.setElement(byField, field, { clickWithMouse: true }); + } + + public async checkSelectedMetricsGroupByValue(value: string) { + const groupBy = await find.byCssSelector( + '.tvbAggRow--split [data-test-subj="comboBoxInput"]' + ); + return await comboBox.isOptionSelected(groupBy, value); + } } return new VisualBuilderPage(); diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 9fcb38efce0db..8cac43d97317b 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -445,6 +445,15 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } else if (type === 'custom') { await comboBox.setCustom('visEditorInterval', newValue); } else { + if (type === 'numeric') { + const autoMode = await testSubjects.getAttribute( + `visEditorIntervalSwitch${aggNth}`, + 'aria-checked' + ); + if (autoMode === 'true') { + await testSubjects.click(`visEditorIntervalSwitch${aggNth}`); + } + } if (append) { await testSubjects.append(`visEditorInterval${aggNth}`, String(newValue)); } else { diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 37837747a69dd..7f3744c16397a 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "27.4.1", + "@elastic/eui": "28.2.0", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "react-dom": "^16.12.0", diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json index 0e1a0b550ca15..acb0cf67ac5c7 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "27.4.1", + "@elastic/eui": "28.2.0", "react": "^16.12.0", "typescript": "4.0.2" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index ebf0e046eca4c..ff84c25400af0 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "27.4.1", + "@elastic/eui": "28.2.0", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "typescript": "4.0.2" diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js deleted file mode 100644 index 7aa12ea7a1130..0000000000000 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SelfChangingEditor } from './self_changing_editor'; -import { SelfChangingComponent } from './self_changing_components'; - -import { npSetup } from '../../../../../../src/legacy/ui/public/new_platform'; - -npSetup.plugins.visualizations.createReactVisualization({ - name: 'self_changing_vis', - title: 'Self Changing Vis', - icon: 'controlsHorizontal', - description: - 'This visualization is able to change its own settings, that you could also set in the editor.', - visConfig: { - component: SelfChangingComponent, - defaults: { - counter: 0, - }, - }, - editorConfig: { - optionTabs: [ - { - name: 'options', - title: 'Options', - editor: SelfChangingEditor, - }, - ], - }, - requestHandler: 'none', -}); diff --git a/tsconfig.json b/tsconfig.json index 66906fb18bb80..819cb5dc4c1e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,9 +7,6 @@ "kibana/public": ["src/core/public"], "kibana/server": ["src/core/server"], "plugins/*": ["src/legacy/core_plugins/*/public/"], - "ui/*": [ - "src/legacy/ui/public/*" - ], "test_utils/*": [ "src/test_utils/public/*" ], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 69ad9ad33bf72..7d21f958bb80b 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -35,7 +35,7 @@ "xpack.main": "legacy/plugins/xpack_main", "xpack.maps": ["plugins/maps", "legacy/plugins/maps"], "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], - "xpack.monitoring": ["plugins/monitoring", "legacy/plugins/monitoring"], + "xpack.monitoring": ["plugins/monitoring"], "xpack.remoteClusters": "plugins/remote_clusters", "xpack.painlessLab": "plugins/painless_lab", "xpack.reporting": ["plugins/reporting"], diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index a0574dbdf36da..a693e008db6ea 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -13,7 +13,6 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector moduleNameMapper: { '@elastic/eui$': `${kibanaDirectory}/node_modules/@elastic/eui/test-env`, '@elastic/eui/lib/(.*)?': `${kibanaDirectory}/node_modules/@elastic/eui/test-env/$1`, - '^ui/(.*)': `${kibanaDirectory}/src/legacy/ui/public/$1`, '^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`, 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx index 7d915ea23c66f..58916f26121d4 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -44,6 +44,7 @@ export class DashboardToUrlDrilldown implements Drilldown { public readonly order = 8; readonly minimalLicense = 'gold'; // example of minimal license support + readonly licenseFeatureName = 'Sample URL Drilldown'; public readonly getDisplayName = () => 'Go to URL (example)'; diff --git a/x-pack/index.js b/x-pack/index.js index b984782df3986..074b8e6859dc2 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -5,10 +5,9 @@ */ import { xpackMain } from './legacy/plugins/xpack_main'; -import { monitoring } from './legacy/plugins/monitoring'; import { security } from './legacy/plugins/security'; import { spaces } from './legacy/plugins/spaces'; module.exports = function (kibana) { - return [xpackMain(kibana), monitoring(kibana), spaces(kibana), security(kibana)]; + return [xpackMain(kibana), spaces(kibana), security(kibana)]; }; diff --git a/x-pack/legacy/plugins/monitoring/README.md b/x-pack/legacy/plugins/monitoring/README.md deleted file mode 100644 index 0222f06e7ae91..0000000000000 --- a/x-pack/legacy/plugins/monitoring/README.md +++ /dev/null @@ -1,89 +0,0 @@ -## Using Monitoring - -The easiest way to get to know the new Monitoring is probably by [reading the -docs](https://github.com/elastic/x-plugins/blob/master/docs/public/marvel/index.asciidoc). - -Install the distribution the way a customer would is pending the first release -of Unified X-Pack plugins. - -## Developing - -You will need to get Elasticsearch and X-Pack plugins for ES that match the -version of the UI. The best way to do this is to run `gradle run` from a clone -of the x-plugins repository. - -To set up Monitoring and automatic file syncing code changes into Kibana's plugin -directory, clone the kibana and x-plugins repos in the same directory and from -`x-plugins/kibana/monitoring`, run `yarn start`. - -Once the syncing process has run at least once, start the Kibana server in -development mode. It will handle restarting the server and re-optimizing the -bundles as-needed. Go to https://localhost:5601 and click Monitoring from the App -Drawer. - -## Running tests - -- Run the command: - ``` - yarn test - ``` - -- Debug tests -Add a `debugger` line to create a breakpoint, and then: - - ``` - gulp sync && mocha debug --compilers js:@babel/register /pathto/kibana/plugins/monitoring/pathto/__tests__/testfile.js - ``` - -## Multicluster Setup for Development - -To run the UI with multiple clusters, the easiest way is to run 2 nodes out of -the same Elasticsearch directory, but use different start up commands for each one. One -node will be assigned to the "monitoring" cluster and the other will be for the "production" -cluster. - -1. Add the Security users: - ``` - % ./bin/x-pack/users useradd -r remote_monitoring_agent -p notsecure remote - % ./bin/x-pack/users useradd -r monitoring_user -p notsecure monitoring_user - ``` - -1. Start up the Monitoring cluster: - ``` - % ./bin/elasticsearch \ - -Ehttp.port=9210 \ - -Ecluster.name=monitoring \ - -Epath.data=monitoring-data \ - -Enode.name=monitor1node1 - ``` - -1. Start up the Production cluster: - ``` - % ./bin/elasticsearch \ - -Expack.monitoring.exporters.id2.type=http \ - -Expack.monitoring.exporters.id2.host=http://127.0.0.1:9210 \ - -Expack.monitoring.exporters.id2.auth.username=remote \ - -Expack.monitoring.exporters.id2.auth.password=notsecure \ - -Ecluster.name=production \ - -Enode.name=prod1node1 \ - -Epath.data=production-data - ``` - -1. Set the Kibana config: - ``` - % cat config/kibana.dev.yml - monitoring.ui.elasticsearch: - hosts: "http://localhost:9210" - username: "kibana_system" - password: "changeme" - ``` - -1. Start another Kibana instance: - ``` - % yarn start - ``` - -1. Start a Kibana instance connected to the Monitoring cluster (for running queries in Sense on Monitoring data): - ``` - % ./bin/kibana --config config/kibana.dev.yml --elasticsearch.hosts http://localhost:9210 --server.name monitoring-kibana --server.port 5611 - ``` diff --git a/x-pack/legacy/plugins/monitoring/config.ts b/x-pack/legacy/plugins/monitoring/config.ts deleted file mode 100644 index 52f4b866dd7b2..0000000000000 --- a/x-pack/legacy/plugins/monitoring/config.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * User-configurable settings for xpack.monitoring via configuration schema - * @param {Object} Joi - HapiJS Joi module that allows for schema validation - * @return {Object} config schema - */ -export const config = (Joi: any) => { - const DEFAULT_REQUEST_HEADERS = ['authorization']; - - return Joi.object({ - enabled: Joi.boolean().default(true), - ui: Joi.object({ - enabled: Joi.boolean().default(true), - logs: Joi.object({ - index: Joi.string().default('filebeat-*'), - }).default(), - ccs: Joi.object({ - enabled: Joi.boolean().default(true), - }).default(), - container: Joi.object({ - elasticsearch: Joi.object({ - enabled: Joi.boolean().default(false), - }).default(), - logstash: Joi.object({ - enabled: Joi.boolean().default(false), - }).default(), - }).default(), - max_bucket_size: Joi.number().default(10000), - min_interval_seconds: Joi.number().default(10), - show_license_expiration: Joi.boolean().default(true), - elasticsearch: Joi.object({ - customHeaders: Joi.object().default({}), - logQueries: Joi.boolean().default(false), - requestHeadersWhitelist: Joi.array().items().single().default(DEFAULT_REQUEST_HEADERS), - sniffOnStart: Joi.boolean().default(false), - sniffInterval: Joi.number().allow(false).default(false), - sniffOnConnectionFault: Joi.boolean().default(false), - hosts: Joi.array() - .items(Joi.string().uri({ scheme: ['http', 'https'] })) - .single(), // if empty, use Kibana's connection config - username: Joi.string(), - password: Joi.string(), - requestTimeout: Joi.number().default(30000), - pingTimeout: Joi.number().default(30000), - ssl: Joi.object({ - verificationMode: Joi.string().valid('none', 'certificate', 'full').default('full'), - certificateAuthorities: Joi.array().single().items(Joi.string()), - certificate: Joi.string(), - key: Joi.string(), - keyPassphrase: Joi.string(), - keystore: Joi.object({ - path: Joi.string(), - password: Joi.string(), - }).default(), - truststore: Joi.object({ - path: Joi.string(), - password: Joi.string(), - }).default(), - alwaysPresentCertificate: Joi.boolean().default(false), - }).default(), - apiVersion: Joi.string().default('master'), - logFetchCount: Joi.number().default(10), - }).default(), - }).default(), - kibana: Joi.object({ - collection: Joi.object({ - enabled: Joi.boolean().default(true), - interval: Joi.number().default(10000), // op status metrics get buffered at `ops.interval` and flushed to the bulk endpoint at this interval - }).default(), - }).default(), - elasticsearch: Joi.object({ - customHeaders: Joi.object().default({}), - logQueries: Joi.boolean().default(false), - requestHeadersWhitelist: Joi.array().items().single().default(DEFAULT_REQUEST_HEADERS), - sniffOnStart: Joi.boolean().default(false), - sniffInterval: Joi.number().allow(false).default(false), - sniffOnConnectionFault: Joi.boolean().default(false), - hosts: Joi.array() - .items(Joi.string().uri({ scheme: ['http', 'https'] })) - .single(), // if empty, use Kibana's connection config - username: Joi.string(), - password: Joi.string(), - requestTimeout: Joi.number().default(30000), - pingTimeout: Joi.number().default(30000), - ssl: Joi.object({ - verificationMode: Joi.string().valid('none', 'certificate', 'full').default('full'), - certificateAuthorities: Joi.array().single().items(Joi.string()), - certificate: Joi.string(), - key: Joi.string(), - keyPassphrase: Joi.string(), - keystore: Joi.object({ - path: Joi.string(), - password: Joi.string(), - }).default(), - truststore: Joi.object({ - path: Joi.string(), - password: Joi.string(), - }).default(), - alwaysPresentCertificate: Joi.boolean().default(false), - }).default(), - apiVersion: Joi.string().default('master'), - }).default(), - cluster_alerts: Joi.object({ - enabled: Joi.boolean().default(true), - email_notifications: Joi.object({ - enabled: Joi.boolean().default(true), - email_address: Joi.string().email(), - }).default(), - }).default(), - licensing: Joi.object({ - api_polling_frequency: Joi.number().default(30001), - }), - agent: Joi.object({ - interval: Joi.string() - .regex(/[\d\.]+[yMwdhms]/) - .default('10s'), - }).default(), - tests: Joi.object({ - cloud_detector: Joi.object({ - enabled: Joi.boolean().default(true), - }).default(), - }).default(), - }).default(); -}; diff --git a/x-pack/legacy/plugins/monitoring/index.ts b/x-pack/legacy/plugins/monitoring/index.ts deleted file mode 100644 index f03e1ebc009f5..0000000000000 --- a/x-pack/legacy/plugins/monitoring/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Hapi from 'hapi'; -import { config } from './config'; - -/** - * Invokes plugin modules to instantiate the Monitoring plugin for Kibana - * @param kibana {Object} Kibana plugin instance - * @return {Object} Monitoring UI Kibana plugin object - */ -const deps = ['kibana', 'elasticsearch', 'xpack_main']; -export const monitoring = (kibana: any) => { - return new kibana.Plugin({ - require: deps, - id: 'monitoring', - configPrefix: 'monitoring', - init(server: Hapi.Server) { - const npMonitoring = server.newPlatform.setup.plugins.monitoring as object & { - registerLegacyAPI: (api: unknown) => void; - }; - if (npMonitoring) { - const kbnServerStatus = this.kbnServer.status; - npMonitoring.registerLegacyAPI({ - getServerStatus: () => { - const status = kbnServerStatus.toJSON(); - return status?.overall?.state; - }, - }); - } - }, - config, - }); -}; diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js index b83c5a5ff606e..854fba6624719 100644 --- a/x-pack/legacy/plugins/xpack_main/index.js +++ b/x-pack/legacy/plugins/xpack_main/index.js @@ -6,12 +6,9 @@ import { resolve } from 'path'; import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; -import { replaceInjectedVars } from './server/lib/replace_injected_vars'; import { setupXPackMain } from './server/lib/setup_xpack_main'; import { xpackInfoRoute, settingsRoute } from './server/routes/api/v1'; -export { callClusterFactory } from './server/lib/call_cluster_factory'; - export const xpackMain = (kibana) => { return new kibana.Plugin({ id: 'xpack_main', @@ -25,32 +22,7 @@ export const xpackMain = (kibana) => { }).default(); }, - uiCapabilities(server) { - const featuresPlugin = server.newPlatform.setup.plugins.features; - if (!featuresPlugin) { - throw new Error('New Platform XPack Features plugin is not available.'); - } - return featuresPlugin.getFeaturesUICapabilities(); - }, - - uiExports: { - replaceInjectedVars, - injectDefaultVars(server) { - const config = server.config(); - - return { - activeSpace: null, - spacesEnabled: config.get('xpack.spaces.enabled'), - }; - }, - }, - init(server) { - const featuresPlugin = server.newPlatform.setup.plugins.features; - if (!featuresPlugin) { - throw new Error('New Platform XPack Features plugin is not available.'); - } - mirrorPluginStatus(server.plugins.elasticsearch, this, 'yellow', 'red'); setupXPackMain(server); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/call_cluster_factory.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/call_cluster_factory.js deleted file mode 100644 index abe0d327d7b5c..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/call_cluster_factory.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { callClusterFactory } from '../call_cluster_factory'; - -describe('callClusterFactory', () => { - let mockServer; - let mockCluster; - - beforeEach(() => { - mockCluster = { - callWithRequest: sinon.stub().returns(Promise.resolve({ hits: { total: 0 } })), - callWithInternalUser: sinon.stub().returns(Promise.resolve({ hits: { total: 0 } })), - }; - mockServer = { - plugins: { - elasticsearch: { - getCluster: sinon.stub().withArgs('admin').returns(mockCluster), - }, - }, - log() {}, - }; - }); - - it('returns an object with getter methods', () => { - const _callClusterFactory = callClusterFactory(mockServer); - expect(_callClusterFactory).to.be.an('object'); - expect(_callClusterFactory.getCallClusterWithReq).to.be.an('function'); - expect(_callClusterFactory.getCallClusterInternal).to.be.an('function'); - expect(mockCluster.callWithRequest.called).to.be(false); - expect(mockCluster.callWithInternalUser.called).to.be(false); - }); - - describe('getCallClusterWithReq', () => { - it('throws an error if req is not passed', async () => { - const runCallCluster = () => callClusterFactory(mockServer).getCallClusterWithReq(); - expect(runCallCluster).to.throwException(); - }); - - it('returns a method that wraps callWithRequest', async () => { - const mockReq = { - headers: { - authorization: 'Basic dSQzcm5AbTM6cEAkJHcwcmQ=', // u$3rn@m3:p@$$w0rd - }, - }; - const callCluster = callClusterFactory(mockServer).getCallClusterWithReq(mockReq); - - const result = await callCluster('search', { body: { match: { match_all: {} } } }); - expect(result).to.eql({ hits: { total: 0 } }); - - expect(mockCluster.callWithInternalUser.called).to.be(false); - expect(mockCluster.callWithRequest.calledOnce).to.be(true); - const [req, method] = mockCluster.callWithRequest.getCall(0).args; - expect(req).to.be(mockReq); - expect(method).to.be('search'); - }); - }); - - describe('getCallClusterInternal', () => { - it('returns a method that wraps callWithInternalUser', async () => { - const callCluster = callClusterFactory(mockServer).getCallClusterInternal(); - - const result = await callCluster('search', { body: { match: { match_all: {} } } }); - expect(result).to.eql({ hits: { total: 0 } }); - - expect(mockCluster.callWithRequest.called).to.be(false); - expect(mockCluster.callWithInternalUser.calledOnce).to.be(true); - const [method] = mockCluster.callWithInternalUser.getCall(0).args; - expect(method).to.eql('search'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/inject_xpack_info_signature.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/inject_xpack_info_signature.js deleted file mode 100644 index 420f3b2d6631c..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/inject_xpack_info_signature.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import { injectXPackInfoSignature } from '../inject_xpack_info_signature'; - -describe('injectXPackInfoSignature()', () => { - class MockErrorResponse extends Error { - constructor() { - super(); - this.output = { - headers: {}, - }; - - this.headers = {}; - } - } - - const fakeH = { continue: 'blah' }; - - let mockXPackInfo; - beforeEach(() => { - mockXPackInfo = sinon.stub({ - isAvailable() {}, - getSignature() {}, - refreshNow() {}, - }); - }); - - describe('error response', () => { - it('refreshes `xpackInfo` and do not inject signature if it is not available.', async () => { - mockXPackInfo.isAvailable.returns(true); - mockXPackInfo.getSignature.returns('this-should-never-be-set'); - - // We need this to make sure the code waits for `refreshNow` to complete before it tries - // to access its properties. - mockXPackInfo.refreshNow = () => { - return new Promise((resolve) => { - mockXPackInfo.isAvailable.returns(false); - resolve(); - }); - }; - - const mockResponse = new MockErrorResponse(); - const response = await injectXPackInfoSignature( - mockXPackInfo, - { response: mockResponse }, - fakeH - ); - - expect(mockResponse.headers).to.eql({}); - expect(mockResponse.output.headers).to.eql({}); - expect(response).to.be(fakeH.continue); - }); - - it('refreshes `xpackInfo` and injects its updated signature.', async () => { - mockXPackInfo.isAvailable.returns(true); - mockXPackInfo.getSignature.returns('old-signature'); - - // We need this to make sure the code waits for `refreshNow` to complete before it tries - // to access its properties. - mockXPackInfo.refreshNow = () => { - return new Promise((resolve) => { - mockXPackInfo.getSignature.returns('new-signature'); - resolve(); - }); - }; - - const mockResponse = new MockErrorResponse(); - const response = await injectXPackInfoSignature( - mockXPackInfo, - { response: mockResponse }, - fakeH - ); - - expect(mockResponse.headers).to.eql({}); - expect(mockResponse.output.headers).to.eql({ - 'kbn-xpack-sig': 'new-signature', - }); - expect(response).to.be(fakeH.continue); - }); - }); - - describe('non-error response', () => { - it('do not inject signature if `xpackInfo` is not available.', async () => { - mockXPackInfo.isAvailable.returns(false); - mockXPackInfo.getSignature.returns('this-should-never-be-set'); - - const mockResponse = { headers: {}, output: { headers: {} } }; - const response = await injectXPackInfoSignature( - mockXPackInfo, - { response: mockResponse }, - fakeH - ); - - expect(mockResponse.headers).to.eql({}); - expect(mockResponse.output.headers).to.eql({}); - sinon.assert.notCalled(mockXPackInfo.refreshNow); - expect(response).to.be(fakeH.continue); - }); - - it('injects signature if `xpackInfo` is available.', async () => { - mockXPackInfo.isAvailable.returns(true); - mockXPackInfo.getSignature.returns('available-signature'); - - const mockResponse = { headers: {}, output: { headers: {} } }; - const response = await injectXPackInfoSignature( - mockXPackInfo, - { response: mockResponse }, - fakeH - ); - - expect(mockResponse.headers).to.eql({ - 'kbn-xpack-sig': 'available-signature', - }); - expect(mockResponse.output.headers).to.eql({}); - sinon.assert.notCalled(mockXPackInfo.refreshNow); - expect(response).to.be(fakeH.continue); - }); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js deleted file mode 100644 index ce6e20bd874b2..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/replace_injected_vars.js +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; - -import { replaceInjectedVars } from '../replace_injected_vars'; -import { KibanaRequest } from '../../../../../../../src/core/server'; - -const buildRequest = (path = '/app/kibana') => { - const get = sinon.stub(); - - return { - app: {}, - path, - route: { settings: {} }, - headers: {}, - raw: { - req: { - socket: {}, - }, - }, - getSavedObjectsClient: () => { - return { - get, - create: sinon.stub(), - - errors: { - isNotFoundError: (error) => { - return error.message === 'not found exception'; - }, - }, - }; - }, - }; -}; - -describe('replaceInjectedVars uiExport', () => { - it('sends xpack info if request is authenticated and license is not basic', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(); - const server = mockServer(); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql({ - a: 1, - xpackInitialInfo: { - b: 1, - }, - }); - - sinon.assert.calledOnce(server.newPlatform.setup.plugins.security.authc.isAuthenticated); - sinon.assert.calledWithExactly( - server.newPlatform.setup.plugins.security.authc.isAuthenticated, - sinon.match.instanceOf(KibanaRequest) - ); - }); - - it('sends the xpack info if security plugin is disabled', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(); - const server = mockServer(); - delete server.plugins.security; - delete server.newPlatform.setup.plugins.security; - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql({ - a: 1, - xpackInitialInfo: { - b: 1, - }, - }); - }); - - it('sends the xpack info if xpack license is basic', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(); - const server = mockServer(); - server.plugins.xpack_main.info.license.isOneOf.returns(true); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql({ - a: 1, - xpackInitialInfo: { - b: 1, - }, - }); - }); - - it('respects the telemetry opt-in document when opted-out', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(); - const server = mockServer(); - server.plugins.xpack_main.info.license.isOneOf.returns(true); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql({ - a: 1, - xpackInitialInfo: { - b: 1, - }, - }); - }); - - it('respects the telemetry opt-in document when opted-in', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(); - const server = mockServer(); - server.plugins.xpack_main.info.license.isOneOf.returns(true); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql({ - a: 1, - xpackInitialInfo: { - b: 1, - }, - }); - }); - - it('indicates that telemetry is opted-out when not loading an application', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(true, '/'); - const server = mockServer(); - server.plugins.xpack_main.info.license.isOneOf.returns(true); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql({ - a: 1, - xpackInitialInfo: { - b: 1, - }, - }); - }); - - it('sends the originalInjectedVars if not authenticated', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(); - const server = mockServer(); - server.newPlatform.setup.plugins.security.authc.isAuthenticated.returns(false); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql(originalInjectedVars); - }); - - it('sends the originalInjectedVars if xpack info is unavailable', async () => { - const originalInjectedVars = { a: 1 }; - const request = buildRequest(); - const server = mockServer(); - server.plugins.xpack_main.info.isAvailable.returns(false); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql(originalInjectedVars); - }); - - it('sends the originalInjectedVars (with xpackInitialInfo = undefined) if security is disabled, xpack info is unavailable', async () => { - const originalInjectedVars = { - a: 1, - uiCapabilities: { navLinks: { foo: true }, bar: { baz: true }, catalogue: { cfoo: true } }, - }; - const request = buildRequest(); - const server = mockServer(); - delete server.plugins.security; - server.plugins.xpack_main.info.isAvailable.returns(false); - - const newVars = await replaceInjectedVars(originalInjectedVars, request, server); - expect(newVars).to.eql({ - a: 1, - xpackInitialInfo: undefined, - uiCapabilities: { - navLinks: { foo: true }, - bar: { baz: true }, - catalogue: { - cfoo: true, - }, - }, - }); - }); -}); - -// creates a mock server object that defaults to being authenticated with a -// non-basic license -function mockServer() { - const getLicenseCheckResults = sinon.stub().returns({}); - return { - newPlatform: { - setup: { - plugins: { security: { authc: { isAuthenticated: sinon.stub().returns(true) } } }, - }, - }, - plugins: { - security: {}, - xpack_main: { - getFeatures: () => [ - { - id: 'mockFeature', - name: 'Mock Feature', - privileges: { - all: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: ['mockFeatureCapability'], - }, - }, - }, - ], - info: { - isAvailable: sinon.stub().returns(true), - feature: () => ({ - getLicenseCheckResults, - }), - license: { - isOneOf: sinon.stub().returns(false), - }, - toJSON: () => ({ b: 1 }), - }, - }, - }, - }; -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js index b4a2c090d6309..c34e27642d2ce 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js @@ -8,7 +8,6 @@ import { BehaviorSubject } from 'rxjs'; import sinon from 'sinon'; import { XPackInfo } from '../xpack_info'; import { setupXPackMain } from '../setup_xpack_main'; -import * as InjectXPackInfoSignatureNS from '../inject_xpack_info_signature'; describe('setupXPackMain()', () => { const sandbox = sinon.createSandbox(); @@ -19,7 +18,6 @@ describe('setupXPackMain()', () => { beforeEach(() => { sandbox.useFakeTimers(); - sandbox.stub(InjectXPackInfoSignatureNS, 'injectXPackInfoSignature'); mockElasticsearchPlugin = { getCluster: sinon.stub(), @@ -63,29 +61,9 @@ describe('setupXPackMain()', () => { setupXPackMain(mockServer); sinon.assert.calledWithExactly(mockServer.expose, 'info', sinon.match.instanceOf(XPackInfo)); - sinon.assert.calledWithExactly(mockServer.ext, 'onPreResponse', sinon.match.func); sinon.assert.calledWithExactly(mockElasticsearchPlugin.status.on, 'change', sinon.match.func); }); - it('onPreResponse hook calls `injectXPackInfoSignature` for every request.', () => { - setupXPackMain(mockServer); - - const xPackInfo = mockServer.expose.firstCall.args[1]; - const onPreResponse = mockServer.ext.firstCall.args[1]; - - const mockRequest = {}; - const mockReply = sinon.stub(); - - onPreResponse(mockRequest, mockReply); - - sinon.assert.calledWithExactly( - InjectXPackInfoSignatureNS.injectXPackInfoSignature, - xPackInfo, - sinon.match.same(mockRequest), - sinon.match.same(mockReply) - ); - }); - describe('Elasticsearch plugin state changes cause XPackMain plugin state change.', () => { let xPackInfo; let onElasticsearchPluginStatusChange; diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/call_cluster_factory.js b/x-pack/legacy/plugins/xpack_main/server/lib/call_cluster_factory.js deleted file mode 100644 index f946725e017ff..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/call_cluster_factory.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * Factory for acquiring a method that can send an authenticated query to Elasticsearch. - * - * The method can either: - * - take authentication from an HTTP request object, which should be used when - * the caller is an HTTP API - * - fabricate authentication using the system username and password from the - * Kibana config, which should be used when the caller is an internal process - * - * @param {Object} server: the Kibana server object - * @return {Function}: callCluster function - */ -export function callClusterFactory(server) { - const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster( - 'admin' - ); - - return { - /* - * caller is coming from a request, so use callWithRequest with actual - * Authorization header credentials - * @param {Object} req: HTTP request object - */ - getCallClusterWithReq(req) { - if (req === undefined) { - throw new Error('request object is required'); - } - return (...args) => callWithRequest(req, ...args); - }, - - getCallClusterInternal() { - /* - * caller is an internal function of the stats collection system, so use - * internal system user - */ - return callWithInternalUser; - }, - }; -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/inject_xpack_info_signature.js b/x-pack/legacy/plugins/xpack_main/server/lib/inject_xpack_info_signature.js deleted file mode 100644 index 166bd2b4755f0..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/inject_xpack_info_signature.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export async function injectXPackInfoSignature(info, request, h) { - // If we're returning an error response, refresh xpack info from - // Elasticsearch in case the error is due to a change in license information - // in Elasticsearch. - const isErrorResponse = request.response instanceof Error; - if (isErrorResponse) { - await info.refreshNow(); - } - - if (info.isAvailable()) { - // Note: request.response.output is used instead of request.response because - // evidently HAPI does not allow headers to be set on the latter in case of - // error responses. - const response = isErrorResponse ? request.response.output : request.response; - response.headers['kbn-xpack-sig'] = info.getSignature(); - } - - return h.continue; -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/replace_injected_vars.js b/x-pack/legacy/plugins/xpack_main/server/lib/replace_injected_vars.js deleted file mode 100644 index f09f97d44bfe8..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/replace_injected_vars.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { KibanaRequest } from '../../../../../../src/core/server'; - -export async function replaceInjectedVars(originalInjectedVars, request, server) { - const xpackInfo = server.plugins.xpack_main.info; - - const withXpackInfo = async () => ({ - ...originalInjectedVars, - xpackInitialInfo: xpackInfo.isAvailable() ? xpackInfo.toJSON() : undefined, - }); - - // security feature is disabled - if (!server.plugins.security || !server.newPlatform.setup.plugins.security) { - return await withXpackInfo(); - } - - // not enough license info to make decision one way or another - if (!xpackInfo.isAvailable()) { - return originalInjectedVars; - } - - // request is not authenticated - if ( - !(await server.newPlatform.setup.plugins.security.authc.isAuthenticated( - KibanaRequest.from(request) - )) - ) { - return originalInjectedVars; - } - - // plugin enabled, license is appropriate, request is authenticated - return await withXpackInfo(); -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js index 9196b210bba20..33b551bbe864f 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { injectXPackInfoSignature } from './inject_xpack_info_signature'; import { XPackInfo } from './xpack_info'; /** @@ -20,11 +19,6 @@ export function setupXPackMain(server) { server.expose('info', info); - server.ext('onPreResponse', (request, h) => injectXPackInfoSignature(info, request, h)); - - const { getFeatures } = server.newPlatform.setup.plugins.features; - server.expose('getFeatures', getFeatures); - const setPluginStatus = () => { if (info.isAvailable()) { server.plugins.xpack_main.status.green('Ready'); diff --git a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts b/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts index 2a59c2f1366d4..f4363a8e57b37 100644 --- a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts +++ b/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts @@ -11,5 +11,4 @@ export { XPackFeature } from './lib/xpack_info'; export interface XPackMainPlugin { info: XPackInfo; - getFeatures(): Feature[]; } diff --git a/x-pack/package.json b/x-pack/package.json index c2fc16153290e..0e9aef37aa253 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -195,7 +195,7 @@ "jsdom": "13.1.0", "jsondiffpatch": "0.4.1", "jsts": "^1.6.2", - "kea": "^2.0.1", + "kea": "2.2.0-rc.4", "loader-utils": "^1.2.3", "lz-string": "^1.4.4", "madge": "3.4.4", @@ -276,7 +276,7 @@ "@babel/runtime": "^7.11.2", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.9.3", - "@elastic/eui": "27.4.1", + "@elastic/eui": "28.2.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.0", @@ -313,6 +313,7 @@ "file-type": "^10.9.0", "font-awesome": "4.7.0", "fp-ts": "^2.3.1", + "geojson-vt": "^3.2.1", "get-port": "^5.0.0", "getos": "^3.1.0", "git-url-parse": "11.1.2", @@ -384,6 +385,7 @@ "ui-select": "0.19.8", "uuid": "3.3.2", "vscode-languageserver": "^5.2.1", + "vt-pbf": "^3.1.1", "webpack": "^4.41.5", "wellknown": "^0.5.0", "xml2js": "^0.4.22", diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts index 844aa6d2de7ed..7e938e766657c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts @@ -65,7 +65,7 @@ describe('request', () => { logger, proxySettings: { proxyUrl: 'http://localhost:1212', - rejectUnauthorizedCertificates: false, + proxyRejectUnauthorizedCertificates: false, }, }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts index 2468fab8c6ac5..8623a67e8a68e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts @@ -14,7 +14,7 @@ const logger = loggingSystemMock.create().get() as jest.Mocked; describe('getProxyAgent', () => { test('return HttpsProxyAgent for https proxy url', () => { const agent = getProxyAgent( - { proxyUrl: 'https://someproxyhost', rejectUnauthorizedCertificates: false }, + { proxyUrl: 'https://someproxyhost', proxyRejectUnauthorizedCertificates: false }, logger ); expect(agent instanceof HttpsProxyAgent).toBeTruthy(); @@ -22,7 +22,7 @@ describe('getProxyAgent', () => { test('return HttpProxyAgent for http proxy url', () => { const agent = getProxyAgent( - { proxyUrl: 'http://someproxyhost', rejectUnauthorizedCertificates: false }, + { proxyUrl: 'http://someproxyhost', proxyRejectUnauthorizedCertificates: false }, logger ); expect(agent instanceof HttpProxyAgent).toBeTruthy(); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts index bb4dadd3a4698..957d31546b019 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts @@ -23,7 +23,7 @@ export function getProxyAgent( protocol: proxyUrl.protocol, headers: proxySettings.proxyHeaders, // do not fail on invalid certs if value is false - rejectUnauthorized: proxySettings.rejectUnauthorizedCertificates, + rejectUnauthorized: proxySettings.proxyRejectUnauthorizedCertificates, }); } else { return new HttpProxyAgent(proxySettings.proxyUrl); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index f69a2fc1d209c..b6c4a4ea882e5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -73,7 +73,7 @@ describe('send_email module', () => { }, { proxyUrl: 'https://example.com', - rejectUnauthorizedCertificates: false, + proxyRejectUnauthorizedCertificates: false, } ); // @ts-expect-error @@ -140,6 +140,9 @@ describe('send_email module', () => { "host": "example.com", "port": 1025, "secure": false, + "tls": Object { + "rejectUnauthorized": undefined, + }, }, ] `); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index a4f32f1880cb5..dead8fee63d4f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -19,6 +19,7 @@ export interface SendEmailOptions { routing: Routing; content: Content; proxySettings?: ProxySettings; + rejectUnauthorized?: boolean; } // config validation ensures either service is set or host/port are set @@ -45,7 +46,7 @@ export interface Content { // send an email export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise { - const { transport, routing, content, proxySettings } = options; + const { transport, routing, content, proxySettings, rejectUnauthorized } = options; const { service, host, port, secure, user, password } = transport; const { from, to, cc, bcc } = routing; const { subject, message } = content; @@ -68,15 +69,18 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom transportConfig.host = host; transportConfig.port = port; transportConfig.secure = !!secure; - if (proxySettings && !transportConfig.secure) { + + if (proxySettings) { transportConfig.tls = { // do not fail on invalid certs if value is false - rejectUnauthorized: proxySettings?.rejectUnauthorizedCertificates, + rejectUnauthorized: proxySettings?.proxyRejectUnauthorizedCertificates, }; - } - if (proxySettings) { transportConfig.proxy = proxySettings.proxyUrl; transportConfig.headers = proxySettings.proxyHeaders; + } else if (!transportConfig.secure) { + transportConfig.tls = { + rejectUnauthorized, + }; } } diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts index b15d92cecba62..d98a41ed1f355 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts @@ -167,7 +167,7 @@ describe('execute()', () => { params: { message: 'this invocation should succeed' }, proxySettings: { proxyUrl: 'https://someproxyhost', - rejectUnauthorizedCertificates: false, + proxyRejectUnauthorizedCertificates: false, }, }); expect(response).toMatchInlineSnapshot(` @@ -206,7 +206,7 @@ describe('execute()', () => { params: { message: 'this invocation should succeed' }, proxySettings: { proxyUrl: 'https://someproxyhost', - rejectUnauthorizedCertificates: false, + proxyRejectUnauthorizedCertificates: false, }, }); expect(mockedLogger.debug).toHaveBeenCalledWith( diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index ac815a425a2b7..1d7edd5f6b38f 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -18,7 +18,8 @@ describe('config validation', () => { "*", ], "preconfigured": Object {}, - "rejectUnauthorizedCertificates": true, + "proxyRejectUnauthorizedCertificates": true, + "rejectUnauthorized": true, } `); }); @@ -34,7 +35,8 @@ describe('config validation', () => { }, }, }, - rejectUnauthorizedCertificates: false, + proxyRejectUnauthorizedCertificates: false, + rejectUnauthorized: false, }; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { @@ -55,7 +57,8 @@ describe('config validation', () => { "secrets": Object {}, }, }, - "rejectUnauthorizedCertificates": false, + "proxyRejectUnauthorizedCertificates": false, + "rejectUnauthorized": false, } `); }); diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 087a08f572c65..8823cea9f4452 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -34,7 +34,8 @@ export const configSchema = schema.object({ }), proxyUrl: schema.maybe(schema.string()), proxyHeaders: schema.maybe(schema.recordOf(schema.string(), schema.string())), - rejectUnauthorizedCertificates: schema.boolean({ defaultValue: true }), + proxyRejectUnauthorizedCertificates: schema.boolean({ defaultValue: true }), + rejectUnauthorized: schema.boolean({ defaultValue: true }), }); export type ActionsConfig = TypeOf; diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index fef70c3a48455..31c4d26d1793e 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginInitializerContext, PluginConfigDescriptor } from '../../../../src/core/server'; import { ActionsPlugin } from './plugin'; import { configSchema } from './config'; import { ActionsClient as ActionsClientClass } from './actions_client'; import { ActionsAuthorization as ActionsAuthorizationClass } from './authorization/actions_authorization'; +import { ActionsConfigType } from './types'; export type ActionsClient = PublicMethodsOf; export type ActionsAuthorization = PublicMethodsOf; @@ -24,6 +25,9 @@ export { PluginSetupContract, PluginStartContract } from './plugin'; export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext); -export const config = { +export const config: PluginConfigDescriptor = { schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('xpack.actions.whitelistedHosts', 'xpack.actions.allowedHosts'), + ], }; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 4fdf9f2523568..9d545600e61ee 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -34,7 +34,8 @@ describe('Actions Plugin', () => { enabledActionTypes: ['*'], allowedHosts: ['*'], preconfigured: {}, - rejectUnauthorizedCertificates: true, + proxyRejectUnauthorizedCertificates: true, + rejectUnauthorized: true, }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); @@ -195,7 +196,8 @@ describe('Actions Plugin', () => { secrets: {}, }, }, - rejectUnauthorizedCertificates: true, + proxyRejectUnauthorizedCertificates: true, + rejectUnauthorized: true, }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 413e6663105b8..a6c5899281658 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -323,7 +323,8 @@ export class ActionsPlugin implements Plugin, Plugi ? { proxyUrl: this.actionsConfig.proxyUrl, proxyHeaders: this.actionsConfig.proxyHeaders, - rejectUnauthorizedCertificates: this.actionsConfig.rejectUnauthorizedCertificates, + proxyRejectUnauthorizedCertificates: this.actionsConfig + .proxyRejectUnauthorizedCertificates, } : undefined, }); diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 0a7d6bf01b7ec..3e92ca331bb93 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -145,5 +145,5 @@ export interface ActionTaskExecutorParams { export interface ProxySettings { proxyUrl: string; proxyHeaders?: Record; - rejectUnauthorizedCertificates: boolean; + proxyRejectUnauthorizedCertificates: boolean; } diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 7c42fb6f12a54..aecf4af667603 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -120,6 +120,8 @@ exports[`Error TRACE_ID 1`] = `"trace id"`; exports[`Error TRANSACTION_BREAKDOWN_COUNT 1`] = `undefined`; +exports[`Error TRANSACTION_DOM_INTERACTIVE 1`] = `undefined`; + exports[`Error TRANSACTION_DURATION 1`] = `undefined`; exports[`Error TRANSACTION_ID 1`] = `"transaction id"`; @@ -132,6 +134,8 @@ exports[`Error TRANSACTION_RESULT 1`] = `undefined`; exports[`Error TRANSACTION_SAMPLED 1`] = `undefined`; +exports[`Error TRANSACTION_TIME_TO_FIRST_BYTE 1`] = `undefined`; + exports[`Error TRANSACTION_TYPE 1`] = `"request"`; exports[`Error TRANSACTION_URL 1`] = `undefined`; @@ -268,6 +272,8 @@ exports[`Span TRACE_ID 1`] = `"trace id"`; exports[`Span TRANSACTION_BREAKDOWN_COUNT 1`] = `undefined`; +exports[`Span TRANSACTION_DOM_INTERACTIVE 1`] = `undefined`; + exports[`Span TRANSACTION_DURATION 1`] = `undefined`; exports[`Span TRANSACTION_ID 1`] = `"transaction id"`; @@ -280,6 +286,8 @@ exports[`Span TRANSACTION_RESULT 1`] = `undefined`; exports[`Span TRANSACTION_SAMPLED 1`] = `undefined`; +exports[`Span TRANSACTION_TIME_TO_FIRST_BYTE 1`] = `undefined`; + exports[`Span TRANSACTION_TYPE 1`] = `undefined`; exports[`Span TRANSACTION_URL 1`] = `undefined`; @@ -416,6 +424,8 @@ exports[`Transaction TRACE_ID 1`] = `"trace id"`; exports[`Transaction TRANSACTION_BREAKDOWN_COUNT 1`] = `undefined`; +exports[`Transaction TRANSACTION_DOM_INTERACTIVE 1`] = `undefined`; + exports[`Transaction TRANSACTION_DURATION 1`] = `1337`; exports[`Transaction TRANSACTION_ID 1`] = `"transaction id"`; @@ -428,6 +438,8 @@ exports[`Transaction TRANSACTION_RESULT 1`] = `"transaction result"`; exports[`Transaction TRANSACTION_SAMPLED 1`] = `true`; +exports[`Transaction TRANSACTION_TIME_TO_FIRST_BYTE 1`] = `undefined`; + exports[`Transaction TRANSACTION_TYPE 1`] = `"transaction type"`; exports[`Transaction TRANSACTION_URL 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index 610a32e8e9b99..4aa68de9b8b32 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -99,3 +99,8 @@ export const TRANSACTION_URL = 'transaction.page.url'; export const CLIENT_GEO = 'client.geo'; export const USER_AGENT_DEVICE = 'user_agent.device.name'; export const USER_AGENT_OS = 'user_agent.os.name'; + +export const TRANSACTION_TIME_TO_FIRST_BYTE = + 'transaction.marks.agent.timeToFirstByte'; +export const TRANSACTION_DOM_INTERACTIVE = + 'transaction.marks.agent.domInteractive'; diff --git a/x-pack/plugins/apm/public/utils/formatters/__test__/formatters.test.ts b/x-pack/plugins/apm/common/utils/formatters.test.ts similarity index 96% rename from x-pack/plugins/apm/public/utils/formatters/__test__/formatters.test.ts rename to x-pack/plugins/apm/common/utils/formatters.test.ts index 66101baf3a746..ce317c5a6a827 100644 --- a/x-pack/plugins/apm/public/utils/formatters/__test__/formatters.test.ts +++ b/x-pack/plugins/apm/common/utils/formatters.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { asPercent } from '../formatters'; +import { asPercent } from './formatters'; describe('formatters', () => { describe('asPercent', () => { diff --git a/x-pack/plugins/apm/common/utils/formatters.ts b/x-pack/plugins/apm/common/utils/formatters.ts new file mode 100644 index 0000000000000..f7c609d949adf --- /dev/null +++ b/x-pack/plugins/apm/common/utils/formatters.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import numeral from '@elastic/numeral'; + +export function asPercent( + numerator: number, + denominator: number | undefined, + fallbackResult = '' +) { + if (!denominator || isNaN(numerator)) { + return fallbackResult; + } + + const decimal = numerator / denominator; + + // 33.2 => 33% + // 3.32 => 3.3% + // 0 => 0% + if (Math.abs(decimal) >= 0.1 || decimal === 0) { + return numeral(decimal).format('0%'); + } + + return numeral(decimal).format('0.0%'); +} diff --git a/x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature.disabled b/x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature.disabled index be1597c8340eb..727898773904e 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature.disabled +++ b/x-pack/plugins/apm/e2e/cypress/integration/rum_dashboard.feature.disabled @@ -12,20 +12,14 @@ Feature: RUM Dashboard | os | | location | - Scenario: Page load distribution percentiles + Scenario: Display RUM Data components When a user browses the APM UI application for RUM Data Then should display percentile for page load chart - - Scenario: Page load distribution chart tooltip - When a user browses the APM UI application for RUM Data - Then should display tooltip on hover - - Scenario: Page load distribution chart legends - When a user browses the APM UI application for RUM Data - Then should display chart legend + And should display tooltip on hover + And should display chart legend Scenario: Breakdown filter - Given a user click page load breakdown filter + Given a user clicks the page load breakdown filter When the user selected the breakdown Then breakdown series should appear in chart diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/client_metrics_helper.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/client_metrics_helper.ts new file mode 100644 index 0000000000000..1cc36059b8ff8 --- /dev/null +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/client_metrics_helper.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DEFAULT_TIMEOUT } from './rum_dashboard'; + +/** + * Verifies the behavior of the client metrics component + * @param metrics array of three elements + * @param checkTitleStatus if it's needed to check title elements + */ +export function verifyClientMetrics( + metrics: string[], + checkTitleStatus: boolean +) { + const clientMetricsSelector = '[data-cy=client-metrics] .euiStat__title'; + + // wait for all loading to finish + cy.get('kbnLoadingIndicator').should('not.be.visible'); + + if (checkTitleStatus) { + cy.get('.euiStat__title', { timeout: DEFAULT_TIMEOUT }).should( + 'be.visible' + ); + cy.get('.euiSelect-isLoading').should('not.be.visible'); + } + + cy.get('.euiStat__title-isLoading').should('not.be.visible'); + + cy.get(clientMetricsSelector).eq(0).should('have.text', metrics[0]); + + cy.get(clientMetricsSelector).eq(1).should('have.text', metrics[1]); + + cy.get(clientMetricsSelector).eq(2).should('have.text', metrics[2]); +} diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/page_load_dist.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/page_load_dist.ts index f319f7ef98667..d671bdc0078eb 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/page_load_dist.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/page_load_dist.ts @@ -9,7 +9,7 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; /** The default time in ms to wait for a Cypress command to complete */ export const DEFAULT_TIMEOUT = 60 * 1000; -Given(`a user click page load breakdown filter`, () => { +Given(`a user clicks the page load breakdown filter`, () => { // wait for all loading to finish cy.get('kbnLoadingIndicator').should('not.be.visible'); cy.get('.euiStat__title-isLoading').should('not.be.visible'); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/rum_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/rum_dashboard.ts index 8e010d5180f88..804974d8d437d 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/rum_dashboard.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/rum_dashboard.ts @@ -6,6 +6,7 @@ import { Given, Then } from 'cypress-cucumber-preprocessor/steps'; import { loginAndWaitForPage } from '../../../integration/helpers'; +import { verifyClientMetrics } from './client_metrics_helper'; /** The default time in ms to wait for a Cypress command to complete */ export const DEFAULT_TIMEOUT = 60 * 1000; @@ -21,19 +22,9 @@ Given(`a user browses the APM UI application for RUM Data`, () => { }); Then(`should have correct client metrics`, () => { - const clientMetrics = '[data-cy=client-metrics] .euiStat__title'; + const metrics = ['0.01 sec', '0.08 sec', '55 ']; - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiStat__title', { timeout: DEFAULT_TIMEOUT }).should('be.visible'); - cy.get('.euiSelect-isLoading').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); - - cy.get(clientMetrics).eq(2).should('have.text', '55 '); - - cy.get(clientMetrics).eq(1).should('have.text', '0.08 sec'); - - cy.get(clientMetrics).eq(0).should('have.text', '0.01 sec'); + verifyClientMetrics(metrics, true); }); Then(`should display percentile for page load chart`, () => { diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/service_name_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/service_name_filter.ts index b0694c902085a..68fc4d528543a 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/service_name_filter.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum/service_name_filter.ts @@ -6,6 +6,7 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'; import { DEFAULT_TIMEOUT } from '../apm'; +import { verifyClientMetrics } from './client_metrics_helper'; When('a user changes the selected service name', (filterName) => { // wait for all loading to finish @@ -16,15 +17,7 @@ When('a user changes the selected service name', (filterName) => { }); Then(`it displays relevant client metrics`, () => { - const clientMetrics = '[data-cy=client-metrics] .euiStat__title'; + const metrics = ['0.01 sec', '0.07 sec', '7 ']; - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); - - cy.get(clientMetrics).eq(2).should('have.text', '7 '); - - cy.get(clientMetrics).eq(1).should('have.text', '0.07 sec'); - - cy.get(clientMetrics).eq(0).should('have.text', '0.01 sec'); + verifyClientMetrics(metrics, false); }); diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx new file mode 100644 index 0000000000000..fc369b9cf672a --- /dev/null +++ b/x-pack/plugins/apm/public/application/application.test.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { act } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import { Observable } from 'rxjs'; +import { AppMountParameters, CoreStart, HttpSetup } from 'src/core/public'; +import { mockApmPluginContextValue } from '../context/ApmPluginContext/MockApmPluginContext'; +import { ApmPluginSetupDeps } from '../plugin'; +import { createCallApmApi } from '../services/rest/createCallApmApi'; +import { renderApp } from './'; +import { disableConsoleWarning } from '../utils/testHelpers'; + +describe('renderApp', () => { + let mockConsole: jest.SpyInstance; + + beforeAll(() => { + // The RUM agent logs an unnecessary message here. There's a couple open + // issues need to be fixed to get the ability to turn off all of the logging: + // + // * https://github.com/elastic/apm-agent-rum-js/issues/799 + // * https://github.com/elastic/apm-agent-rum-js/issues/861 + // + // for now, override `console.warn` to filter those messages out. + mockConsole = disableConsoleWarning('[Elastic APM]'); + }); + + afterAll(() => { + mockConsole.mockRestore(); + }); + + it('renders the app', () => { + const { core, config } = mockApmPluginContextValue; + const plugins = { + licensing: { license$: new Observable() }, + triggers_actions_ui: { actionTypeRegistry: {}, alertTypeRegistry: {} }, + usageCollection: { reportUiStats: () => {} }, + }; + const params = { + element: document.createElement('div'), + history: createMemoryHistory(), + }; + jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined); + createCallApmApi((core.http as unknown) as HttpSetup); + + jest + .spyOn(window.console, 'warn') + .mockImplementationOnce((message: string) => { + if (message.startsWith('[Elastic APM')) { + return; + } else { + console.warn(message); // eslint-disable-line no-console + } + }); + + let unmount: () => void; + + act(() => { + unmount = renderApp( + (core as unknown) as CoreStart, + (plugins as unknown) as ApmPluginSetupDeps, + (params as unknown) as AppMountParameters, + config + ); + }); + + expect(() => { + unmount(); + }).not.toThrowError(); + }); +}); diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index cf3fe2decfa44..d76ed5c2100b2 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -16,11 +16,11 @@ import { ApmPluginSetupDeps } from '../plugin'; import { KibanaContextProvider, useUiSetting$, + RedirectAppLinks, } from '../../../../../src/plugins/kibana_react/public'; import { px, units } from '../style/variables'; import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; -import { history, resetHistory } from '../utils/history'; import 'react-vis/dist/style.css'; import { RumHome } from '../components/app/RumDashboard/RumHome'; import { ConfigSchema } from '../index'; @@ -70,12 +70,12 @@ function CsmApp() { export function CsmAppRoot({ core, deps, - routerHistory, + history, config, }: { core: CoreStart; deps: ApmPluginSetupDeps; - routerHistory: typeof history; + history: AppMountParameters['history']; config: ConfigSchema; }) { const i18nCore = core.i18n; @@ -86,19 +86,21 @@ export function CsmAppRoot({ plugins, }; return ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); } @@ -109,19 +111,13 @@ export function CsmAppRoot({ export const renderApp = ( core: CoreStart, deps: ApmPluginSetupDeps, - { element }: AppMountParameters, + { element, history }: AppMountParameters, config: ConfigSchema ) => { createCallApmApi(core.http); - resetHistory(); ReactDOM.render( - , + , element ); return () => { diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 5e502f58e5f56..3f4f3116152c4 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -5,36 +5,36 @@ */ import { ApmRoute } from '@elastic/apm-rum-react'; +import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import React from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; -import styled, { ThemeProvider, DefaultTheme } from 'styled-components'; -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; -import { CoreStart, AppMountParameters } from '../../../../../src/core/public'; -import { ApmPluginSetupDeps } from '../plugin'; +import 'react-vis/dist/style.css'; +import styled, { DefaultTheme, ThemeProvider } from 'styled-components'; +import { ConfigSchema } from '../'; +import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; +import { + KibanaContextProvider, + RedirectAppLinks, + useUiSetting$, +} from '../../../../../src/plugins/kibana_react/public'; +import { AlertsContextProvider } from '../../../triggers_actions_ui/public'; +import { routes } from '../components/app/Main/route_config'; +import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; +import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; import { ApmPluginContext } from '../context/ApmPluginContext'; import { LicenseProvider } from '../context/LicenseContext'; import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; import { LocationProvider } from '../context/LocationContext'; import { MatchedRouteProvider } from '../context/MatchedRouteContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; -import { AlertsContextProvider } from '../../../triggers_actions_ui/public'; +import { ApmPluginSetupDeps } from '../plugin'; +import { createCallApmApi } from '../services/rest/createCallApmApi'; import { createStaticIndexPattern } from '../services/rest/index_pattern'; -import { - KibanaContextProvider, - useUiSetting$, -} from '../../../../../src/plugins/kibana_react/public'; -import { px, units } from '../style/variables'; -import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; -import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; -import { routes } from '../components/app/Main/route_config'; -import { history, resetHistory } from '../utils/history'; import { setHelpExtension } from '../setHelpExtension'; +import { px, units } from '../style/variables'; import { setReadonlyBadge } from '../updateBadge'; -import { createCallApmApi } from '../services/rest/createCallApmApi'; -import { ConfigSchema } from '..'; -import 'react-vis/dist/style.css'; const MainContainer = styled.div` padding: ${px(units.plus)}; @@ -68,12 +68,12 @@ function App() { export function ApmAppRoot({ core, deps, - routerHistory, + history, config, }: { core: CoreStart; deps: ApmPluginSetupDeps; - routerHistory: typeof history; + history: AppMountParameters['history']; config: ConfigSchema; }) { const i18nCore = core.i18n; @@ -84,36 +84,38 @@ export function ApmAppRoot({ plugins, }; return ( - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + ); } @@ -124,7 +126,7 @@ export function ApmAppRoot({ export const renderApp = ( core: CoreStart, deps: ApmPluginSetupDeps, - { element }: AppMountParameters, + { element, history }: AppMountParameters, config: ConfigSchema ) => { // render APM feedback link in global help menu @@ -133,8 +135,6 @@ export const renderApp = ( createCallApmApi(core.http); - resetHistory(); - // Automatically creates static index pattern and stores as saved object createStaticIndexPattern().catch((e) => { // eslint-disable-next-line no-console @@ -142,12 +142,7 @@ export const renderApp = ( }); ReactDOM.render( - , + , element ); return () => { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx new file mode 100644 index 0000000000000..5ad6fd547169d --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx @@ -0,0 +1,807 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { EuiThemeProvider } from '../../../../../../observability/public'; +import { Exception } from '../../../../../typings/es_schemas/raw/error_raw'; +import { ExceptionStacktrace } from './ExceptionStacktrace'; + +storiesOf('app/ErrorGroupDetails/DetailView/ExceptionStacktrace', module) + .addDecorator((storyFn) => { + return {storyFn()}; + }) + .add('JavaScript with some context', () => { + const exceptions: Exception[] = [ + { + code: '503', + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/elastic-apm-http-client/index.js', + abs_path: '/app/node_modules/elastic-apm-http-client/index.js', + line: { + number: 711, + context: + " const err = new Error('Unexpected APM Server response when polling config')", + }, + function: 'processConfigErrorResponse', + context: { + pre: ['', 'function processConfigErrorResponse (res, buf) {'], + post: ['', ' err.code = res.statusCode'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/elastic-apm-http-client/index.js', + abs_path: '/app/node_modules/elastic-apm-http-client/index.js', + line: { + number: 196, + context: + ' res.destroy(processConfigErrorResponse(res, buf))', + }, + function: '', + context: { + pre: [' }', ' } else {'], + post: [' }', ' })'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/fast-stream-to-buffer/index.js', + abs_path: '/app/node_modules/fast-stream-to-buffer/index.js', + line: { + number: 20, + context: ' cb(err, buffers[0], stream)', + }, + function: 'IncomingMessage.', + context: { + pre: [' break', ' case 1:'], + post: [' break', ' default:'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/once/once.js', + abs_path: '/app/node_modules/once/once.js', + line: { + number: 25, + context: ' return f.value = fn.apply(this, arguments)', + }, + function: 'f', + context: { + pre: [' if (f.called) return f.value', ' f.called = true'], + post: [' }', ' f.called = false'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'node_modules/end-of-stream/index.js', + abs_path: '/app/node_modules/end-of-stream/index.js', + line: { + number: 36, + context: '\t\tif (!writable) callback.call(stream);', + }, + function: 'onend', + context: { + pre: ['\tvar onend = function() {', '\t\treadable = false;'], + post: ['\t};', ''], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: 'events.js', + filename: 'events.js', + line: { + number: 327, + }, + function: 'emit', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: '_stream_readable.js', + abs_path: '_stream_readable.js', + line: { + number: 1220, + }, + function: 'endReadableNT', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'internal/process/task_queues.js', + abs_path: 'internal/process/task_queues.js', + line: { + number: 84, + }, + function: 'processTicksAndRejections', + }, + ], + module: 'elastic-apm-http-client', + handled: false, + attributes: { + response: + '\r\n503 Service Temporarily Unavailable\r\n\r\n

503 Service Temporarily Unavailable

\r\n
nginx/1.17.7
\r\n\r\n\r\n', + }, + type: 'Error', + message: 'Unexpected APM Server response when polling config', + }, + ]; + + return ( + + ); + }) + .add('Ruby with context and library frames', () => { + const exceptions: Exception[] = [ + { + stacktrace: [ + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_record/core.rb', + abs_path: + '/usr/local/bundle/gems/activerecord-5.2.4.1/lib/active_record/core.rb', + line: { + number: 177, + }, + function: 'find', + }, + { + library_frame: false, + exclude_from_grouping: false, + filename: 'api/orders_controller.rb', + abs_path: '/app/app/controllers/api/orders_controller.rb', + line: { + number: 23, + context: ' render json: Order.find(params[:id])\n', + }, + function: 'show', + context: { + pre: ['\n', ' def show\n'], + post: [' end\n', ' end\n'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal/basic_implicit_render.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/basic_implicit_render.rb', + line: { + number: 6, + }, + function: 'send_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/base.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/base.rb', + line: { + number: 194, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal/rendering.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/rendering.rb', + line: { + number: 30, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/callbacks.rb', + line: { + number: 42, + }, + function: 'block in process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/callbacks.rb', + line: { + number: 132, + }, + function: 'run_callbacks', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/callbacks.rb', + line: { + number: 41, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/rescue.rb', + filename: 'action_controller/metal/rescue.rb', + line: { + number: 22, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/instrumentation.rb', + filename: 'action_controller/metal/instrumentation.rb', + line: { + number: 34, + }, + function: 'block in process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/notifications.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/notifications.rb', + line: { + number: 168, + }, + function: 'block in instrument', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/notifications/instrumenter.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/notifications/instrumenter.rb', + line: { + number: 23, + }, + function: 'instrument', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/notifications.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/notifications.rb', + line: { + number: 168, + }, + function: 'instrument', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal/instrumentation.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/instrumentation.rb', + line: { + number: 32, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal/params_wrapper.rb', + filename: 'action_controller/metal/params_wrapper.rb', + line: { + number: 256, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_record/railties/controller_runtime.rb', + abs_path: + '/usr/local/bundle/gems/activerecord-5.2.4.1/lib/active_record/railties/controller_runtime.rb', + line: { + number: 24, + }, + function: 'process_action', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'abstract_controller/base.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/abstract_controller/base.rb', + line: { + number: 134, + }, + function: 'process', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_view/rendering.rb', + abs_path: + '/usr/local/bundle/gems/actionview-5.2.4.1/lib/action_view/rendering.rb', + line: { + number: 32, + }, + function: 'process', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_controller/metal.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal.rb', + line: { + number: 191, + }, + function: 'dispatch', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_controller/metal.rb', + filename: 'action_controller/metal.rb', + line: { + number: 252, + }, + function: 'dispatch', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'action_dispatch/routing/route_set.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/routing/route_set.rb', + line: { + number: 52, + }, + function: 'dispatch', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/routing/route_set.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/routing/route_set.rb', + line: { + number: 34, + }, + function: 'serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/journey/router.rb', + filename: 'action_dispatch/journey/router.rb', + line: { + number: 52, + }, + function: 'block in serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/journey/router.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/journey/router.rb', + line: { + number: 35, + }, + function: 'each', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/journey/router.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/journey/router.rb', + line: { + number: 35, + }, + function: 'serve', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/routing/route_set.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/routing/route_set.rb', + line: { + number: 840, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'rack/static.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/static.rb', + line: { + number: 161, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/tempfile_reaper.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb', + line: { + number: 15, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/etag.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/etag.rb', + line: { + number: 27, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/conditional_get.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/conditional_get.rb', + line: { + number: 27, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/head.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/head.rb', + line: { + number: 12, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/http/content_security_policy.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/http/content_security_policy.rb', + line: { + number: 18, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'rack/session/abstract/id.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/session/abstract/id.rb', + line: { + number: 266, + }, + function: 'context', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/session/abstract/id.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/session/abstract/id.rb', + line: { + number: 260, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/cookies.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/cookies.rb', + line: { + number: 670, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/callbacks.rb', + line: { + number: 28, + }, + function: 'block in call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/callbacks.rb', + line: { + number: 98, + }, + function: 'run_callbacks', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/callbacks.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/callbacks.rb', + line: { + number: 26, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/debug_exceptions.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/debug_exceptions.rb', + line: { + number: 61, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'action_dispatch/middleware/show_exceptions.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/show_exceptions.rb', + line: { + number: 33, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'lograge/rails_ext/rack/logger.rb', + abs_path: + '/usr/local/bundle/gems/lograge-0.11.2/lib/lograge/rails_ext/rack/logger.rb', + line: { + number: 15, + }, + function: 'call_app', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'rails/rack/logger.rb', + abs_path: + '/usr/local/bundle/gems/railties-5.2.4.1/lib/rails/rack/logger.rb', + line: { + number: 28, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/remote_ip.rb', + filename: 'action_dispatch/middleware/remote_ip.rb', + line: { + number: 81, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'request_store/middleware.rb', + abs_path: + '/usr/local/bundle/gems/request_store-1.5.0/lib/request_store/middleware.rb', + line: { + number: 19, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/request_id.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/request_id.rb', + line: { + number: 27, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/method_override.rb', + abs_path: + '/usr/local/bundle/gems/rack-2.2.3/lib/rack/method_override.rb', + line: { + number: 24, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/runtime.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/runtime.rb', + line: { + number: 22, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'active_support/cache/strategy/local_cache_middleware.rb', + abs_path: + '/usr/local/bundle/gems/activesupport-5.2.4.1/lib/active_support/cache/strategy/local_cache_middleware.rb', + line: { + number: 29, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/executor.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/executor.rb', + line: { + number: 14, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'action_dispatch/middleware/static.rb', + abs_path: + '/usr/local/bundle/gems/actionpack-5.2.4.1/lib/action_dispatch/middleware/static.rb', + line: { + number: 127, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rack/sendfile.rb', + abs_path: '/usr/local/bundle/gems/rack-2.2.3/lib/rack/sendfile.rb', + line: { + number: 110, + }, + function: 'call', + }, + { + library_frame: false, + exclude_from_grouping: false, + filename: 'opbeans_shuffle.rb', + abs_path: '/app/lib/opbeans_shuffle.rb', + line: { + number: 32, + context: ' @app.call(env)\n', + }, + function: 'call', + context: { + pre: [' end\n', ' else\n'], + post: [' end\n', ' rescue Timeout::Error\n'], + }, + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'elastic_apm/middleware.rb', + abs_path: + '/usr/local/bundle/gems/elastic-apm-3.8.0/lib/elastic_apm/middleware.rb', + line: { + number: 36, + }, + function: 'call', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'rails/engine.rb', + abs_path: + '/usr/local/bundle/gems/railties-5.2.4.1/lib/rails/engine.rb', + line: { + number: 524, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/configuration.rb', + abs_path: + '/usr/local/bundle/gems/puma-4.3.5/lib/puma/configuration.rb', + line: { + number: 228, + }, + function: 'call', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/server.rb', + abs_path: '/usr/local/bundle/gems/puma-4.3.5/lib/puma/server.rb', + line: { + number: 713, + }, + function: 'handle_request', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'puma/server.rb', + abs_path: '/usr/local/bundle/gems/puma-4.3.5/lib/puma/server.rb', + line: { + number: 472, + }, + function: 'process_client', + }, + { + library_frame: true, + exclude_from_grouping: false, + filename: 'puma/server.rb', + abs_path: '/usr/local/bundle/gems/puma-4.3.5/lib/puma/server.rb', + line: { + number: 328, + }, + function: 'block in run', + }, + { + exclude_from_grouping: false, + library_frame: true, + filename: 'puma/thread_pool.rb', + abs_path: + '/usr/local/bundle/gems/puma-4.3.5/lib/puma/thread_pool.rb', + line: { + number: 134, + }, + function: 'block in spawn_thread', + }, + ], + handled: false, + module: 'ActiveRecord', + message: "Couldn't find Order with 'id'=956", + type: 'ActiveRecord::RecordNotFound', + }, + ]; + + return ; + }); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 4e1af6e0dc239..5202ca13ed102 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -6,40 +6,40 @@ import { EuiButtonEmpty, + EuiIcon, EuiPanel, EuiSpacer, EuiTab, EuiTabs, EuiTitle, - EuiIcon, EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Location } from 'history'; +import { first } from 'lodash'; import React from 'react'; +import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; -import { first } from 'lodash'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ErrorGroupAPIResponse } from '../../../../../server/lib/errors/get_error_group'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { px, unit, units } from '../../../../style/variables'; +import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; import { DiscoverErrorLink } from '../../../shared/Links/DiscoverLinks/DiscoverErrorLink'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; -import { history } from '../../../../utils/history'; import { ErrorMetadata } from '../../../shared/MetadataTable/ErrorMetadata'; import { Stacktrace } from '../../../shared/Stacktrace'; +import { Summary } from '../../../shared/Summary'; +import { HttpInfoSummaryItem } from '../../../shared/Summary/HttpInfoSummaryItem'; +import { UserAgentSummaryItem } from '../../../shared/Summary/UserAgentSummaryItem'; +import { TimestampTooltip } from '../../../shared/TimestampTooltip'; import { ErrorTab, exceptionStacktraceTab, getTabs, logStacktraceTab, } from './ErrorTabs'; -import { Summary } from '../../../shared/Summary'; -import { TimestampTooltip } from '../../../shared/TimestampTooltip'; -import { HttpInfoSummaryItem } from '../../../shared/Summary/HttpInfoSummaryItem'; -import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; -import { UserAgentSummaryItem } from '../../../shared/Summary/UserAgentSummaryItem'; import { ExceptionStacktrace } from './ExceptionStacktrace'; const HeaderContainer = styled.div` @@ -71,6 +71,7 @@ function getCurrentTab( } export function DetailView({ errorGroup, urlParams, location }: Props) { + const history = useHistory(); const { transaction, error, occurrencesCount } = errorGroup; if (!error) { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx index a173f4068db6a..5798deaf19c9c 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx @@ -6,10 +6,11 @@ import { mount } from 'enzyme'; import React from 'react'; +import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockUrlParamsContextProvider } from '../../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; import { mockMoment, toJson } from '../../../../../utils/testHelpers'; import { ErrorGroupList } from '../index'; import props from './props.json'; -import { MockUrlParamsContextProvider } from '../../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { return { @@ -36,9 +37,11 @@ describe('ErrorGroupOverview -> List', () => { it('should render with data', () => { const wrapper = mount( - - - + + + + + ); expect(toJson(wrapper)).toMatchSnapshot(); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index 40522edc21b52..5183432b4ae0f 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -784,11 +784,11 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` >
a0ce2 @@ -829,11 +829,11 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -876,13 +876,13 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > List should render with data 1`] = ` > f3ac9 @@ -1063,11 +1063,11 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1110,13 +1110,13 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > List should render with data 1`] = ` > e9086 @@ -1297,11 +1297,11 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1344,13 +1344,13 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > List should render with data 1`] = ` > 8673d @@ -1531,11 +1531,11 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > @@ -1578,13 +1578,13 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > { 'rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0' ); const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual([ - { - text: 'APM', - href: - '#/?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }, - { - text: 'Services', - href: - '#/services?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }, - { - text: 'opbeans-node', - href: - '#/services/opbeans-node?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }, - { - text: 'Errors', - href: - '#/services/opbeans-node/errors?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', - }, - { text: 'myGroupId', href: undefined }, - ]); + expect(breadcrumbs).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + text: 'APM', + href: + '/basepath/app/apm/?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', + }), + expect.objectContaining({ + text: 'Services', + href: + '/basepath/app/apm/services?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', + }), + expect.objectContaining({ + text: 'opbeans-node', + href: + '/basepath/app/apm/services/opbeans-node?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', + }), + expect.objectContaining({ + text: 'Errors', + href: + '/basepath/app/apm/services/opbeans-node/errors?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0', + }), + expect.objectContaining({ text: 'myGroupId', href: undefined }), + ]) + ); + expect(changeTitle).toHaveBeenCalledWith([ 'myGroupId', 'Errors', @@ -95,12 +98,23 @@ describe('UpdateBreadcrumbs', () => { it('/services/:serviceName/errors', () => { mountBreadcrumb('/services/opbeans-node/errors'); const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual([ - { text: 'APM', href: '#/?kuery=myKuery' }, - { text: 'Services', href: '#/services?kuery=myKuery' }, - { text: 'opbeans-node', href: '#/services/opbeans-node?kuery=myKuery' }, - { text: 'Errors', href: undefined }, - ]); + expect(breadcrumbs).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + text: 'APM', + href: '/basepath/app/apm/?kuery=myKuery', + }), + expect.objectContaining({ + text: 'Services', + href: '/basepath/app/apm/services?kuery=myKuery', + }), + expect.objectContaining({ + text: 'opbeans-node', + href: '/basepath/app/apm/services/opbeans-node?kuery=myKuery', + }), + expect.objectContaining({ text: 'Errors', href: undefined }), + ]) + ); expect(changeTitle).toHaveBeenCalledWith([ 'Errors', 'opbeans-node', @@ -112,12 +126,24 @@ describe('UpdateBreadcrumbs', () => { it('/services/:serviceName/transactions', () => { mountBreadcrumb('/services/opbeans-node/transactions'); const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual([ - { text: 'APM', href: '#/?kuery=myKuery' }, - { text: 'Services', href: '#/services?kuery=myKuery' }, - { text: 'opbeans-node', href: '#/services/opbeans-node?kuery=myKuery' }, - { text: 'Transactions', href: undefined }, - ]); + expect(breadcrumbs).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + text: 'APM', + href: '/basepath/app/apm/?kuery=myKuery', + }), + expect.objectContaining({ + text: 'Services', + href: '/basepath/app/apm/services?kuery=myKuery', + }), + expect.objectContaining({ + text: 'opbeans-node', + href: '/basepath/app/apm/services/opbeans-node?kuery=myKuery', + }), + expect.objectContaining({ text: 'Transactions', href: undefined }), + ]) + ); + expect(changeTitle).toHaveBeenCalledWith([ 'Transactions', 'opbeans-node', @@ -132,16 +158,33 @@ describe('UpdateBreadcrumbs', () => { 'transactionName=my-transaction-name' ); const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual([ - { text: 'APM', href: '#/?kuery=myKuery' }, - { text: 'Services', href: '#/services?kuery=myKuery' }, - { text: 'opbeans-node', href: '#/services/opbeans-node?kuery=myKuery' }, - { - text: 'Transactions', - href: '#/services/opbeans-node/transactions?kuery=myKuery', - }, - { text: 'my-transaction-name', href: undefined }, - ]); + + expect(breadcrumbs).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + text: 'APM', + href: '/basepath/app/apm/?kuery=myKuery', + }), + expect.objectContaining({ + text: 'Services', + href: '/basepath/app/apm/services?kuery=myKuery', + }), + expect.objectContaining({ + text: 'opbeans-node', + href: '/basepath/app/apm/services/opbeans-node?kuery=myKuery', + }), + expect.objectContaining({ + text: 'Transactions', + href: + '/basepath/app/apm/services/opbeans-node/transactions?kuery=myKuery', + }), + expect.objectContaining({ + text: 'my-transaction-name', + href: undefined, + }), + ]) + ); + expect(changeTitle).toHaveBeenCalledWith([ 'my-transaction-name', 'Transactions', diff --git a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx b/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx index e7657c63f41bb..5bf5cea587f93 100644 --- a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx @@ -5,20 +5,20 @@ */ import { Location } from 'history'; -import React from 'react'; -import { AppMountContext } from 'src/core/public'; +import React, { MouseEvent } from 'react'; +import { CoreStart } from 'src/core/public'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { Breadcrumb, - ProvideBreadcrumbs, BreadcrumbRoute, + ProvideBreadcrumbs, } from './ProvideBreadcrumbs'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; interface Props { location: Location; breadcrumbs: Breadcrumb[]; - core: AppMountContext['core']; + core: CoreStart; } function getTitleFromBreadCrumbs(breadcrumbs: Breadcrumb[]) { @@ -27,15 +27,24 @@ function getTitleFromBreadCrumbs(breadcrumbs: Breadcrumb[]) { class UpdateBreadcrumbsComponent extends React.Component { public updateHeaderBreadcrumbs() { + const { basePath } = this.props.core.http; const breadcrumbs = this.props.breadcrumbs.map( ({ value, match }, index) => { + const { search } = this.props.location; const isLastBreadcrumbItem = index === this.props.breadcrumbs.length - 1; + const href = isLastBreadcrumbItem + ? undefined // makes the breadcrumb item not clickable + : getAPMHref({ basePath, path: match.url, search }); return { text: value, - href: isLastBreadcrumbItem - ? undefined // makes the breadcrumb item not clickable - : getAPMHref(match.url, this.props.location.search), + href, + onClick: (event: MouseEvent) => { + if (href) { + event.preventDefault(); + this.props.core.application.navigateToUrl(href); + } + }, }; } ); diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index 8caddc94b6907..56026dcf477ec 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -38,14 +38,28 @@ interface RouteParams { } export const renderAsRedirectTo = (to: string) => { - return ({ location }: RouteComponentProps) => ( - - ); + return ({ location }: RouteComponentProps) => { + let resolvedUrl: URL | undefined; + + // Redirect root URLs with a hash to support backward compatibility with URLs + // from before we switched to the non-hash platform history. + if (location.pathname === '' && location.hash.length > 0) { + // We just want the search and pathname so the host doesn't matter + resolvedUrl = new URL(location.hash.slice(1), 'http://localhost'); + to = resolvedUrl.pathname; + } + + return ( + + ); + }; }; export const routes: BreadcrumbRoute[] = [ diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx new file mode 100644 index 0000000000000..ad12afe35fa20 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { routes } from './'; + +describe('routes', () => { + describe('/', () => { + const route = routes.find((r) => r.path === '/'); + + describe('with no hash path', () => { + it('redirects to /services', () => { + const location = { hash: '', pathname: '/', search: '' }; + expect( + (route as any).render({ location } as any).props.to.pathname + ).toEqual('/services'); + }); + }); + + describe('with a hash path', () => { + it('redirects to the hash path', () => { + const location = { + hash: + '#/services/opbeans-python/transactions/view?rangeFrom=now-24h&rangeTo=now&refreshInterval=10000&refreshPaused=false&traceId=d919c89dc7ca48d84b9dde1fef01d1f8&transactionId=1b542853d787ba7b&transactionName=GET%20opbeans.views.product_customers&transactionType=request&flyoutDetailTab=&waterfallItemId=1b542853d787ba7b', + pathname: '', + search: '', + }; + + expect(((route as any).render({ location }) as any).props.to).toEqual({ + hash: '', + pathname: '/services/opbeans-python/transactions/view', + search: + '?rangeFrom=now-24h&rangeTo=now&refreshInterval=10000&refreshPaused=false&traceId=d919c89dc7ca48d84b9dde1fef01d1f8&transactionId=1b542853d787ba7b&transactionName=GET%20opbeans.views.product_customers&transactionType=request&flyoutDetailTab=&waterfallItemId=1b542853d787ba7b', + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx index 14c912d0bd519..d99dc4d5cd37a 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx @@ -5,13 +5,14 @@ */ import React from 'react'; +import { useHistory } from 'react-router-dom'; import { useFetcher } from '../../../../../hooks/useFetcher'; -import { history } from '../../../../../utils/history'; +import { toQuery } from '../../../../shared/Links/url_helpers'; import { Settings } from '../../../Settings'; import { AgentConfigurationCreateEdit } from '../../../Settings/AgentConfigurations/AgentConfigurationCreateEdit'; -import { toQuery } from '../../../../shared/Links/url_helpers'; export function EditAgentConfigurationRouteHandler() { + const history = useHistory(); const { search } = history.location; // typescript complains because `pageStop` does not exist in `APMQueryParams` @@ -40,6 +41,7 @@ export function EditAgentConfigurationRouteHandler() { } export function CreateAgentConfigurationRouteHandler() { + const history = useHistory(); const { search } = history.location; // Ignoring here because we specifically DO NOT want to add the query params to the global route handler diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx index b2b5e66d06ac6..33573052dbcbb 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx @@ -35,7 +35,7 @@ import { BreakdownSeries } from '../PageLoadDistribution/BreakdownSeries'; interface PageLoadData { pageLoadDistribution: Array<{ x: number; y: number }>; - percentiles: Record | undefined; + percentiles: Record | undefined; minDuration: number; maxDuration: number; } diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index 9211504a2dffe..c76be19edfe47 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -4,33 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import numeral from '@elastic/numeral'; import { Axis, BarSeries, BrushEndListener, Chart, + DARK_THEME, + LIGHT_THEME, niceTimeFormatByDay, ScaleType, SeriesNameFn, Settings, timeFormatter, } from '@elastic/charts'; -import { DARK_THEME, LIGHT_THEME } from '@elastic/charts'; - +import { Position } from '@elastic/charts/dist/utils/commons'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT, } from '@elastic/eui/dist/eui_charts_theme'; +import numeral from '@elastic/numeral'; import moment from 'moment'; -import { Position } from '@elastic/charts/dist/utils/commons'; -import { I18LABELS } from '../translations'; -import { history } from '../../../../utils/history'; -import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; -import { ChartWrapper } from '../ChartWrapper'; +import React from 'react'; +import { useHistory } from 'react-router-dom'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; +import { ChartWrapper } from '../ChartWrapper'; +import { I18LABELS } from '../translations'; interface Props { data?: Array>; @@ -38,6 +38,7 @@ interface Props { } export function PageViewsChart({ data, loading }: Props) { + const history = useHistory(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx index 407ec42f03ff5..7e81dc011bdb5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx @@ -16,11 +16,11 @@ import styled from 'styled-components'; import { EuiToolTip } from '@elastic/eui'; interface Props { - percentiles?: Record; + percentiles?: Record; } function generateAnnotationData( - values?: Record + values?: Record ): LineAnnotationDatum[] { return Object.entries(values ?? {}).map((value) => ({ dataValue: value[1], diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx new file mode 100644 index 0000000000000..1187b71dff825 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { render } from '@testing-library/react'; +import cytoscape from 'cytoscape'; +import React, { ReactNode } from 'react'; +import { ThemeContext } from 'styled-components'; +import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { Controls } from './Controls'; +import { CytoscapeContext } from './Cytoscape'; + +const cy = cytoscape({ + elements: [{ classes: 'primary', data: { id: 'test node' } }], +}); + +function Wrapper({ children }: { children?: ReactNode }) { + return ( + + + + {children} + + + + ); +} + +describe('Controls', () => { + describe('with a primary node', () => { + it('links to the full map', async () => { + const result = render(, { wrapper: Wrapper }); + const { findByTestId } = result; + + const button = await findByTestId('viewFullMapButton'); + + expect(button.getAttribute('href')).toEqual( + '/basepath/app/apm/service-map' + ); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx index bcc87cbf35819..c8f586240471f 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -4,16 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect, useState } from 'react'; import { EuiButtonIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import React, { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { CytoscapeContext } from './Cytoscape'; -import { getAnimationOptions, getNodeHeight } from './cytoscapeOptions'; -import { getAPMHref } from '../../shared/Links/apm/APMLink'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useTheme } from '../../../hooks/useTheme'; import { useUrlParams } from '../../../hooks/useUrlParams'; +import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { APMQueryParams } from '../../shared/Links/url_helpers'; -import { useTheme } from '../../../hooks/useTheme'; +import { CytoscapeContext } from './Cytoscape'; +import { getAnimationOptions, getNodeHeight } from './cytoscapeOptions'; const ControlsContainer = styled('div')` left: ${({ theme }) => theme.eui.gutterTypes.gutterMedium}; @@ -96,6 +97,8 @@ function useDebugDownloadUrl(cy?: cytoscape.Core) { } export function Controls() { + const { core } = useApmPluginContext(); + const { basePath } = core.http; const theme = useTheme(); const cy = useContext(CytoscapeContext); const { urlParams } = useUrlParams(); @@ -103,6 +106,12 @@ export function Controls() { const [zoom, setZoom] = useState((cy && cy.zoom()) || 1); const duration = parseInt(theme.eui.euiAnimSpeedFast, 10); const downloadUrl = useDebugDownloadUrl(cy); + const viewFullMapUrl = getAPMHref({ + basePath, + path: '/service-map', + search: currentSearch, + query: urlParams as APMQueryParams, + }); // Handle zoom events useEffect(() => { @@ -209,11 +218,8 @@ export function Controls() { + {formatter(value).formatted} +
+ ); +} + +describe('useFormatter', () => { + const timeSeries = ([ + { + data: [ + { x: 1, y: toMicroseconds(11, 'minutes') }, + { x: 2, y: toMicroseconds(1, 'minutes') }, + { x: 3, y: toMicroseconds(60, 'seconds') }, + ], + }, + { + data: [ + { x: 1, y: toMicroseconds(120, 'seconds') }, + { x: 2, y: toMicroseconds(1, 'minutes') }, + { x: 3, y: toMicroseconds(60, 'seconds') }, + ], + }, + { + data: [ + { x: 1, y: toMicroseconds(60, 'seconds') }, + { x: 2, y: toMicroseconds(5, 'minutes') }, + { x: 3, y: toMicroseconds(100, 'seconds') }, + ], + }, + ] as unknown) as TimeSeries[]; + it('returns new formatter when disabled series state changes', () => { + const { getByText } = render( + + ); + expect(getByText('2.0 min')).toBeInTheDocument(); + act(() => { + fireEvent.click(getByText('disable series')); + }); + expect(getByText('120 s')).toBeInTheDocument(); + }); + it('falls back to the first formatter when disabled series is empty', () => { + const { getByText } = render( + + ); + expect(getByText('2.0 min')).toBeInTheDocument(); + act(() => { + fireEvent.click(getByText('disable series')); + }); + expect(getByText('2.0 min')).toBeInTheDocument(); + // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); + // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); + // setDisabledSeriesState([true, true, false]); + // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); + }); + it('falls back to the first formatter when disabled series is all true', () => { + const { getByText } = render( + + ); + expect(getByText('2.0 min')).toBeInTheDocument(); + act(() => { + fireEvent.click(getByText('disable series')); + }); + expect(getByText('2.0 min')).toBeInTheDocument(); + // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); + // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); + // setDisabledSeriesState([true, true, false]); + // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts new file mode 100644 index 0000000000000..8cd8929c89960 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState, Dispatch, SetStateAction } from 'react'; +import { isEmpty } from 'lodash'; +import { + getDurationFormatter, + TimeFormatter, +} from '../../../../utils/formatters'; +import { TimeSeries } from '../../../../../typings/timeseries'; +import { getMaxY } from './helper'; + +export const useFormatter = ( + series: TimeSeries[] +): { + formatter: TimeFormatter; + setDisabledSeriesState: Dispatch>; +} => { + const [disabledSeriesState, setDisabledSeriesState] = useState([]); + const visibleSeries = series.filter( + (serie, index) => disabledSeriesState[index] !== true + ); + const maxY = getMaxY(isEmpty(visibleSeries) ? series : visibleSeries); + const formatter = getDurationFormatter(maxY); + + return { formatter, setDisabledSeriesState }; +}; diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx index 8c38cdcda958d..8334efffbd511 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx +++ b/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { ReactNode } from 'react'; +import { Observable, of } from 'rxjs'; import { ApmPluginContext, ApmPluginContextValue } from '.'; -import { createCallApmApi } from '../../services/rest/createCallApmApi'; import { ConfigSchema } from '../..'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; +import { createCallApmApi } from '../../services/rest/createCallApmApi'; const uiSettings: Record = { [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [ @@ -33,8 +34,17 @@ const uiSettings: Record = { }; const mockCore = { + application: { + capabilities: { + apm: {}, + }, + currentAppId$: new Observable(), + }, chrome: { + docTitle: { change: () => {} }, setBreadcrumbs: () => {}, + setHelpExtension: () => {}, + setBadge: () => {}, }, docLinks: { DOC_LINK_VERSION: '0', @@ -45,6 +55,9 @@ const mockCore = { prepend: (path: string) => `/basepath${path}`, }, }, + i18n: { + Context: ({ children }: { children: ReactNode }) => children, + }, notifications: { toasts: { addWarning: () => {}, @@ -53,6 +66,7 @@ const mockCore = { }, uiSettings: { get: (key: string) => uiSettings[key], + get$: (key: string) => of(mockCore.uiSettings.get(key)), }, }; diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx b/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx index 37304d292540d..39d961f6a8164 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx +++ b/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx @@ -4,16 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CoreStart } from 'kibana/public'; import { createContext } from 'react'; -import { AppMountContext } from 'kibana/public'; -import { ConfigSchema } from '../..'; +import { ConfigSchema } from '../../'; import { ApmPluginSetupDeps } from '../../plugin'; -export type AppMountContextBasePath = AppMountContext['core']['http']['basePath']; - export interface ApmPluginContextValue { config: ConfigSchema; - core: AppMountContext['core']; + core: CoreStart; plugins: ApmPluginSetupDeps; } diff --git a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx b/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx index f93b69a877057..801c1d7e53f2e 100644 --- a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx +++ b/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx @@ -5,10 +5,10 @@ */ import React, { ReactNode, useMemo, useState } from 'react'; -import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; -import { history } from '../utils/history'; -import { useUrlParams } from '../hooks/useUrlParams'; +import { useHistory } from 'react-router-dom'; +import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; import { useFetcher } from '../hooks/useFetcher'; +import { useUrlParams } from '../hooks/useUrlParams'; const ChartsSyncContext = React.createContext<{ hoverX: number | null; @@ -18,6 +18,7 @@ const ChartsSyncContext = React.createContext<{ } | null>(null); function ChartsSyncContextProvider({ children }: { children: ReactNode }) { + const history = useHistory(); const [time, setTime] = useState(null); const { urlParams, uiFilters } = useUrlParams(); @@ -75,7 +76,7 @@ function ChartsSyncContextProvider({ children }: { children: ReactNode }) { }; return { ...hoverXHandlers }; - }, [time, data.annotations]); + }, [history, time, data.annotations]); return ; } diff --git a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts index 45ede7e7f2607..0ed26fe089487 100644 --- a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts +++ b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts @@ -5,21 +5,21 @@ */ import { omit } from 'lodash'; -import { useFetcher } from './useFetcher'; +import { useHistory } from 'react-router-dom'; +import { Projection } from '../../common/projections'; +import { pickKeys } from '../../common/utils/pick_keys'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { LocalUIFiltersAPIResponse } from '../../server/lib/ui_filters/local_ui_filters'; -import { useUrlParams } from './useUrlParams'; import { LocalUIFilterName, localUIFilters, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../server/lib/ui_filters/local_ui_filters/config'; -import { history } from '../utils/history'; -import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; +import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; import { removeUndefinedProps } from '../context/UrlParamsContext/helpers'; -import { Projection } from '../../common/projections'; -import { pickKeys } from '../../common/utils/pick_keys'; import { useCallApi } from './useCallApi'; +import { useFetcher } from './useFetcher'; +import { useUrlParams } from './useUrlParams'; const getInitialData = ( filterNames: LocalUIFilterName[] @@ -39,6 +39,7 @@ export function useLocalUIFilters({ filterNames: LocalUIFilterName[]; params?: Record; }) { + const history = useHistory(); const { uiFilters, urlParams } = useUrlParams(); const callApi = useCallApi(); diff --git a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts index 2449c13f29435..9db78fde2d8c8 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts @@ -13,7 +13,7 @@ export function useTransactionBreakdown() { uiFilters, } = useUrlParams(); - const { data = { kpis: [], timeseries: [] }, error, status } = useFetcher( + const { data = { timeseries: [] }, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { return callApmApi({ diff --git a/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts b/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts index c4d59beb4b7a2..ca8a4919dd216 100644 --- a/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts +++ b/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts @@ -20,9 +20,12 @@ describe('duration formatters', () => { '10,000 ms' ); expect(asDuration(toMicroseconds(20, 'seconds'))).toEqual('20 s'); - expect(asDuration(toMicroseconds(10, 'minutes'))).toEqual('10 min'); + expect(asDuration(toMicroseconds(10, 'minutes'))).toEqual('600 s'); + expect(asDuration(toMicroseconds(11, 'minutes'))).toEqual('11 min'); expect(asDuration(toMicroseconds(1, 'hours'))).toEqual('60 min'); - expect(asDuration(toMicroseconds(1.5, 'hours'))).toEqual('1.5 h'); + expect(asDuration(toMicroseconds(1.5, 'hours'))).toEqual('90 min'); + expect(asDuration(toMicroseconds(10, 'hours'))).toEqual('600 min'); + expect(asDuration(toMicroseconds(11, 'hours'))).toEqual('11 h'); }); it('falls back to default value', () => { diff --git a/x-pack/plugins/apm/public/utils/formatters/duration.ts b/x-pack/plugins/apm/public/utils/formatters/duration.ts index 64a9e3b952b98..8381b0afb5f07 100644 --- a/x-pack/plugins/apm/public/utils/formatters/duration.ts +++ b/x-pack/plugins/apm/public/utils/formatters/duration.ts @@ -127,10 +127,10 @@ export const toMicroseconds = (value: number, timeUnit: TimeUnit) => moment.duration(value, timeUnit).asMilliseconds() * 1000; function getDurationUnitKey(max: number): DurationTimeUnit { - if (max > toMicroseconds(1, 'hours')) { + if (max > toMicroseconds(10, 'hours')) { return 'hours'; } - if (max > toMicroseconds(1, 'minutes')) { + if (max > toMicroseconds(10, 'minutes')) { return 'minutes'; } if (max > toMicroseconds(10, 'seconds')) { diff --git a/x-pack/plugins/apm/public/utils/formatters/formatters.ts b/x-pack/plugins/apm/public/utils/formatters/formatters.ts index 649f11063b149..6249ce53b6779 100644 --- a/x-pack/plugins/apm/public/utils/formatters/formatters.ts +++ b/x-pack/plugins/apm/public/utils/formatters/formatters.ts @@ -23,24 +23,3 @@ export function tpmUnit(type?: string) { defaultMessage: 'tpm', }); } - -export function asPercent( - numerator: number, - denominator: number | undefined, - fallbackResult = '' -) { - if (!denominator || isNaN(numerator)) { - return fallbackResult; - } - - const decimal = numerator / denominator; - - // 33.2 => 33% - // 3.32 => 3.3% - // 0 => 0% - if (Math.abs(decimal) >= 0.1 || decimal === 0) { - return numeral(decimal).format('0%'); - } - - return numeral(decimal).format('0.0%'); -} diff --git a/x-pack/plugins/apm/public/utils/history.ts b/x-pack/plugins/apm/public/utils/history.ts deleted file mode 100644 index bd2203fe92066..0000000000000 --- a/x-pack/plugins/apm/public/utils/history.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createHashHistory } from 'history'; - -// Make history singleton available across APM project -// TODO: Explore using React context or hook instead? -let history = createHashHistory(); - -export const resetHistory = () => { - history = createHashHistory(); -}; - -export { history }; diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index a750a9ea7af67..037da01c74464 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -26,6 +26,20 @@ import { } from '../../typings/elasticsearch'; import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; +const originalConsoleWarn = console.warn; // eslint-disable-line no-console +/** + * A dependency we're using is using deprecated react methods. Override the + * console to hide the warnings. These should go away when we switch to + * Elastic Charts + */ +export function disableConsoleWarning(messageToDisable: string) { + return jest.spyOn(console, 'warn').mockImplementation((message) => { + if (!message.startsWith(messageToDisable)) { + originalConsoleWarn(message); + } + }); +} + export function toJson(wrapper: ReactWrapper) { return enzymeToJson(wrapper, { noKey: true, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts index 3abc89c470b21..e7eb7b8de65e3 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts @@ -73,10 +73,9 @@ export function registerTransactionDurationAnomalyAlertType({ return; } const alertParams = params as TypeOf; - const mlClient = services.getLegacyScopedClusterClient(ml.mlClient); - const request = { params: 'DummyKibanaRequest' } as KibanaRequest; - const { mlAnomalySearch } = ml.mlSystemProvider(mlClient, request); - const anomalyDetectors = ml.anomalyDetectorsProvider(mlClient, request); + const request = {} as KibanaRequest; + const { mlAnomalySearch } = ml.mlSystemProvider(request); + const anomalyDetectors = ml.anomalyDetectorsProvider(request); const mlJobIds = await getMLJobIds( anomalyDetectors, diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 29aaa98169fa5..8060bf10da99c 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts @@ -37,6 +37,9 @@ export async function getAllEnvironments({ ], }, body: { + // use timeout + min_doc_count to return as early as possible + // if filter is not defined to prevent timeouts + ...(!serviceName ? { timeout: '1ms' } : {}), size: 0, query: { bool: { @@ -48,6 +51,7 @@ export async function getAllEnvironments({ terms: { field: SERVICE_ENVIRONMENT, size: 100, + ...(!serviceName ? { min_doc_count: 0 } : {}), missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined, }, }, @@ -56,6 +60,7 @@ export async function getAllEnvironments({ }; const resp = await apmEventClient.search(params); + const environments = resp.aggregations?.environments.buckets.map( (bucket) => bucket.key as string diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts index c475640595227..9f7aaafbefb8c 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts @@ -34,7 +34,7 @@ export async function callClientWithDebug({ let res: any; let esError = null; try { - res = apiCaller(operationName, params); + res = await apiCaller(operationName, params); } catch (e) { // catch error and throw after outputting debug info esError = e; diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index a242a0adb6d4c..6b69e57389dff 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -122,13 +122,10 @@ function getMlSetup( if (!ml) { return; } - const mlClient = ml.mlClient.asScoped(request); - const mlSystem = ml.mlSystemProvider(mlClient, request); + return { - mlClient, - mlSystem, - modules: ml.modulesProvider(mlClient, request, savedObjectsClient), - anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request), - mlAnomalySearch: mlSystem.mlAnomalySearch, + mlSystem: ml.mlSystemProvider(request), + anomalyDetectors: ml.anomalyDetectorsProvider(request), + modules: ml.modulesProvider(request, savedObjectsClient), }; } diff --git a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap index c5264373ea495..22b8c226e9026 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap @@ -10,15 +10,25 @@ Object { "body": Object { "aggs": Object { "backEnd": Object { - "avg": Object { + "percentiles": Object { "field": "transaction.marks.agent.timeToFirstByte", - "missing": 0, + "hdr": Object { + "number_of_significant_value_digits": 3, + }, + "percents": Array [ + 50, + ], }, }, "domInteractive": Object { - "avg": Object { + "percentiles": Object { "field": "transaction.marks.agent.domInteractive", - "missing": 0, + "hdr": Object { + "number_of_significant_value_digits": 3, + }, + "percents": Array [ + 50, + ], }, }, "pageViews": Object { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts index 194c136e2b3d0..e0dec183f06d6 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts @@ -11,6 +11,10 @@ import { SetupTimeRange, SetupUIFilters, } from '../helpers/setup_request'; +import { + TRANSACTION_DOM_INTERACTIVE, + TRANSACTION_TIME_TO_FIRST_BYTE, +} from '../../../common/elasticsearch_fieldnames'; export async function getClientMetrics({ setup, @@ -30,15 +34,21 @@ export async function getClientMetrics({ aggs: { pageViews: { value_count: { field: 'transaction.type' } }, backEnd: { - avg: { - field: 'transaction.marks.agent.timeToFirstByte', - missing: 0, + percentiles: { + field: TRANSACTION_TIME_TO_FIRST_BYTE, + percents: [50], + hdr: { + number_of_significant_value_digits: 3, + }, }, }, domInteractive: { - avg: { - field: 'transaction.marks.agent.domInteractive', - missing: 0, + percentiles: { + field: TRANSACTION_DOM_INTERACTIVE, + percents: [50], + hdr: { + number_of_significant_value_digits: 3, + }, }, }, }, @@ -53,9 +63,11 @@ export async function getClientMetrics({ // Divide by 1000 to convert ms into seconds return { pageViews, - backEnd: { value: (backEnd.value || 0) / 1000 }, + backEnd: { value: (backEnd.values['50.0'] || 0) / 1000 }, frontEnd: { - value: ((domInteractive.value || 0) - (backEnd.value || 0)) / 1000, + value: + ((domInteractive.values['50.0'] || 0) - (backEnd.values['50.0'] || 0)) / + 1000, }, }; } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap index 2b465a0f87475..c01e5c87eeea2 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap @@ -127,11 +127,13 @@ Object { "services": Object { "terms": Object { "field": "service.name", + "min_doc_count": 0, "size": 50, }, }, }, "size": 0, + "timeout": "1ms", }, } `; diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts index 8b6c1d82beab0..91bdfeef003f1 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts @@ -25,12 +25,14 @@ export async function getServiceNames({ setup }: { setup: Setup }) { ], }, body: { + timeout: '1ms', size: 0, aggs: { services: { terms: { field: SERVICE_NAME, size: 50, + min_doc_count: 0, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index 2a1a581c79574..d0ba31f42c536 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -65,7 +65,7 @@ function getItemsWithRelativeImpact( key: string | Record; avg?: number | null; count?: number | null; - p95?: number; + p95?: number | null; sample?: Transaction; }> ) { @@ -188,7 +188,7 @@ export interface TransactionGroup { key: Record | string; averageResponseTime: number | null | undefined; transactionsPerMinute: number; - p95: number | undefined; + p95: number | null | undefined; impact: number; sample: Transaction; } diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts index 731f75226cbe4..e943214b0b517 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts @@ -50,40 +50,9 @@ describe('getTransactionBreakdown', () => { setup: getMockSetup(noDataResponse), }); - expect(response.kpis.length).toBe(0); - expect(Object.keys(response.timeseries).length).toBe(0); }); - it('returns transaction breakdowns grouped by type and subtype', async () => { - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(dataResponse), - }); - - expect(response.kpis.length).toBe(4); - - expect(response.kpis.map((kpi) => kpi.name)).toEqual([ - 'app', - 'dispatcher-servlet', - 'http', - 'postgresql', - ]); - - expect(response.kpis[0]).toEqual({ - name: 'app', - color: '#54b399', - percentage: 0.5408550899466306, - }); - - expect(response.kpis[3]).toEqual({ - name: 'postgresql', - color: '#9170b8', - percentage: 0.047366859295002, - }); - }); - it('returns a timeseries grouped by type and subtype', async () => { const response = await getTransactionBreakdown({ serviceName: 'myServiceName', @@ -98,7 +67,7 @@ describe('getTransactionBreakdown', () => { const appTimeseries = timeseries[0]; expect(appTimeseries.title).toBe('app'); expect(appTimeseries.type).toBe('areaStacked'); - expect(appTimeseries.hideLegend).toBe(true); + expect(appTimeseries.hideLegend).toBe(false); // empty buckets should result in null values for visible types expect(appTimeseries.data.length).toBe(276); diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts index fbdddea32deb4..9730ddbbf38d7 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -5,6 +5,7 @@ */ import { flatten, orderBy, last } from 'lodash'; +import { asPercent } from '../../../../common/utils/formatters'; import { ProcessorEvent } from '../../../../common/processor_event'; import { SERVICE_NAME, @@ -149,9 +150,16 @@ export async function getTransactionBreakdown({ ) : []; - const kpis = orderBy(visibleKpis, 'name').map((kpi, index) => { - return { + const kpis = orderBy( + visibleKpis.map((kpi) => ({ ...kpi, + lowerCaseName: kpi.name.toLowerCase(), + })), + 'lowerCaseName' + ).map((kpi, index) => { + const { lowerCaseName, ...rest } = kpi; + return { + ...rest, color: getVizColorForIndex(index), }; }); @@ -213,11 +221,9 @@ export async function getTransactionBreakdown({ color: kpi.color, type: 'areaStacked', data: timeseriesPerSubtype[kpi.name], - hideLegend: true, + hideLegend: false, + legendValue: asPercent(kpi.percentage, 1), })); - return { - kpis, - timeseries, - }; + return { timeseries }; } diff --git a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts index d25ec8709e3be..6a2a0e81e81cd 100644 --- a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts +++ b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts @@ -223,7 +223,7 @@ interface AggregationResponsePart< value: number; }; percentiles: { - values: Record; + values: Record; }; extended_stats: { count: number; diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts index 8e49d02beb908..b8eb79aabdcc5 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/error_raw.ts @@ -12,7 +12,7 @@ import { Kubernetes } from './fields/kubernetes'; import { Page } from './fields/page'; import { Process } from './fields/process'; import { Service } from './fields/service'; -import { IStackframe } from './fields/stackframe'; +import { Stackframe } from './fields/stackframe'; import { Url } from './fields/url'; import { User } from './fields/user'; import { Observer } from './fields/observer'; @@ -23,16 +23,20 @@ interface Processor { } export interface Exception { + attributes?: { + response?: string; + }; + code?: string; message?: string; // either message or type are given type?: string; module?: string; handled?: boolean; - stacktrace?: IStackframe[]; + stacktrace?: Stackframe[]; } interface Log { message: string; - stacktrace?: IStackframe[]; + stacktrace?: Stackframe[]; } export interface ErrorRaw extends APMBaseDoc { diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts b/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts index 05b0eb88da40b..901b02814371a 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/fields/stackframe.ts @@ -4,27 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ -type IStackframeBase = { - function?: string; - library_frame?: boolean; - exclude_from_grouping?: boolean; +interface Line { + column?: number; + number: number; +} + +interface Sourcemap { + error?: string; + updated?: boolean; +} + +interface StackframeBase { + abs_path?: string; + classname?: string; context?: { post?: string[]; pre?: string[]; }; + exclude_from_grouping?: boolean; + filename?: string; + function?: string; + module?: string; + library_frame?: boolean; + line?: Line; + sourcemap?: Sourcemap; vars?: { [key: string]: unknown; }; - line?: { - number: number; - }; -} & ({ classname: string } | { filename: string }); +} -export type IStackframeWithLineContext = IStackframeBase & { - line: { - number: number; +export type StackframeWithLineContext = StackframeBase & { + line: Line & { context: string; }; }; -export type IStackframe = IStackframeBase | IStackframeWithLineContext; +export type Stackframe = StackframeBase | StackframeWithLineContext; diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts b/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts index f6c4fce76f134..5c2e391059783 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts @@ -5,7 +5,7 @@ */ import { APMBaseDoc } from './apm_base_doc'; -import { IStackframe } from './fields/stackframe'; +import { Stackframe } from './fields/stackframe'; import { Observer } from './fields/observer'; interface Processor { @@ -24,7 +24,7 @@ export interface SpanRaw extends APMBaseDoc { duration: { us: number }; id: string; name: string; - stacktrace?: IStackframe[]; + stacktrace?: Stackframe[]; subtype?: string; sync?: boolean; type: string; diff --git a/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts b/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts index 4b1f31cb14687..0d2939ed0e8a5 100644 --- a/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts +++ b/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CanvasWorkpad, CanvasElement, CanvasPage } from '../../types'; +import { CanvasWorkpad, CanvasElement, CanvasPage, CanvasVariable } from '../../types'; const BaseWorkpad: CanvasWorkpad = { '@created': '2019-02-08T18:35:23.029Z', @@ -50,6 +50,12 @@ const BaseElement: CanvasElement = { filter: '', }; +const BaseVariable: CanvasVariable = { + name: 'my-var', + value: 'Hello World', + type: 'string', +}; + export const workpads: CanvasWorkpad[] = [ { ...BaseWorkpad, @@ -71,6 +77,11 @@ export const workpads: CanvasWorkpad[] = [ ], }, ], + variables: [ + { + ...BaseVariable, + }, + ], }, { ...BaseWorkpad, @@ -82,6 +93,11 @@ export const workpads: CanvasWorkpad[] = [ ], }, ], + variables: [ + { + ...BaseVariable, + }, + ], }, { ...BaseWorkpad, @@ -103,6 +119,11 @@ export const workpads: CanvasWorkpad[] = [ { ...BasePage, elements: [{ ...BaseElement, expression: 'image | render' }] }, { ...BasePage, elements: [{ ...BaseElement, expression: 'image | render' }] }, ], + variables: [ + { + ...BaseVariable, + }, + ], }, { ...BaseWorkpad, @@ -136,6 +157,11 @@ export const workpads: CanvasWorkpad[] = [ ], }, ], + variables: [ + { + ...BaseVariable, + }, + ], }, { ...BaseWorkpad, @@ -166,6 +192,17 @@ export const workpads: CanvasWorkpad[] = [ ], }, ], + variables: [ + { + ...BaseVariable, + }, + { + ...BaseVariable, + }, + { + ...BaseVariable, + }, + ], }, { ...BaseWorkpad, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts index 16eee349475ef..11551c50d9f25 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts @@ -39,9 +39,9 @@ interface PieOptions { colors: string[]; legend: { show: boolean; - backgroundOpacity: number; - labelBoxBorderColor: string; - position: Legend; + backgroundOpacity?: number; + labelBoxBorderColor?: string; + position?: Legend; }; grid: { show: boolean; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts index 882d1e2ea58b9..33260b5c9303f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); + import { savedLens } from './saved_lens'; import { getQueryFilters } from '../../../public/lib/build_embeddable_filters'; import { ExpressionValueFilter } from '../../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts index 74e41a030de35..b2de0b6526d1e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); + import { savedMap } from './saved_map'; import { getQueryFilters } from '../../../public/lib/build_embeddable_filters'; import { ExpressionValueFilter } from '../../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.test.ts index 9bd32202b563a..7d5952754aa90 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); + import { savedSearch } from './saved_search'; import { buildEmbeddableFilters } from '../../../public/lib/build_embeddable_filters'; import { ExpressionValueFilter } from '../../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts index 8327c1433b9af..a64fb167dd19f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); + import { savedVisualization } from './saved_visualization'; import { getQueryFilters } from '../../../public/lib/build_embeddable_filters'; import { ExpressionValueFilter } from '../../../types'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/image.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/image.stories.storyshot new file mode 100644 index 0000000000000..b9bc21dd6e3ea --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/image.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/image default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/repeat_image.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/repeat_image.stories.storyshot new file mode 100644 index 0000000000000..9b97ae1fdacb3 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/repeat_image.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/repeatImage default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/table.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/table.stories.storyshot new file mode 100644 index 0000000000000..cf9cc6dd82f7f --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/table.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/table default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx new file mode 100644 index 0000000000000..bcd8365034448 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { image } from '../image'; +import { Render } from './render'; +import { elasticLogo } from '../../lib/elastic_logo'; + +storiesOf('renderers/image', module).add('default', () => { + const config = { + type: 'image' as 'image', + mode: 'cover', + dataurl: elasticLogo, + }; + + return ; +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/render.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/render.tsx new file mode 100644 index 0000000000000..647c63c2c1042 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/render.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { action } from '@storybook/addon-actions'; +import React, { useRef, useEffect } from 'react'; +import { RendererFactory, RendererHandlers } from '../../../types'; + +export const defaultHandlers: RendererHandlers = { + destroy: () => action('destroy'), + getElementId: () => 'element-id', + getFilter: () => 'filter', + onComplete: (fn) => undefined, + onEmbeddableDestroyed: action('onEmbeddableDestroyed'), + onEmbeddableInputChange: action('onEmbeddableInputChange'), + onResize: action('onResize'), + resize: action('resize'), + setFilter: action('setFilter'), + done: action('done'), + onDestroy: action('onDestroy'), + reload: action('reload'), + update: action('update'), + event: action('event'), +}; + +/* + Uses a RenderDefinitionFactory and Config to render into an element. + + Intended to be used for stories for RenderDefinitionFactory +*/ +interface RenderAdditionalProps { + height?: string; + width?: string; + handlers?: RendererHandlers; +} + +export const Render = ({ + renderer, + config, + ...rest +}: Renderer extends RendererFactory + ? { renderer: Renderer; config: Config } & RenderAdditionalProps + : { renderer: undefined; config: undefined } & RenderAdditionalProps) => { + const { height, width, handlers } = { + height: '200px', + width: '200px', + handlers: defaultHandlers, + ...rest, + }; + + const containerRef = useRef(null); + + useEffect(() => { + if (renderer && containerRef.current !== null) { + renderer().render(containerRef.current, config, handlers); + } + }, [renderer, config, handlers]); + + return ( +
+ {' '} +
+ ); +}; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/repeat_image.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/repeat_image.stories.tsx new file mode 100644 index 0000000000000..41ccc054a77fb --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/repeat_image.stories.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { repeatImage } from '../repeat_image'; +import { Render } from './render'; +import { elasticLogo } from '../../lib/elastic_logo'; +import { elasticOutline } from '../../lib/elastic_outline'; + +storiesOf('renderers/repeatImage', module).add('default', () => { + const config = { + count: 42, + image: elasticLogo, + size: 20, + max: 60, + emptyImage: elasticOutline, + }; + + return ; +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/table.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/table.stories.tsx new file mode 100644 index 0000000000000..f3c70cb30de45 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/table.stories.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { table } from '../table'; +import { Render } from './render'; + +storiesOf('renderers/table', module).add('default', () => { + const config = { + paginate: true, + perPage: 5, + showHeader: true, + datatable: { + type: 'datatable' as 'datatable', + columns: [ + { + name: 'Foo', + type: 'string' as 'string', + id: 'id-foo', + meta: { type: 'string' as 'string' }, + }, + { + name: 'Bar', + type: 'number' as 'number', + id: 'id-bar', + meta: { type: 'string' as 'string' }, + }, + ], + rows: [ + { Foo: 'a', Bar: 700 }, + { Foo: 'b', Bar: 600 }, + { Foo: 'c', Bar: 500 }, + { Foo: 'd', Bar: 400 }, + { Foo: 'e', Bar: 300 }, + { Foo: 'f', Bar: 200 }, + { Foo: 'g', Bar: 100 }, + ], + }, + }; + + return ; +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/__snapshots__/error.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/__snapshots__/error.stories.storyshot new file mode 100644 index 0000000000000..b7039ee1847c7 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/__snapshots__/error.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/error default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/error.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/error.stories.tsx new file mode 100644 index 0000000000000..c71999bc04bb1 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/error/__stories__/error.stories.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { error } from '../'; +import { Render } from '../../__stories__/render'; + +storiesOf('renderers/error', module).add('default', () => { + const thrownError = new Error('There was an error'); + const config = { + error: thrownError, + }; + return ; +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/__stories__/__snapshots__/markdown.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/__stories__/__snapshots__/markdown.stories.storyshot new file mode 100644 index 0000000000000..79f447c953d6d --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/__stories__/__snapshots__/markdown.stories.storyshot @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/markdown default 1`] = ` +
+ +
+`; + +exports[`Storyshots renderers/markdown links in new tab 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/__stories__/markdown.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/__stories__/markdown.stories.tsx new file mode 100644 index 0000000000000..d5b190c74a92f --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/markdown/__stories__/markdown.stories.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { markdown } from '../'; +import { Render } from '../../__stories__/render'; + +storiesOf('renderers/markdown', module) + .add('default', () => { + const config = { + content: '# This is Markdown', + font: { + css: '', + spec: {}, + type: 'style' as 'style', + }, + openLinksInNewTab: false, + }; + return ; + }) + .add('links in new tab', () => { + const config = { + content: '[Elastic.co](https://elastic.co)', + font: { + css: '', + spec: {}, + type: 'style' as 'style', + }, + openLinksInNewTab: true, + }; + return ; + }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/__stories__/__snapshots__/pie.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/__stories__/__snapshots__/pie.stories.storyshot new file mode 100644 index 0000000000000..3260dbe8c83c2 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/__stories__/__snapshots__/pie.stories.storyshot @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/pie default 1`] = ` +
+ +
+`; + +exports[`Storyshots renderers/pie with legend 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/__stories__/pie.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/__stories__/pie.stories.tsx new file mode 100644 index 0000000000000..dea2876de0ec8 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/pie/__stories__/pie.stories.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { pie } from '../'; +import { Render } from '../../__stories__/render'; + +const pieOptions = { + canvas: false, + colors: ['#882E72', '#B178A6', '#D6C1DE'], + grid: { show: false }, + legend: { show: false }, + series: { + pie: { + show: true, + innerRadius: 0, + label: { show: true, radius: 1 }, + radius: 'auto' as 'auto', + stroke: { width: 0 }, + tilt: 1, + }, + }, +}; + +const data = [ + { + data: [10], + label: 'A', + }, + { + data: [10], + label: 'B', + }, + { + data: [10], + label: 'C', + }, +]; + +storiesOf('renderers/pie', module) + .add('default', () => { + const config = { + data, + options: pieOptions, + font: { + css: '', + spec: {}, + type: 'style' as 'style', + }, + }; + return ; + }) + .add('with legend', () => { + const options = { + ...pieOptions, + legend: { show: true }, + }; + + const config = { + data, + options, + font: { + css: '', + spec: {}, + type: 'style' as 'style', + }, + }; + + return ; + }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/__stories__/__snapshots__/plot.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/__stories__/__snapshots__/plot.stories.storyshot new file mode 100644 index 0000000000000..7419d13fc7195 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/__stories__/__snapshots__/plot.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/plot default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/__stories__/plot.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/__stories__/plot.stories.tsx new file mode 100644 index 0000000000000..0e9566d2a5c20 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/plot/__stories__/plot.stories.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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { plot } from '../'; +import { Render } from '../../__stories__/render'; + +const plotOptions = { + canvas: false, + colors: ['#882E72', '#B178A6', '#D6C1DE'], + grid: { + margin: { + bottom: 0, + left: 0, + right: 30, + top: 20, + }, + }, + legend: { show: true }, + series: { + bubbles: { + show: true, + fill: false, + }, + }, + xaxis: { + show: true, + mode: 'time', + }, + yaxis: { + show: true, + }, +}; + +const data = [ + { + bubbles: { show: true }, + data: [ + [1546351551031, 33, { size: 5 }], + [1546351551131, 38, { size: 2 }], + ], + label: 'done', + }, + { + bubbles: { show: true }, + data: [ + [1546351551032, 37, { size: 4 }], + [1546351551139, 45, { size: 3 }], + ], + label: 'running', + }, +]; + +storiesOf('renderers/plot', module).add('default', () => { + const config = { + data, + options: plotOptions, + font: { + css: '', + spec: {}, + type: 'style' as 'style', + }, + }; + return ; +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/__snapshots__/progress.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/__snapshots__/progress.stories.storyshot new file mode 100644 index 0000000000000..1fe884656ef3b --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/__snapshots__/progress.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/progress default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/progress.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/progress.stories.tsx new file mode 100644 index 0000000000000..3e20cfd4772a8 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/__stories__/progress.stories.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { progress } from '../'; +import { Render } from '../../__stories__/render'; +import { Shape } from '../../../functions/common/progress'; + +storiesOf('renderers/progress', module).add('default', () => { + const config = { + barColor: '#bc1234', + barWeight: 20, + font: { + css: '', + spec: {}, + type: 'style' as 'style', + }, + label: '66%', + max: 1, + shape: Shape.UNICORN, + value: 0.66, + valueColor: '#000', + valueWeight: 15, + }; + + return ; +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/__snapshots__/reveal_image.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/__snapshots__/reveal_image.stories.storyshot new file mode 100644 index 0000000000000..b9963565a09f5 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/__snapshots__/reveal_image.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/revealImage default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/reveal_image.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/reveal_image.stories.tsx new file mode 100644 index 0000000000000..637f9a2bb6986 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/reveal_image.stories.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { revealImage } from '../'; +import { Render } from '../../__stories__/render'; +import { elasticOutline } from '../../../lib/elastic_outline'; +import { elasticLogo } from '../../../lib/elastic_logo'; +import { Origin } from '../../../functions/common/revealImage'; + +storiesOf('renderers/revealImage', module).add('default', () => { + const config = { + image: elasticLogo, + emptyImage: elasticOutline, + origin: Origin.LEFT, + percent: 0.45, + }; + + return ; +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/__stories__/__snapshots__/shape.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/__stories__/__snapshots__/shape.stories.storyshot new file mode 100644 index 0000000000000..317c20021015a --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/__stories__/__snapshots__/shape.stories.storyshot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots renderers/shape default 1`] = ` +
+ +
+`; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/__stories__/shape.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/__stories__/shape.stories.tsx new file mode 100644 index 0000000000000..19df14c08688f --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/shape/__stories__/shape.stories.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { shape } from '../'; +import { Render } from '../../__stories__/render'; +import { Shape } from '../../../functions/common/shape'; + +storiesOf('renderers/shape', module).add('default', () => { + const config = { + type: 'shape' as 'shape', + border: '#FFEEDD', + borderWidth: 8, + shape: Shape.BOOKMARK, + fill: '#112233', + maintainAspect: true, + }; + + return ; +}); diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.test.ts b/x-pack/plugins/canvas/common/lib/autocomplete.test.ts index f7a773f6ca36a..777810cad05ba 100644 --- a/x-pack/plugins/canvas/common/lib/autocomplete.test.ts +++ b/x-pack/plugins/canvas/common/lib/autocomplete.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); import { functionSpecs } from '../../__tests__/fixtures/function_specs'; import { diff --git a/x-pack/plugins/canvas/public/__tests__/setup.js b/x-pack/plugins/canvas/public/__tests__/setup.js deleted file mode 100644 index 1792397d0614e..0000000000000 --- a/x-pack/plugins/canvas/public/__tests__/setup.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import enzyme from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; - -// this will run before any code that's inside a describe block -// so we can use it to set up whatever we need for our browser tests -before(() => { - // configure enzume - enzyme.configure({ adapter: new Adapter() }); -}); diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.test.tsx b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.test.tsx index e8b772e0f2fbd..b037d30ada12f 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.test.tsx +++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.test.tsx @@ -14,7 +14,6 @@ import { import { METRIC_TYPE } from '../../../lib/ui_metric'; import { ResolvedArgType } from '../../../../types'; -jest.mock('ui/new_platform'); const trackMetric = jest.fn(); const Component = withUnconnectedElementsLoadedTelemetry(() =>
, trackMetric); diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/tsconfig.json b/x-pack/plugins/canvas/public/lib/aeroelastic/tsconfig.json deleted file mode 100644 index 3b61e4b414626..0000000000000 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "../../../../../tsconfig", - "compilerOptions": { - "module": "commonjs", - "lib": ["es2018", "dom"], - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": false, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "noImplicitReturns": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "baseUrl": ".", - "paths": { - "layout/*": ["aeroelastic/*"] - }, - "types": ["@kbn/x-pack/plugins/canvas/public/lib/aeroelastic"] - }, - "exclude": ["node_modules", "**/*.spec.ts", "node_modules/@types/mocha"] -} diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts b/x-pack/plugins/canvas/public/lib/aeroelastic/typespec.test.ts similarity index 67% rename from x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts rename to x-pack/plugins/canvas/public/lib/aeroelastic/typespec.test.ts index cb46a3d6be402..3b1fde12edcc4 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/typespec.test.ts @@ -4,49 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { select } from '../../select'; -import { Json, Selector, Vector2d, Vector3d, TransformMatrix2d, TransformMatrix3d } from '../..'; +import { select } from './select'; +import { Json, Selector, Vector2d, Vector3d, TransformMatrix2d, TransformMatrix3d } from './index'; import { mvMultiply as mult2d, ORIGIN as UNIT2D, UNITMATRIX as UNITMATRIX2D, add as add2d, -} from '../../matrix2d'; +} from './matrix2d'; import { mvMultiply as mult3d, ORIGIN as UNIT3D, NANMATRIX as NANMATRIX3D, add as add3d, -} from '../../matrix'; +} from './matrix'; -/* +// helper to mark variables as "used" so they don't trigger errors +const use = (...vars: any[]) => vars.includes(null); +/* Type checking isn't too useful if future commits can accidentally weaken the type constraints, because a TypeScript linter will not complain - everything that passed before will continue to pass. The coder will not have feedback that the original intent with the typing got compromised. To declare the intent via passing and failing type checks, test cases are needed, some of which designed to expect a TS pass, some of them to expect a TS complaint. It documents intent for peers too, as type specs are a tough read. - Run compile-time type specification tests in the `kibana` root with: - - yarn typespec - Test "cases" expecting to pass TS checks are not annotated, while ones we want TS to complain about are prepended with the comment - - // typings:expect-error + + // @ts-expect-error The test "suite" and "cases" are wrapped in IIFEs to prevent linters from complaining about the unused binding. It can be structured internally as desired. - */ -((): void => { - /** - * TYPE TEST SUITE - */ +describe('vector array creation', () => { + it('passes typechecking', () => { + let vec2d: Vector2d = UNIT2D; + let vec3d: Vector3d = UNIT3D; + + use(vec2d, vec3d); - (function vectorArrayCreationTests(vec2d: Vector2d, vec3d: Vector3d): void { // 2D vector OK vec2d = [0, 0, 0] as Vector2d; // OK vec2d = [-0, NaN, -Infinity] as Vector2d; // IEEE 754 values are OK @@ -57,30 +55,35 @@ import { // 2D vector not OK - // typings:expect-error + // @ts-expect-error vec2d = 3; // not even an array - // typings:expect-error + // @ts-expect-error vec2d = [] as Vector2d; // no elements - // typings:expect-error + // @ts-expect-error vec2d = [0, 0] as Vector2d; // too few elements - // typings:expect-error + // @ts-expect-error vec2d = [0, 0, 0, 0] as Vector2d; // too many elements // 3D vector not OK - // typings:expect-error + // @ts-expect-error vec3d = 3; // not even an array - // typings:expect-error + // @ts-expect-error vec3d = [] as Vector3d; // no elements - // typings:expect-error + // @ts-expect-error vec3d = [0, 0, 0] as Vector3d; // too few elements - // typings:expect-error + // @ts-expect-error vec3d = [0, 0, 0, 0, 0] as Vector3d; // too many elements + }); +}); + +describe('matrix array creation', () => { + it('passes typechecking', () => { + let mat2d: TransformMatrix2d = UNITMATRIX2D; + let mat3d: TransformMatrix3d = NANMATRIX3D; - return; // arrayCreationTests - })(UNIT2D, UNIT3D); + use(mat2d, mat3d); - (function matrixArrayCreationTests(mat2d: TransformMatrix2d, mat3d: TransformMatrix3d): void { // 2D matrix OK mat2d = [0, 1, 2, 3, 4, 5, 6, 7, 8] as TransformMatrix2d; // OK mat2d = [-0, NaN, -Infinity, 3, 4, 5, 6, 7, 8] as TransformMatrix2d; // IEEE 754 values are OK @@ -91,80 +94,87 @@ import { // 2D matrix not OK - // typings:expect-error + // @ts-expect-error mat2d = 3; // not even an array - // typings:expect-error + // @ts-expect-error mat2d = [] as TransformMatrix2d; // no elements - // typings:expect-error + // @ts-expect-error mat2d = [0, 1, 2, 3, 4, 5, 6, 7] as TransformMatrix2d; // too few elements - // typings:expect-error + // @ts-expect-error mat2d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as TransformMatrix2d; // too many elements // 3D vector not OK - // typings:expect-error + // @ts-expect-error mat3d = 3; // not even an array - // typings:expect-error + // @ts-expect-error mat3d = [] as TransformMatrix3d; // no elements - // typings:expect-error + // @ts-expect-error mat3d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] as TransformMatrix3d; // too few elements - // typings:expect-error + // @ts-expect-error mat3d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as TransformMatrix3d; // too many elements - // Matrix modification should NOT be OK - mat3d[3] = 100; // too bad the ReadOnly part appears not to be enforced so can't precede it with typings:expect-error + // @ts-expect-error + mat3d[3] = 100; // Matrix modification is NOT OK + }); +}); - return; // arrayCreationTests - })(UNITMATRIX2D, NANMATRIX3D); +describe('matrix addition', () => { + it('passes typecheck', () => { + const mat2d: TransformMatrix2d = UNITMATRIX2D; + const mat3d: TransformMatrix3d = NANMATRIX3D; - (function matrixMatrixAdditionTests(mat2d: TransformMatrix2d, mat3d: TransformMatrix3d): void { add2d(mat2d, mat2d); // OK add3d(mat3d, mat3d); // OK - // typings:expect-error + // @ts-expect-error add2d(mat2d, mat3d); // at least one arg doesn't comply - // typings:expect-error + // @ts-expect-error add2d(mat3d, mat2d); // at least one arg doesn't comply - // typings:expect-error + // @ts-expect-error add2d(mat3d, mat3d); // at least one arg doesn't comply - // typings:expect-error + // @ts-expect-error add3d(mat2d, mat3d); // at least one arg doesn't comply - // typings:expect-error + // @ts-expect-error add3d(mat3d, mat2d); // at least one arg doesn't comply - // typings:expect-error + // @ts-expect-error add3d(mat2d, mat2d); // at least one arg doesn't comply + }); +}); - return; // matrixMatrixAdditionTests - })(UNITMATRIX2D, NANMATRIX3D); +describe('matric vector multiplication', () => { + it('passes typecheck', () => { + const vec2d: Vector2d = UNIT2D; + const mat2d: TransformMatrix2d = UNITMATRIX2D; + const vec3d: Vector3d = UNIT3D; + const mat3d: TransformMatrix3d = NANMATRIX3D; - (function matrixVectorMultiplicationTests( - vec2d: Vector2d, - mat2d: TransformMatrix2d, - vec3d: Vector3d, - mat3d: TransformMatrix3d - ): void { mult2d(mat2d, vec2d); // OK mult3d(mat3d, vec3d); // OK - // typings:expect-error + // @ts-expect-error mult3d(mat2d, vec2d); // trying to use a 3d fun for 2d args - // typings:expect-error + // @ts-expect-error mult2d(mat3d, vec3d); // trying to use a 2d fun for 3d args - // typings:expect-error + // @ts-expect-error mult2d(mat3d, vec2d); // 1st arg is a mismatch - // typings:expect-error + // @ts-expect-error mult2d(mat2d, vec3d); // 2nd arg is a mismatch - // typings:expect-error + // @ts-expect-error mult3d(mat2d, vec3d); // 1st arg is a mismatch - // typings:expect-error + // @ts-expect-error mult3d(mat3d, vec2d); // 2nd arg is a mismatch + }); +}); - return; // matrixVectorTests - })(UNIT2D, UNITMATRIX2D, UNIT3D, NANMATRIX3D); +describe('json', () => { + it('passes typecheck', () => { + let plain: Json = null; + + use(plain); - (function jsonTests(plain: Json): void { // numbers are OK plain = 1; plain = NaN; @@ -182,37 +192,37 @@ import { plain = [0, null, false, NaN, 3.14, 'one more']; plain = { a: { b: 5, c: { d: [1, 'a', -Infinity, null], e: -1 }, f: 'b' }, g: false }; - // typings:expect-error + // @ts-expect-error plain = undefined; // it's undefined - // typings:expect-error + // @ts-expect-error plain = (a) => a; // it's a function - // typings:expect-error + // @ts-expect-error plain = [new Date()]; // it's a time - // typings:expect-error + // @ts-expect-error plain = { a: Symbol('haha') }; // symbol isn't permitted either - // typings:expect-error + // @ts-expect-error plain = window || void 0; - // typings:expect-error + // @ts-expect-error plain = { a: { b: 5, c: { d: [1, 'a', undefined, null] } } }; // going deep into the structure + }); +}); - return; // jsonTests - })(null); +describe('select', () => { + it('passes typecheck', () => { + let selector: Selector; - (function selectTests(selector: Selector): void { selector = select((a: Json) => a); // one arg selector = select((a: Json, b: Json): Json => `${a} and ${b}`); // more args selector = select(() => 1); // zero arg selector = select((...args: Json[]) => args); // variadic - // typings:expect-error + // @ts-expect-error selector = (a: Json) => a; // not a selector - // typings:expect-error + // @ts-expect-error selector = select(() => {}); // should yield a JSON value, but it returns void - // typings:expect-error + // @ts-expect-error selector = select((x: Json) => ({ a: x, b: undefined })); // should return a Json - return; // selectTests - })(select((a: Json) => a)); - - return; // test suite -})(); + use(selector); + }); +}); diff --git a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts index bf69a862d5c30..e451a39df06db 100644 --- a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts +++ b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); // actions/elements has some dependencies on ui/new_platform. jest.mock('../../../lib/app_state'); import { workpadRefresh } from '../workpad_refresh'; diff --git a/x-pack/plugins/canvas/public/state/reducers/embeddables.test.ts b/x-pack/plugins/canvas/public/state/reducers/embeddables.test.ts index 5b1192630897a..08b59e5fedc44 100644 --- a/x-pack/plugins/canvas/public/state/reducers/embeddables.test.ts +++ b/x-pack/plugins/canvas/public/state/reducers/embeddables.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); + import { State } from '../../../types'; import { updateEmbeddableExpression } from '../actions/embeddable'; import { embeddableReducer } from './embeddable'; diff --git a/x-pack/plugins/canvas/server/collectors/workpad_collector.test.ts b/x-pack/plugins/canvas/server/collectors/workpad_collector.test.ts index 9f71edcc05bf2..32665cc42dc4e 100644 --- a/x-pack/plugins/canvas/server/collectors/workpad_collector.test.ts +++ b/x-pack/plugins/canvas/server/collectors/workpad_collector.test.ts @@ -49,6 +49,14 @@ describe('usage collector handle es response data', () => { 'shape', ], }, + variables: { + total: 7, + per_workpad: { + avg: 1.1666666666666667, + min: 0, + max: 3, + }, + }, }); }); @@ -63,6 +71,7 @@ describe('usage collector handle es response data', () => { pages: { total: 1, per_workpad: { avg: 1, min: 1, max: 1 } }, elements: { total: 1, per_page: { avg: 1, min: 1, max: 1 } }, functions: { total: 1, in_use: ['toast'], per_element: { avg: 1, min: 1, max: 1 } }, + variables: { total: 1, per_workpad: { avg: 1, min: 1, max: 1 } }, }); }); @@ -76,6 +85,39 @@ describe('usage collector handle es response data', () => { pages: { total: 0, per_workpad: { avg: 0, min: 0, max: 0 } }, elements: undefined, functions: undefined, + variables: { total: 1, per_workpad: { avg: 1, min: 1, max: 1 } }, // Variables still possible even with no pages + }); + }); + + it('should handle cases where the version workpad might not have variables', () => { + const workpad = cloneDeep(workpads[0]); + // @ts-ignore + workpad.variables = undefined; + + const mockWorkpadsOld = [workpad]; + const usage = summarizeWorkpads(mockWorkpadsOld); + expect(usage).toEqual({ + workpads: { total: 1 }, + pages: { total: 1, per_workpad: { avg: 1, min: 1, max: 1 } }, + elements: { total: 1, per_page: { avg: 1, min: 1, max: 1 } }, + functions: { + total: 7, + in_use: [ + 'demodata', + 'ply', + 'rowCount', + 'as', + 'staticColumn', + 'math', + 'mapColumn', + 'sort', + 'pointseries', + 'plot', + 'seriesStyle', + ], + per_element: { avg: 7, min: 7, max: 7 }, + }, + variables: { total: 0, per_workpad: { avg: 0, min: 0, max: 0 } }, // Variables still possible even with no pages }); }); diff --git a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts index 4b00d061c17ce..9fa39c580962d 100644 --- a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts @@ -44,6 +44,14 @@ interface WorkpadTelemetry { max: number; }; }; + variables?: { + total: number; + per_workpad: { + avg: number; + min: number; + max: number; + }; + }; } /** @@ -81,7 +89,10 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr }); }, []); - return { pages, elementCounts, functionCounts }; + const variableCount = + workpad.variables && workpad.variables.length ? workpad.variables.length : 0; + + return { pages, elementCounts, functionCounts, variableCount }; }); // combine together info from across the workpads @@ -91,9 +102,10 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr pageCounts: number[]; elementCounts: number[]; functionCounts: number[]; + variableCounts: number[]; }>( (accum, pageInfo) => { - const { pages, elementCounts, functionCounts } = pageInfo; + const { pages, elementCounts, functionCounts, variableCount } = pageInfo; return { pageMin: pages.count < accum.pageMin ? pages.count : accum.pageMin, @@ -101,6 +113,7 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr pageCounts: accum.pageCounts.concat(pages.count), elementCounts: accum.elementCounts.concat(elementCounts), functionCounts: accum.functionCounts.concat(functionCounts), + variableCounts: accum.variableCounts.concat([variableCount]), }; }, { @@ -109,13 +122,23 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr pageCounts: [], elementCounts: [], functionCounts: [], + variableCounts: [], } ); - const { pageCounts, pageMin, pageMax, elementCounts, functionCounts } = combinedWorkpadsInfo; + const { + pageCounts, + pageMin, + pageMax, + elementCounts, + functionCounts, + variableCounts, + } = combinedWorkpadsInfo; const pageTotal = arraySum(pageCounts); const elementsTotal = arraySum(elementCounts); const functionsTotal = arraySum(functionCounts); + const variableTotal = arraySum(variableCounts); + const pagesInfo = workpadsInfo.length > 0 ? { @@ -151,11 +174,21 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr } : undefined; + const variableInfo = { + total: variableTotal, + per_workpad: { + avg: variableTotal / variableCounts.length, + min: arrayMin(variableCounts) || 0, + max: arrayMax(variableCounts) || 0, + }, + }; + return { workpads: { total: workpadsInfo.length }, pages: pagesInfo, elements: elementsInfo, functions: functionsInfo, + variables: variableInfo, }; } diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap index 75e132863e47e..c4917539cbeda 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap @@ -6,7 +6,6 @@ exports[` can navigate Autoplay Settings 1`] = ` data-eui="EuiFocusTrap" >
can navigate Autoplay Settings 2`] = ` data-eui="EuiFocusTrap" >
can navigate Toolbar Settings, closes when activated 1`] = data-eui="EuiFocusTrap" >
can navigate Toolbar Settings, closes when activated 2`] = data-eui="EuiFocusTrap" >
can navigate Toolbar Settings, closes when activated 2`] =
`; -exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js index eda275ba50c1a..cdb107016e465 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js @@ -11,8 +11,6 @@ jest.mock('../services/auto_follow_pattern_validators', () => ({ validateLeaderIndexPattern: jest.fn(), })); -jest.mock('ui/new_platform'); - describe(' { describe('updateFormErrors()', () => { it('should merge errors with existing fieldsErrors', () => { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js index 93da20a8ed93c..72c4832a631a7 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.test.js @@ -6,8 +6,6 @@ import { updateFields, updateFormErrors } from './follower_index_form'; -jest.mock('ui/new_platform'); - describe(' state transitions', () => { it('updateFormErrors() should merge errors with existing fieldsErrors', () => { const errors = { name: 'Some error' }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js index 11ec125c17d59..924bbe708c73a 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js @@ -6,8 +6,6 @@ import { validateAutoFollowPattern } from './auto_follow_pattern_validators'; -jest.mock('ui/new_platform'); - describe('Auto-follow pattern validators', () => { describe('validateAutoFollowPattern()', () => { it('returns empty object when autoFollowPattern is undefined', () => { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js index 01dccf70a21d6..a3655df1f8956 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js @@ -8,7 +8,6 @@ import { reducer, initialState } from './api'; import { API_STATUS } from '../../constants'; import { apiRequestStart, apiRequestEnd, setApiError } from '../actions'; -jest.mock('ui/new_platform'); jest.mock('../../constants', () => ({ API_STATUS: { IDLE: 'idle', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts index 0fb3bb8080d82..3f71759390879 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts @@ -4,28 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kea } from 'kea'; +import { kea, MakeLogicType } from 'kea'; import { IInitialAppData } from '../../../common/types'; -import { IKeaLogic } from '../shared/types'; -export interface IAppLogicValues { +export interface IAppValues { hasInitialized: boolean; } -export interface IAppLogicActions { +export interface IAppActions { initializeAppData(props: IInitialAppData): void; } -export const AppLogic = kea({ - actions: (): IAppLogicActions => ({ +export const AppLogic = kea>({ + actions: { initializeAppData: (props) => props, - }), - reducers: () => ({ + }, + reducers: { hasInitialized: [ false, { initializeAppData: () => true, }, ], - }), -}) as IKeaLogic; + }, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/engine.svg b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/engine.svg deleted file mode 100644 index ceab918e92e70..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/engine.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/logo.svg b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/logo.svg deleted file mode 100644 index 2284a425b5add..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/logo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/meta_engine.svg b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/meta_engine.svg deleted file mode 100644 index 4e01e9a0b34fb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/meta_engine.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/engine_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/engine_icon.tsx new file mode 100644 index 0000000000000..64494bfaa7949 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/engine_icon.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export const EngineIcon: React.FC = () => ( + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/meta_engine_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/meta_engine_icon.tsx new file mode 100644 index 0000000000000..e2939ddcde9d7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/meta_engine_icon.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export const MetaEngineIcon: React.FC = () => ( + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss index e39bbbc95564b..5e8a20ba425ad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss @@ -15,6 +15,8 @@ .engineIcon { display: inline-block; width: $euiSize; - height: $euiSize; + // Use line-height of EuiTitle - SVG will vertically center accordingly + height: map-get(map-get($euiTitles, 's'), 'line-height'); + vertical-align: top; margin-right: $euiSizeXS; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx index c3b47b2b585bd..9703fde7e140a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx @@ -20,8 +20,8 @@ import { FlashMessages } from '../../../shared/flash_messages'; import { LicenseContext, ILicenseContext, hasPlatinumLicense } from '../../../shared/licensing'; import { KibanaContext, IKibanaContext } from '../../../index'; -import EnginesIcon from '../../assets/engine.svg'; -import MetaEnginesIcon from '../../assets/meta_engine.svg'; +import { EngineIcon } from './assets/engine_icon'; +import { MetaEngineIcon } from './assets/meta_engine_icon'; import { EngineOverviewHeader, LoadingState, EmptyState } from './components'; import { EngineTable } from './engine_table'; @@ -93,7 +93,7 @@ export const EngineOverview: React.FC = () => {

- + {

- + ( ( ); export const AppSearchConfigured: React.FC = (props) => { - const { hasInitialized } = useValues(AppLogic) as IAppLogicValues; - const { initializeAppData } = useActions(AppLogic) as IAppLogicActions; - const { errorConnecting } = useValues(HttpLogic) as IHttpLogicValues; + const { hasInitialized } = useValues(AppLogic); + const { initializeAppData } = useActions(AppLogic); + const { errorConnecting } = useValues(HttpLogic); useEffect(() => { if (!hasInitialized) initializeAppData(props); diff --git a/x-pack/plugins/enterprise_search/public/applications/kea.d.ts b/x-pack/plugins/enterprise_search/public/applications/kea.d.ts deleted file mode 100644 index 961d93ccc12e6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/kea.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -declare module 'kea' { - export function useValues(logic?: object): object; - export function useActions(logic?: object): object; - export function getContext(): { store: object }; - export function resetContext(context: object): object; - export function kea(logic: object): object; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx index 5a909a287795c..d184eb4dcd644 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { useValues } from 'kea'; import { EuiCallOut, EuiCallOutProps, EuiSpacer } from '@elastic/eui'; -import { FlashMessagesLogic, IFlashMessagesValues } from './flash_messages_logic'; +import { FlashMessagesLogic } from './flash_messages_logic'; const FLASH_MESSAGE_TYPES = { success: { color: 'success' as EuiCallOutProps['color'], icon: 'check' }, @@ -18,7 +18,7 @@ const FLASH_MESSAGE_TYPES = { }; export const FlashMessages: React.FC = ({ children }) => { - const { messages } = useValues(FlashMessagesLogic) as IFlashMessagesValues; + const { messages } = useValues(FlashMessagesLogic); // If we have no messages to display, do not render the element at all if (!messages.length) return null; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts index 96c7817832c52..3ae48f352b2c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kea } from 'kea'; +import { kea, MakeLogicType } from 'kea'; import { ReactNode } from 'react'; import { History } from 'history'; -import { IKeaLogic, TKeaReducers, IKeaParams } from '../types'; - export interface IFlashMessage { type: 'success' | 'info' | 'warning' | 'error'; message: ReactNode; @@ -22,27 +20,27 @@ export interface IFlashMessagesValues { historyListener: Function | null; } export interface IFlashMessagesActions { - setFlashMessages(messages: IFlashMessage | IFlashMessage[]): void; + setFlashMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; clearFlashMessages(): void; - setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): void; + setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; clearQueuedMessages(): void; - listenToHistory(history: History): void; - setHistoryListener(historyListener: Function): void; + listenToHistory(history: History): History; + setHistoryListener(historyListener: Function): { historyListener: Function }; } const convertToArray = (messages: IFlashMessage | IFlashMessage[]) => !Array.isArray(messages) ? [messages] : messages; -export const FlashMessagesLogic = kea({ - actions: (): IFlashMessagesActions => ({ +export const FlashMessagesLogic = kea>({ + actions: { setFlashMessages: (messages) => ({ messages: convertToArray(messages) }), clearFlashMessages: () => null, setQueuedMessages: (messages) => ({ messages: convertToArray(messages) }), clearQueuedMessages: () => null, listenToHistory: (history) => history, setHistoryListener: (historyListener) => ({ historyListener }), - }), - reducers: (): TKeaReducers => ({ + }, + reducers: { messages: [ [], { @@ -63,8 +61,8 @@ export const FlashMessagesLogic = kea({ setHistoryListener: (_, { historyListener }) => historyListener, }, ], - }), - listeners: ({ values, actions }): Partial => ({ + }, + listeners: ({ values, actions }) => ({ listenToHistory: (history) => { // On React Router navigation, clear previous flash messages and load any queued messages const unlisten = history.listen(() => { @@ -81,7 +79,4 @@ export const FlashMessagesLogic = kea({ if (removeHistoryListener) removeHistoryListener(); }, }), -} as IKeaParams) as IKeaLogic< - IFlashMessagesValues, - IFlashMessagesActions ->; +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx index 584124468a91f..a3ceabcf6ac8a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx @@ -8,19 +8,15 @@ import React, { useEffect } from 'react'; import { useValues, useActions } from 'kea'; import { History } from 'history'; -import { - FlashMessagesLogic, - IFlashMessagesValues, - IFlashMessagesActions, -} from './flash_messages_logic'; +import { FlashMessagesLogic } from './flash_messages_logic'; interface IFlashMessagesProviderProps { history: History; } export const FlashMessagesProvider: React.FC = ({ history }) => { - const { historyListener } = useValues(FlashMessagesLogic) as IFlashMessagesValues; - const { listenToHistory } = useActions(FlashMessagesLogic) as IFlashMessagesActions; + const { historyListener } = useValues(FlashMessagesLogic); + const { listenToHistory } = useActions(FlashMessagesLogic); useEffect(() => { if (!historyListener) listenToHistory(history); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts index 7bf7a19ed451f..fb2d9b1061723 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts @@ -4,32 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kea } from 'kea'; +import { kea, MakeLogicType } from 'kea'; import { HttpSetup } from 'src/core/public'; -import { IKeaLogic, IKeaParams, TKeaReducers } from '../../shared/types'; - -export interface IHttpLogicValues { +export interface IHttpValues { http: HttpSetup; httpInterceptors: Function[]; errorConnecting: boolean; } -export interface IHttpLogicActions { - initializeHttp({ http, errorConnecting }: { http: HttpSetup; errorConnecting?: boolean }): void; +export interface IHttpActions { + initializeHttp({ + http, + errorConnecting, + }: { + http: HttpSetup; + errorConnecting?: boolean; + }): { http: HttpSetup; errorConnecting?: boolean }; initializeHttpInterceptors(): void; - setHttpInterceptors(httpInterceptors: Function[]): void; - setErrorConnecting(errorConnecting: boolean): void; + setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] }; + setErrorConnecting(errorConnecting: boolean): { errorConnecting: boolean }; } -export const HttpLogic = kea({ - actions: (): IHttpLogicActions => ({ +export const HttpLogic = kea>({ + actions: { initializeHttp: ({ http, errorConnecting }) => ({ http, errorConnecting }), initializeHttpInterceptors: () => null, setHttpInterceptors: (httpInterceptors) => ({ httpInterceptors }), setErrorConnecting: (errorConnecting) => ({ errorConnecting }), - }), - reducers: (): TKeaReducers => ({ + }, + reducers: { http: [ (null as unknown) as HttpSetup, { @@ -49,7 +53,7 @@ export const HttpLogic = kea({ setErrorConnecting: (_, { errorConnecting }) => errorConnecting, }, ], - }), + }, listeners: ({ values, actions }) => ({ initializeHttpInterceptors: () => { const httpInterceptors = []; @@ -80,7 +84,4 @@ export const HttpLogic = kea({ }); }, }), -} as IKeaParams) as IKeaLogic< - IHttpLogicValues, - IHttpLogicActions ->; +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx index 6febc1869054f..4c2160195a1af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx @@ -9,7 +9,7 @@ import { useActions } from 'kea'; import { HttpSetup } from 'src/core/public'; -import { HttpLogic, IHttpLogicActions } from './http_logic'; +import { HttpLogic } from './http_logic'; interface IHttpProviderProps { http: HttpSetup; @@ -17,7 +17,7 @@ interface IHttpProviderProps { } export const HttpProvider: React.FC = (props) => { - const { initializeHttp, initializeHttpInterceptors } = useActions(HttpLogic) as IHttpLogicActions; + const { initializeHttp, initializeHttpInterceptors } = useActions(HttpLogic); useEffect(() => { initializeHttp(props); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts index 449ff9d56debf..db65e80ca25c2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts @@ -4,5 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { HttpLogic, IHttpLogicValues, IHttpLogicActions } from './http_logic'; +export { HttpLogic, IHttpValues, IHttpActions } from './http_logic'; export { HttpProvider } from './http_provider'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 561016d36921d..3fd1dcad0066e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -5,63 +5,3 @@ */ export { IFlashMessage } from './flash_messages'; - -export interface IKeaLogic { - mount(): Function; - values: IKeaValues; - actions: IKeaActions; -} - -/** - * This reusable interface mostly saves us a few characters / allows us to skip - * defining params inline. Unfortunately, the return values *do not work* as - * expected (hence the voids). While I can tell selectors to use TKeaSelectors, - * the return value is *not* properly type checked if it's not declared inline. :/ - * - * Also note that if you switch to Kea 2.1's plain object notation - - * `selectors: {}` vs. `selectors: () => ({})` - * - type checking also stops working and type errors become significantly less - * helpful - showing less specific error messages and highlighting. 👎 - */ -export interface IKeaParams { - selectors?(params: { selectors: IKeaValues }): void; - listeners?(params: { actions: IKeaActions; values: IKeaValues }): void; - events?(params: { actions: IKeaActions; values: IKeaValues }): void; -} - -/** - * This reducers() type checks that: - * - * 1. The value object keys are defined within IKeaValues - * 2. The default state (array[0]) matches the type definition within IKeaValues - * 3. The action object keys (array[1]) are defined within IKeaActions - * 3. The new state returned by the action matches the type definition within IKeaValues - */ -export type TKeaReducers = { - [Value in keyof IKeaValues]?: [ - IKeaValues[Value], - { - [Action in keyof IKeaActions]?: ( - state: IKeaValues[Value], - payload: IKeaValues - ) => IKeaValues[Value]; - } - ]; -}; - -/** - * This selectors() type checks that: - * - * 1. The object keys are defined within IKeaValues - * 2. The selected values are defined within IKeaValues - * 3. The returned value match the type definition within IKeaValues - * - * The unknown[] and any[] are unfortunately because I have no idea how to - * assert for arbitrary type/values as an array - */ -export type TKeaSelectors = { - [Value in keyof IKeaValues]?: [ - (selectors: IKeaValues) => unknown[], - (...args: any[]) => IKeaValues[Value] // eslint-disable-line @typescript-eslint/no-explicit-any - ]; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts index b7116f02663c1..5bf2b41cfc264 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kea } from 'kea'; +import { kea, MakeLogicType } from 'kea'; import { IInitialAppData } from '../../../common/types'; import { IWorkplaceSearchInitialData } from '../../../common/types/workplace_search'; -import { IKeaLogic } from '../shared/types'; export interface IAppValues extends IWorkplaceSearchInitialData { hasInitialized: boolean; @@ -17,16 +16,16 @@ export interface IAppActions { initializeAppData(props: IInitialAppData): void; } -export const AppLogic = kea({ - actions: (): IAppActions => ({ +export const AppLogic = kea>({ + actions: { initializeAppData: ({ workplaceSearch }) => workplaceSearch, - }), - reducers: () => ({ + }, + reducers: { hasInitialized: [ false, { initializeAppData: () => true, }, ], - }), -}) as IKeaLogic; + }, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/assets/logo.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/assets/logo.svg deleted file mode 100644 index e6b987c398268..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/assets/logo.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index c0a51d5670a14..23e24e343937d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -10,8 +10,8 @@ import { useActions, useValues } from 'kea'; import { IInitialAppData } from '../../../common/types'; import { KibanaContext, IKibanaContext } from '../index'; -import { HttpLogic, IHttpLogicValues } from '../shared/http'; -import { AppLogic, IAppActions, IAppValues } from './app_logic'; +import { HttpLogic } from '../shared/http'; +import { AppLogic } from './app_logic'; import { Layout } from '../shared/layout'; import { WorkplaceSearchNav } from './components/layout/nav'; @@ -27,9 +27,9 @@ export const WorkplaceSearch: React.FC = (props) => { }; export const WorkplaceSearchConfigured: React.FC = (props) => { - const { hasInitialized } = useValues(AppLogic) as IAppValues; - const { initializeAppData } = useActions(AppLogic) as IAppActions; - const { errorConnecting } = useValues(HttpLogic) as IHttpLogicValues; + const { hasInitialized } = useValues(AppLogic); + const { initializeAppData } = useActions(AppLogic); + const { errorConnecting } = useValues(HttpLogic); useEffect(() => { if (!hasInitialized) initializeAppData(props); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/index.ts index e5169a51ce522..9e86993a5289d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { setMockValues, mockLogicValues, mockLogicActions } from './overview_logic.mock'; +export { setMockValues, mockValues, mockActions } from './overview_logic.mock'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts index 05715c648e5dc..9ce3021917a21 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts @@ -7,7 +7,7 @@ import { IOverviewValues } from '../overview_logic'; import { IAccount, IOrganization } from '../../../types'; -export const mockLogicValues = { +export const mockValues = { accountsCount: 0, activityFeed: [], canCreateContentSources: false, @@ -24,21 +24,21 @@ export const mockLogicValues = { dataLoading: true, } as IOverviewValues; -export const mockLogicActions = { +export const mockActions = { initializeOverview: jest.fn(() => ({})), }; jest.mock('kea', () => ({ ...(jest.requireActual('kea') as object), - useActions: jest.fn(() => ({ ...mockLogicActions })), - useValues: jest.fn(() => ({ ...mockLogicValues })), + useActions: jest.fn(() => ({ ...mockActions })), + useValues: jest.fn(() => ({ ...mockValues })), })); import { useValues } from 'kea'; export const setMockValues = (values: object) => { (useValues as jest.Mock).mockImplementationOnce(() => ({ - ...mockLogicValues, + ...mockValues, ...values, })); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index fa4decccb34b1..5598123f1c286 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -28,7 +28,7 @@ import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; import { ContentSection } from '../../components/shared/content_section'; -import { OverviewLogic, IOverviewValues } from './overview_logic'; +import { OverviewLogic } from './overview_logic'; import { OnboardingCard } from './onboarding_card'; @@ -68,7 +68,7 @@ export const OnboardingSteps: React.FC = () => { fpAccount: { isCurated }, organization: { name, defaultOrgName }, isFederatedAuth, - } = useValues(OverviewLogic) as IOverviewValues; + } = useValues(OverviewLogic); const accountsPath = !isFederatedAuth && (canCreateInvitations || isCurated) ? USERS_PATH : undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx index 53549cfcdbce7..4dc762e29deba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { ContentSection } from '../../components/shared/content_section'; import { ORG_SOURCES_PATH, USERS_PATH } from '../../routes'; -import { OverviewLogic, IOverviewValues } from './overview_logic'; +import { OverviewLogic } from './overview_logic'; import { StatisticCard } from './statistic_card'; @@ -25,7 +25,7 @@ export const OrganizationStats: React.FC = () => { accountsCount, personalSourcesCount, isFederatedAuth, - } = useValues(OverviewLogic) as IOverviewValues; + } = useValues(OverviewLogic); return ( { it('calls initialize function', async () => { mount(); - expect(mockLogicActions.initializeOverview).toHaveBeenCalled(); + expect(mockActions.initializeOverview).toHaveBeenCalled(); }); it('renders onboarding state', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx index 134fc9389694d..dbc007c2aa97d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx @@ -14,7 +14,7 @@ import { useActions, useValues } from 'kea'; import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; -import { OverviewLogic, IOverviewActions, IOverviewValues } from './overview_logic'; +import { OverviewLogic } from './overview_logic'; import { Loading } from '../../components/shared/loading'; import { ProductButton } from '../../components/shared/product_button'; @@ -44,7 +44,7 @@ const HEADER_DESCRIPTION = i18n.translate( ); export const Overview: React.FC = () => { - const { initializeOverview } = useActions(OverviewLogic) as IOverviewActions; + const { initializeOverview } = useActions(OverviewLogic); const { dataLoading, @@ -52,7 +52,7 @@ export const Overview: React.FC = () => { hasOrgSources, isOldAccount, organization: { name: orgName, defaultOrgName }, - } = useValues(OverviewLogic) as IOverviewValues; + } = useValues(OverviewLogic); useEffect(() => { initializeOverview(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts index 61108d7cb1f2f..6989635064ca9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts @@ -9,7 +9,7 @@ import { resetContext } from 'kea'; jest.mock('../../../shared/http', () => ({ HttpLogic: { values: { http: { get: jest.fn() } } } })); import { HttpLogic } from '../../../shared/http'; -import { mockLogicValues } from './__mocks__'; +import { mockValues } from './__mocks__'; import { OverviewLogic } from './overview_logic'; describe('OverviewLogic', () => { @@ -20,7 +20,7 @@ describe('OverviewLogic', () => { }); it('has expected default values', () => { - expect(OverviewLogic.values).toEqual(mockLogicValues); + expect(OverviewLogic.values).toEqual(mockValues); }); describe('setServerData', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts index 6606e5b55cb33..2c6846b6db7db 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kea } from 'kea'; +import { kea, MakeLogicType } from 'kea'; import { HttpLogic } from '../../../shared/http'; import { IAccount, IOrganization } from '../../types'; -import { IKeaLogic, TKeaReducers, IKeaParams } from '../../../shared/types'; import { IFeedActivity } from './recent_activity'; @@ -29,7 +28,7 @@ export interface IOverviewServerData { } export interface IOverviewActions { - setServerData(serverData: IOverviewServerData): void; + setServerData(serverData: IOverviewServerData): IOverviewServerData; initializeOverview(): void; } @@ -37,12 +36,12 @@ export interface IOverviewValues extends IOverviewServerData { dataLoading: boolean; } -export const OverviewLogic = kea({ - actions: (): IOverviewActions => ({ +export const OverviewLogic = kea>({ + actions: { setServerData: (serverData) => serverData, initializeOverview: () => null, - }), - reducers: (): TKeaReducers => ({ + }, + reducers: { organization: [ {} as IOrganization, { @@ -127,11 +126,11 @@ export const OverviewLogic = kea({ setServerData: () => false, }, ], - }), - listeners: ({ actions }): Partial => ({ + }, + listeners: ({ actions }) => ({ initializeOverview: async () => { const response = await HttpLogic.values.http.get('/api/workplace_search/overview'); actions.setServerData(response); }, }), -} as IKeaParams) as IKeaLogic; +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx index ada89c33be7e2..3c476be8d10e6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx @@ -17,7 +17,7 @@ import { sendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; import { getSourcePath } from '../../routes'; -import { OverviewLogic, IOverviewValues } from './overview_logic'; +import { OverviewLogic } from './overview_logic'; import './recent_activity.scss'; @@ -33,7 +33,7 @@ export const RecentActivity: React.FC = () => { const { organization: { name, defaultOrgName }, activityFeed, - } = useValues(OverviewLogic) as IOverviewValues; + } = useValues(OverviewLogic); return ( { + let initContext: ReturnType; + let coreSetup: ReturnType; + let coreStart: ReturnType; + let typeRegistry: ReturnType; + + beforeEach(() => { + initContext = coreMock.createPluginInitializerContext(); + coreSetup = coreMock.createSetup(); + coreStart = coreMock.createStart(); + typeRegistry = savedObjectsServiceMock.createTypeRegistryMock(); + typeRegistry.getVisibleTypes.mockReturnValue([ + { + name: 'foo', + hidden: false, + mappings: { properties: {} }, + namespaceType: 'single' as 'single', + }, + ]); + coreStart.savedObjects.getTypeRegistry.mockReturnValue(typeRegistry); + }); + it('returns OSS + registered features', async () => { const plugin = new Plugin(initContext); const { registerFeature } = await plugin.setup(coreSetup, {}); @@ -88,4 +95,12 @@ describe('Features Plugin', () => { expect(soTypes.includes('foo')).toBe(true); expect(soTypes.includes('bar')).toBe(false); }); + + it('registers a capabilities provider', async () => { + const plugin = new Plugin(initContext); + await plugin.setup(coreSetup, {}); + + expect(coreSetup.capabilities.registerProvider).toHaveBeenCalledTimes(1); + expect(coreSetup.capabilities.registerProvider).toHaveBeenCalledWith(expect.any(Function)); + }); }); diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts index 5783b20eae648..61b66d95ca44f 100644 --- a/x-pack/plugins/features/server/plugin.ts +++ b/x-pack/plugins/features/server/plugin.ts @@ -61,10 +61,15 @@ export class Plugin { featureRegistry: this.featureRegistry, }); + const getFeaturesUICapabilities = () => + uiCapabilitiesForFeatures(this.featureRegistry.getAll()); + + core.capabilities.registerProvider(getFeaturesUICapabilities); + return deepFreeze({ registerFeature: this.featureRegistry.register.bind(this.featureRegistry), getFeatures: this.featureRegistry.getAll.bind(this.featureRegistry), - getFeaturesUICapabilities: () => uiCapabilitiesForFeatures(this.featureRegistry.getAll()), + getFeaturesUICapabilities, }); } diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index 100122af943e1..1b783df1b7d02 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -21,7 +21,6 @@ import { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; -jest.mock('ui/new_platform'); jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); const waitForIndexPatternFetch = () => new Promise((r) => setTimeout(r)); diff --git a/x-pack/plugins/graph/public/state_management/mocks.ts b/x-pack/plugins/graph/public/state_management/mocks.ts index d32bc9a175a47..f28f51544a6ed 100644 --- a/x-pack/plugins/graph/public/state_management/mocks.ts +++ b/x-pack/plugins/graph/public/state_management/mocks.ts @@ -17,8 +17,6 @@ import { GraphStoreDependencies, createRootReducer, GraphStore, GraphState } fro import { Workspace, GraphWorkspaceSavedObject, IndexPatternSavedObject } from '../types'; import { IndexPattern } from '../../../../../src/plugins/data/public'; -jest.mock('ui/new_platform'); - export interface MockedGraphEnvironment { store: GraphStore; mockedDeps: jest.Mocked; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index 38dd49a286b58..39eb54b941ac4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -76,6 +76,10 @@ Array [ "value": "warm", "view": "Warm", }, + Object { + "value": "frozen", + "view": "Frozen", + }, Object { "value": "cold", "view": "Cold", diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap similarity index 70% rename from x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap rename to x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap index ad3e0956fcf25..cbb9f82888701 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap @@ -52,68 +52,60 @@ exports[`policy table should show empty state when there are not any policies 1`
-
+
+ +

+ Create your first index lifecycle policy +

+ class="euiText euiText--medium" + > +

+ An index lifecycle policy helps you manage your indices as they age. +

+
+ +
+
+ -
-
+ +
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js b/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.tsx similarity index 68% rename from x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js rename to x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.tsx index 60e3e9443bec9..d95b4503c266b 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.tsx @@ -4,54 +4,62 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment-timezone'; -import React from 'react'; -import { Provider } from 'react-redux'; -// axios has a $http like interface so using it to simulate $http -import axios from 'axios'; -import axiosXhrAdapter from 'axios/lib/adapters/xhr'; -import sinon from 'sinon'; +import React, { ReactElement } from 'react'; +import { ReactWrapper } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test'; -import { scopedHistoryMock } from '../../../../../src/core/public/mocks'; -import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; -import { fetchedPolicies } from '../../public/application/store/actions'; -import { indexLifecycleManagementStore } from '../../public/application/store'; -import { PolicyTable } from '../../public/application/sections/policy_table'; +import { + fatalErrorsServiceMock, + injectedMetadataServiceMock, + scopedHistoryMock, +} from '../../../../../src/core/public/mocks'; +import { HttpService } from '../../../../../src/core/public/http'; +import { usageCollectionPluginMock } from '../../../../../src/plugins/usage_collection/public/mocks'; + +import { PolicyTable } from '../../public/application/sections/policy_table/policy_table'; import { init as initHttp } from '../../public/application/services/http'; import { init as initUiMetric } from '../../public/application/services/ui_metric'; +import { PolicyFromES } from '../../public/application/services/policies/types'; -initHttp(axios.create({ adapter: axiosXhrAdapter }), (path) => path); -initUiMetric({ reportUiStats: () => {} }); - -let server = null; +initHttp( + new HttpService().setup({ + injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + fatalErrors: fatalErrorsServiceMock.createSetupContract(), + }) +); +initUiMetric(usageCollectionPluginMock.createSetupContract()); -let store = null; -const policies = []; +const policies: PolicyFromES[] = []; for (let i = 0; i < 105; i++) { policies.push({ version: i, - modified_date: moment().subtract(i, 'days').valueOf(), - linkedIndices: i % 2 === 0 ? [`index${i}`] : null, + modified_date: moment().subtract(i, 'days').toISOString(), + linkedIndices: i % 2 === 0 ? [`index${i}`] : undefined, name: `testy${i}`, + policy: { + name: `testy${i}`, + phases: {}, + }, }); } jest.mock(''); -let component = null; +let component: ReactElement; -const snapshot = (rendered) => { +const snapshot = (rendered: string[]) => { expect(rendered).toMatchSnapshot(); }; -const mountedSnapshot = (rendered) => { +const mountedSnapshot = (rendered: ReactWrapper) => { expect(takeMountedSnapshot(rendered)).toMatchSnapshot(); }; -const names = (rendered) => { +const names = (rendered: ReactWrapper) => { return findTestSubject(rendered, 'policyTablePolicyNameLink'); }; -const namesText = (rendered) => { - return names(rendered).map((button) => button.text()); +const namesText = (rendered: ReactWrapper): string[] => { + return (names(rendered) as ReactWrapper).map((button) => button.text()); }; -const testSort = (headerName) => { +const testSort = (headerName: string) => { const rendered = mountWithIntl(component); const nameHeader = findTestSubject(rendered, `policyTableHeaderCell-${headerName}`).find( 'button' @@ -63,7 +71,7 @@ const testSort = (headerName) => { rendered.update(); snapshot(namesText(rendered)); }; -const openContextMenu = (buttonIndex) => { +const openContextMenu = (buttonIndex: number) => { const rendered = mountWithIntl(component); const actionsButton = findTestSubject(rendered, 'policyActionsContextMenuButton'); actionsButton.at(buttonIndex).simulate('click'); @@ -73,33 +81,26 @@ const openContextMenu = (buttonIndex) => { describe('policy table', () => { beforeEach(() => { - store = indexLifecycleManagementStore(); component = ( - - {}} /> - + ); - store.dispatch(fetchedPolicies(policies)); - server = sinon.fakeServer.create(); - server.respondWith('/api/index_lifecycle_management/policies', [ - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify(policies), - ]); }); - test('should show spinner when policies are loading', () => { - store = indexLifecycleManagementStore(); + + test('should show empty state when there are not any policies', () => { component = ( - - {}} /> - + ); const rendered = mountWithIntl(component); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); - }); - test('should show empty state when there are not any policies', () => { - store.dispatch(fetchedPolicies([])); - const rendered = mountWithIntl(component); mountedSnapshot(rendered); }); test('should change pages when a pagination link is clicked on', () => { @@ -123,7 +124,7 @@ describe('policy table', () => { test('should filter based on content of search input', () => { const rendered = mountWithIntl(component); const searchInput = rendered.find('.euiFieldSearch').first(); - searchInput.instance().value = 'testy0'; + ((searchInput.instance() as unknown) as HTMLInputElement).value = 'testy0'; searchInput.simulate('keyup', { key: 'Enter', keyCode: 13, which: 13 }); rendered.update(); snapshot(namesText(rendered)); @@ -147,7 +148,7 @@ describe('policy table', () => { expect(buttons.at(0).text()).toBe('View indices linked to policy'); expect(buttons.at(1).text()).toBe('Add policy to index template'); expect(buttons.at(2).text()).toBe('Delete policy'); - expect(buttons.at(2).getDOMNode().disabled).toBeTruthy(); + expect((buttons.at(2).getDOMNode() as HTMLButtonElement).disabled).toBeTruthy(); }); test('should have proper actions in context menu when there are not linked indices', () => { const rendered = openContextMenu(1); @@ -155,7 +156,7 @@ describe('policy table', () => { expect(buttons.length).toBe(2); expect(buttons.at(0).text()).toBe('Add policy to index template'); expect(buttons.at(1).text()).toBe('Delete policy'); - expect(buttons.at(1).getDOMNode().disabled).toBeFalsy(); + expect((buttons.at(1).getDOMNode() as HTMLButtonElement).disabled).toBeFalsy(); }); test('confirmation modal should show when delete button is pressed', () => { const rendered = openContextMenu(1); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts index 3a19f03547b5b..fb626e7d7fe76 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts @@ -10,6 +10,7 @@ import { DeletePhase, HotPhase, WarmPhase, + FrozenPhase, } from '../services/policies/types'; export const defaultNewHotPhase: HotPhase = { @@ -47,6 +48,16 @@ export const defaultNewColdPhase: ColdPhase = { phaseIndexPriority: '0', }; +export const defaultNewFrozenPhase: FrozenPhase = { + phaseEnabled: false, + selectedMinimumAge: '0', + selectedMinimumAgeUnits: 'd', + selectedNodeAttrs: '', + selectedReplicaCount: '', + freezeEnabled: false, + phaseIndexPriority: '0', +}; + export const defaultNewDeletePhase: DeletePhase = { phaseEnabled: false, selectedMinimumAge: '0', diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx index 31a9abdc7145e..d7812f186a03f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -6,12 +6,10 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Provider } from 'react-redux'; import { I18nStart, ScopedHistory, ApplicationStart } from 'kibana/public'; import { UnmountCallback } from 'src/core/public'; import { App } from './app'; -import { indexLifecycleManagementStore } from './store'; export const renderApp = ( element: Element, @@ -22,9 +20,7 @@ export const renderApp = ( ): UnmountCallback => { render( - - - + , element ); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx index 11b743ecc4bb6..5128ba1c881a0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx @@ -12,7 +12,7 @@ import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from import { LearnMoreLink } from './learn_more_link'; import { ErrableFormRow } from './form_errors'; import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; -import { ColdPhase, DeletePhase, Phase, Phases, WarmPhase } from '../../../services/policies/types'; +import { PhaseWithMinAge, Phases } from '../../../services/policies/types'; function getTimingLabelForPhase(phase: keyof Phases) { // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. @@ -27,6 +27,11 @@ function getTimingLabelForPhase(phase: keyof Phases) { defaultMessage: 'Timing for cold phase', }); + case 'frozen': + return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseFrozen.minimumAgeLabel', { + defaultMessage: 'Timing for frozen phase', + }); + case 'delete': return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel', { defaultMessage: 'Timing for delete phase', @@ -63,7 +68,7 @@ function getUnitsAriaLabelForPhase(phase: keyof Phases) { } } -interface Props { +interface Props { rolloverEnabled: boolean; errors?: PhaseValidationErrors; phase: keyof Phases & string; @@ -72,7 +77,7 @@ interface Props { isShowingErrors: boolean; } -export const MinAgeInput = ({ +export const MinAgeInput = ({ rolloverEnabled, errors, phaseData, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx index 0ce2c0d7ea566..b4ff62bfb03dc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx @@ -20,7 +20,7 @@ import { LearnMoreLink } from './learn_more_link'; import { ErrableFormRow } from './form_errors'; import { useLoadNodes } from '../../../services/api'; import { NodeAttrsDetails } from './node_attrs_details'; -import { ColdPhase, Phase, Phases, WarmPhase } from '../../../services/policies/types'; +import { PhaseWithAllocationAction, Phases } from '../../../services/policies/types'; import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; const learnMoreLink = ( @@ -38,14 +38,14 @@ const learnMoreLink = ( ); -interface Props { +interface Props { phase: keyof Phases & string; errors?: PhaseValidationErrors; phaseData: T; setPhaseData: (dataKey: keyof T & string, value: string) => void; isShowingErrors: boolean; } -export const NodeAllocation = ({ +export const NodeAllocation = ({ phase, setPhaseData, errors, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx index 1da7508049f24..1505532a2b16e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx @@ -10,17 +10,17 @@ import { EuiFieldNumber, EuiTextColor, EuiDescribedFormGroup } from '@elastic/eu import { LearnMoreLink } from './'; import { OptionalLabel } from './'; import { ErrableFormRow } from './'; -import { ColdPhase, HotPhase, Phase, Phases, WarmPhase } from '../../../services/policies/types'; +import { PhaseWithIndexPriority, Phases } from '../../../services/policies/types'; import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; -interface Props { +interface Props { errors?: PhaseValidationErrors; phase: keyof Phases & string; phaseData: T; setPhaseData: (dataKey: keyof T & string, value: any) => void; isShowingErrors: boolean; } -export const SetPriorityInput = ({ +export const SetPriorityInput = ({ errors, phaseData, phase, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx index 359134e015f7f..f4697693b86c6 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useLoadPoliciesList } from '../../services/api'; @@ -50,25 +50,29 @@ export const EditPolicy: React.FunctionComponent +

+ +

} - color="danger" - > -

- {message} ({statusCode}) -

- - - - + body={ +

+ {message} ({statusCode}) +

+ } + actions={ + + + + } + /> ); } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index c99d01b546679..db58c64a8ae8c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -28,7 +28,7 @@ import { import { toasts } from '../../services/notification'; -import { Policy, PolicyFromES } from '../../services/policies/types'; +import { Phases, Policy, PolicyFromES } from '../../services/policies/types'; import { validatePolicy, ValidationErrors, @@ -42,7 +42,7 @@ import { } from '../../services/policies/policy_serialization'; import { ErrableFormRow, LearnMoreLink, PolicyJsonFlyout } from './components'; -import { ColdPhase, DeletePhase, HotPhase, WarmPhase } from './phases'; +import { ColdPhase, DeletePhase, FrozenPhase, HotPhase, WarmPhase } from './phases'; interface Props { policies: PolicyFromES[]; @@ -118,7 +118,7 @@ export const EditPolicy: React.FunctionComponent = ({ setIsShowingPolicyJsonFlyout(!isShowingPolicyJsonFlyout); }; - const setPhaseData = (phase: 'hot' | 'warm' | 'cold' | 'delete', key: string, value: any) => { + const setPhaseData = (phase: keyof Phases, key: string, value: any) => { setPolicy({ ...policy, phases: { @@ -303,6 +303,16 @@ export const EditPolicy: React.FunctionComponent = ({ + 0} + setPhaseData={(key, value) => setPhaseData('frozen', key, value)} + phaseData={policy.phases.frozen} + hotPhaseRolloverEnabled={policy.phases.hot.rolloverEnabled} + /> + + + 0} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx index fb32752fe24ea..9df6da7a88b2f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { ColdPhase as ColdPhaseInterface, Phases } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; import { LearnMoreLink, @@ -36,9 +36,8 @@ const freezeLabel = i18n.translate('xpack.indexLifecycleMgmt.coldPhase.freezeInd defaultMessage: 'Freeze index', }); -const coldProperty = propertyof('cold'); -const phaseProperty = (propertyName: keyof ColdPhaseInterface) => - propertyof(propertyName); +const coldProperty: keyof Phases = 'cold'; +const phaseProperty = (propertyName: keyof ColdPhaseInterface) => propertyName; interface Props { setPhaseData: (key: keyof ColdPhaseInterface & string, value: string | boolean) => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx index d3c73090f25f2..eab93777a72bd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui'; import { DeletePhase as DeletePhaseInterface, Phases } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; import { ActiveBadge, @@ -20,9 +20,8 @@ import { SnapshotPolicies, } from '../components'; -const deleteProperty = propertyof('delete'); -const phaseProperty = (propertyName: keyof DeletePhaseInterface) => - propertyof(propertyName); +const deleteProperty: keyof Phases = 'delete'; +const phaseProperty = (propertyName: keyof DeletePhaseInterface) => propertyName; interface Props { setPhaseData: (key: keyof DeletePhaseInterface & string, value: string | boolean) => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/frozen_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/frozen_phase.tsx new file mode 100644 index 0000000000000..782906a56a9ba --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/frozen_phase.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { PureComponent, Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiFieldNumber, + EuiDescribedFormGroup, + EuiSwitch, + EuiTextColor, +} from '@elastic/eui'; + +import { FrozenPhase as FrozenPhaseInterface, Phases } from '../../../services/policies/types'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; + +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + OptionalLabel, + ErrableFormRow, + MinAgeInput, + NodeAllocation, + SetPriorityInput, +} from '../components'; + +const freezeLabel = i18n.translate('xpack.indexLifecycleMgmt.frozenPhase.freezeIndexLabel', { + defaultMessage: 'Freeze index', +}); + +const frozenProperty: keyof Phases = 'frozen'; +const phaseProperty = (propertyName: keyof FrozenPhaseInterface) => propertyName; + +interface Props { + setPhaseData: (key: keyof FrozenPhaseInterface & string, value: string | boolean) => void; + phaseData: FrozenPhaseInterface; + isShowingErrors: boolean; + errors?: PhaseValidationErrors; + hotPhaseRolloverEnabled: boolean; +} +export class FrozenPhase extends PureComponent { + render() { + const { + setPhaseData, + phaseData, + errors, + isShowingErrors, + hotPhaseRolloverEnabled, + } = this.props; + + return ( +
+ +

+ +

{' '} + {phaseData.phaseEnabled && !isShowingErrors ? : null} + +
+ } + titleSize="s" + description={ + +

+ +

+ + } + id={`${frozenProperty}-${phaseProperty('phaseEnabled')}`} + checked={phaseData.phaseEnabled} + onChange={(e) => { + setPhaseData(phaseProperty('phaseEnabled'), e.target.checked); + }} + aria-controls="frozenPhaseContent" + /> +
+ } + fullWidth + > + + {phaseData.phaseEnabled ? ( + + + errors={errors} + phaseData={phaseData} + phase={frozenProperty} + isShowingErrors={isShowingErrors} + setPhaseData={setPhaseData} + rolloverEnabled={hotPhaseRolloverEnabled} + /> + + + + phase={frozenProperty} + setPhaseData={setPhaseData} + errors={errors} + phaseData={phaseData} + isShowingErrors={isShowingErrors} + /> + + + + + + + + } + isShowingErrors={isShowingErrors} + errors={errors?.freezeEnabled} + helpText={i18n.translate( + 'xpack.indexLifecycleMgmt.frozenPhase.replicaCountHelpText', + { + defaultMessage: 'By default, the number of replicas remains the same.', + } + )} + > + { + setPhaseData(phaseProperty('selectedReplicaCount'), e.target.value); + }} + min={0} + /> + + + + + ) : ( +
+ )} + + + {phaseData.phaseEnabled ? ( + + + +

+ } + description={ + + {' '} + + + } + fullWidth + titleSize="xs" + > + { + setPhaseData(phaseProperty('freezeEnabled'), e.target.checked); + }} + label={freezeLabel} + aria-label={freezeLabel} + /> + + + errors={errors} + phaseData={phaseData} + phase={frozenProperty} + isShowingErrors={isShowingErrors} + setPhaseData={setPhaseData} + /> + + ) : null} +

+ ); + } +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx index 22f0114d16afe..106e3b9139a9b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { HotPhase as HotPhaseInterface, Phases } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; import { LearnMoreLink, @@ -112,9 +112,8 @@ const maxAgeUnits = [ }), }, ]; -const hotProperty = propertyof('hot'); -const phaseProperty = (propertyName: keyof HotPhaseInterface) => - propertyof(propertyName); +const hotProperty: keyof Phases = 'hot'; +const phaseProperty = (propertyName: keyof HotPhaseInterface) => propertyName; interface Props { errors?: PhaseValidationErrors; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts index 8d1ace5950497..d59f2ff6413fd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts @@ -7,4 +7,5 @@ export { HotPhase } from './hot_phase'; export { WarmPhase } from './warm_phase'; export { ColdPhase } from './cold_phase'; +export { FrozenPhase } from './frozen_phase'; export { DeletePhase } from './delete_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx index f7b8c60a5c71f..2733d01ac222d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx @@ -30,7 +30,7 @@ import { } from '../components'; import { Phases, WarmPhase as WarmPhaseInterface } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; const shrinkLabel = i18n.translate('xpack.indexLifecycleMgmt.warmPhase.shrinkIndexLabel', { defaultMessage: 'Shrink index', @@ -47,9 +47,8 @@ const forcemergeLabel = i18n.translate('xpack.indexLifecycleMgmt.warmPhase.force defaultMessage: 'Force merge data', }); -const warmProperty = propertyof('warm'); -const phaseProperty = (propertyName: keyof WarmPhaseInterface) => - propertyof(propertyName); +const warmProperty: keyof Phases = 'warm'; +const phaseProperty = (propertyName: keyof WarmPhaseInterface) => propertyName; interface Props { setPhaseData: (key: keyof WarmPhaseInterface & string, value: boolean | string) => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/add_policy_to_template_confirm_modal.tsx similarity index 89% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/add_policy_to_template_confirm_modal.tsx index 47134ad097720..90ac3c03856de 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/add_policy_to_template_confirm_modal.tsx @@ -20,21 +20,35 @@ import { EuiText, } from '@elastic/eui'; -import { toasts } from '../../../../services/notification'; -import { addLifecyclePolicyToTemplate, loadIndexTemplates } from '../../../../services/api'; -import { showApiError } from '../../../../services/api_errors'; -import { LearnMoreLink } from '../../../edit_policy/components'; +import { LearnMoreLink } from '../../edit_policy/components'; +import { PolicyFromES } from '../../../services/policies/types'; +import { addLifecyclePolicyToTemplate, loadIndexTemplates } from '../../../services/api'; +import { toasts } from '../../../services/notification'; +import { showApiError } from '../../../services/api_errors'; -export class AddPolicyToTemplateConfirmModal extends Component { - state = { - templates: [], - }; +interface Props { + policy: PolicyFromES; + onCancel: () => void; +} +interface State { + templates: Array<{ name: string }>; + templateName?: string; + aliasName?: string; + templateError?: string; +} +export class AddPolicyToTemplateConfirmModal extends Component { + constructor(props: Props) { + super(props); + this.state = { + templates: [], + }; + } async componentDidMount() { const templates = await loadIndexTemplates(); this.setState({ templates }); } addPolicyToTemplate = async () => { - const { policy, callback, onCancel } = this.props; + const { policy, onCancel } = this.props; const { templateName, aliasName } = this.state; const policyName = policy.name; if (!templateName) { @@ -71,9 +85,6 @@ export class AddPolicyToTemplateConfirmModal extends Component { ); showApiError(e, title); } - if (callback) { - callback(); - } }; renderTemplateHasPolicyWarning() { const selectedTemplate = this.getSelectedTemplate(); @@ -144,7 +155,7 @@ export class AddPolicyToTemplateConfirmModal extends Component { options={options} value={templateName} onChange={(e) => { - this.setState({ templateError: null, templateName: e.target.value }); + this.setState({ templateError: undefined, templateName: e.target.value }); }} /> @@ -204,7 +215,6 @@ export class AddPolicyToTemplateConfirmModal extends Component { defaultMessage: 'Add policy', } )} - onClose={onCancel} >

diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/confirm_delete.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/confirm_delete.tsx similarity index 85% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/confirm_delete.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/confirm_delete.tsx index 0ecc9cc13ecd0..8d8e5ac2a2472 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/confirm_delete.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/confirm_delete.tsx @@ -9,11 +9,17 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; -import { toasts } from '../../../../services/notification'; -import { deletePolicy } from '../../../../services/api'; -import { showApiError } from '../../../../services/api_errors'; +import { PolicyFromES } from '../../../services/policies/types'; +import { toasts } from '../../../services/notification'; +import { showApiError } from '../../../services/api_errors'; +import { deletePolicy } from '../../../services/api'; -export class ConfirmDelete extends Component { +interface Props { + policyToDelete: PolicyFromES; + callback: () => void; + onCancel: () => void; +} +export class ConfirmDelete extends Component { deletePolicy = async () => { const { policyToDelete, callback } = this.props; const policyName = policyToDelete.name; @@ -61,7 +67,6 @@ export class ConfirmDelete extends Component { /> } buttonColor="danger" - onClose={onCancel} >

( -
- -
-); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.container.js deleted file mode 100644 index 8bd78774d2d55..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.container.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { connect } from 'react-redux'; - -import { - fetchPolicies, - policyFilterChanged, - policyPageChanged, - policyPageSizeChanged, - policySortChanged, -} from '../../../../store/actions'; - -import { - getPolicies, - getPageOfPolicies, - getPolicyPager, - getPolicyFilter, - getPolicySort, - isPolicyListLoaded, -} from '../../../../store/selectors'; - -import { PolicyTable as PresentationComponent } from './policy_table'; - -const mapDispatchToProps = (dispatch) => { - return { - policyFilterChanged: (filter) => { - dispatch(policyFilterChanged({ filter })); - }, - policyPageChanged: (pageNumber) => { - dispatch(policyPageChanged({ pageNumber })); - }, - policyPageSizeChanged: (pageSize) => { - dispatch(policyPageSizeChanged({ pageSize })); - }, - policySortChanged: (sortField, isSortAscending) => { - dispatch(policySortChanged({ sortField, isSortAscending })); - }, - fetchPolicies: (withIndices) => { - dispatch(fetchPolicies(withIndices)); - }, - }; -}; - -export const PolicyTable = connect( - (state) => ({ - totalNumberOfPolicies: getPolicies(state).length, - policies: getPageOfPolicies(state), - pager: getPolicyPager(state), - filter: getPolicyFilter(state), - sortField: getPolicySort(state).sortField, - isSortAscending: getPolicySort(state).isSortAscending, - policyListLoaded: isPolicyListLoaded(state), - }), - mapDispatchToProps -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js deleted file mode 100644 index ec1cdb987f4b3..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component, Fragment } from 'react'; -import moment from 'moment-timezone'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { - EuiButton, - EuiButtonEmpty, - EuiLink, - EuiEmptyPrompt, - EuiFieldSearch, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPopover, - EuiContextMenu, - EuiSpacer, - EuiTable, - EuiTableBody, - EuiTableHeader, - EuiTableHeaderCell, - EuiTablePagination, - EuiTableRow, - EuiTableRowCell, - EuiTitle, - EuiText, - EuiPageBody, - EuiPageContent, - EuiScreenReaderOnly, -} from '@elastic/eui'; - -import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; -import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { getIndexListUri } from '../../../../../../../index_management/public'; -import { UIM_EDIT_CLICK } from '../../../../constants/ui_metric'; -import { getPolicyPath } from '../../../../services/navigation'; -import { flattenPanelTree } from '../../../../services/flatten_panel_tree'; -import { trackUiMetric } from '../../../../services/ui_metric'; -import { NoMatch } from '../no_match'; -import { ConfirmDelete } from './confirm_delete'; -import { AddPolicyToTemplateConfirmModal } from './add_policy_to_template_confirm_modal'; - -const COLUMNS = { - name: { - label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.nameHeader', { - defaultMessage: 'Name', - }), - width: 200, - }, - linkedIndices: { - label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.linkedIndicesHeader', { - defaultMessage: 'Linked indices', - }), - width: 120, - }, - version: { - label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.versionHeader', { - defaultMessage: 'Version', - }), - width: 120, - }, - modified_date: { - label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.modifiedDateHeader', { - defaultMessage: 'Modified date', - }), - width: 200, - }, -}; - -export class PolicyTable extends Component { - constructor(props) { - super(props); - - this.state = { - selectedPoliciesMap: {}, - renderConfirmModal: null, - }; - } - componentDidMount() { - this.props.fetchPolicies(true); - } - renderEmpty() { - return ( - - - - } - body={ - -

- -

-
- } - actions={this.renderCreatePolicyButton()} - /> - ); - } - renderDeleteConfirmModal = () => { - const { policyToDelete } = this.state; - if (!policyToDelete) { - return null; - } - return ( - this.setState({ renderConfirmModal: null, policyToDelete: null })} - /> - ); - }; - renderAddPolicyToTemplateConfirmModal = () => { - const { policyToAddToTemplate } = this.state; - if (!policyToAddToTemplate) { - return null; - } - return ( - this.setState({ renderConfirmModal: null, policyToAddToTemplate: null })} - /> - ); - }; - handleDelete = () => { - this.props.fetchPolicies(true); - this.setState({ renderDeleteConfirmModal: null, policyToDelete: null }); - }; - onSort = (column) => { - const { sortField, isSortAscending, policySortChanged } = this.props; - const newIsSortAscending = sortField === column ? !isSortAscending : true; - policySortChanged(column, newIsSortAscending); - }; - - buildHeader() { - const { sortField, isSortAscending } = this.props; - const headers = Object.entries(COLUMNS).map(([fieldName, { label, width }]) => { - const isSorted = sortField === fieldName; - return ( - this.onSort(fieldName)} - isSorted={isSorted} - isSortAscending={isSortAscending} - data-test-subj={`policyTableHeaderCell-${fieldName}`} - className={'policyTable__header--' + fieldName} - width={width} - > - {label} - - ); - }); - headers.push( - - ); - return headers; - } - - buildRowCell(fieldName, value) { - if (fieldName === 'name') { - return ( - /* eslint-disable-next-line @elastic/eui/href-or-on-click */ - - trackUiMetric('click', UIM_EDIT_CLICK) - )} - > - {value} - - ); - } else if (fieldName === 'linkedIndices') { - return ( - - {value ? value.length : '0'} - - ); - } else if (fieldName === 'modified_date' && value) { - return moment(value).format('YYYY-MM-DD HH:mm:ss'); - } - return value; - } - renderCreatePolicyButton() { - return ( - - - - ); - } - renderConfirmModal() { - const { renderConfirmModal } = this.state; - if (renderConfirmModal) { - return renderConfirmModal(); - } else { - return null; - } - } - buildActionPanelTree(policy) { - const hasLinkedIndices = Boolean(policy.linkedIndices && policy.linkedIndices.length); - - const viewIndicesLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.viewIndicesButtonText', - { - defaultMessage: 'View indices linked to policy', - } - ); - const addPolicyToTemplateLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.addPolicyToTemplateButtonText', - { - defaultMessage: 'Add policy to index template', - } - ); - const deletePolicyLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonText', - { - defaultMessage: 'Delete policy', - } - ); - const deletePolicyTooltip = hasLinkedIndices - ? i18n.translate('xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonDisabledTooltip', { - defaultMessage: 'You cannot delete a policy that is being used by an index', - }) - : null; - const items = []; - if (hasLinkedIndices) { - items.push({ - name: viewIndicesLabel, - icon: 'list', - onClick: () => { - this.props.navigateToApp('management', { - path: `/data/index_management${getIndexListUri(`ilm.policy:${policy.name}`, true)}`, - }); - }, - }); - } - items.push({ - name: addPolicyToTemplateLabel, - icon: 'plusInCircle', - onClick: () => - this.setState({ - renderConfirmModal: this.renderAddPolicyToTemplateConfirmModal, - policyToAddToTemplate: policy, - }), - }); - items.push({ - name: deletePolicyLabel, - disabled: hasLinkedIndices, - icon: 'trash', - toolTipContent: deletePolicyTooltip, - onClick: () => - this.setState({ - renderConfirmModal: this.renderDeleteConfirmModal, - policyToDelete: policy, - }), - }); - const panelTree = { - id: 0, - title: i18n.translate('xpack.indexLifecycleMgmt.policyTable.policyActionsMenu.panelTitle', { - defaultMessage: 'Policy options', - }), - items, - }; - return flattenPanelTree(panelTree); - } - togglePolicyPopover = (policy) => { - if (this.isPolicyPopoverOpen(policy)) { - this.closePolicyPopover(policy); - } else { - this.openPolicyPopover(policy); - } - }; - isPolicyPopoverOpen = (policy) => { - return this.state.policyPopover === policy.name; - }; - closePolicyPopover = (policy) => { - if (this.isPolicyPopoverOpen(policy)) { - this.setState({ policyPopover: null }); - } - }; - openPolicyPopover = (policy) => { - this.setState({ policyPopover: policy.name }); - }; - buildRowCells(policy) { - const { name } = policy; - const cells = Object.entries(COLUMNS).map(([fieldName, { width }]) => { - const value = policy[fieldName]; - - if (fieldName === 'name') { - return ( - -
- {this.buildRowCell(fieldName, value)} -
- - ); - } - - return ( - - {this.buildRowCell(fieldName, value)} - - ); - }); - const button = ( - this.togglePolicyPopover(policy)} - color="primary" - > - {i18n.translate('xpack.indexLifecycleMgmt.policyTable.actionsButtonText', { - defaultMessage: 'Actions', - })} - - ); - cells.push( - - this.closePolicyPopover(policy)} - panelPaddingSize="none" - withTitle - anchorPosition="rightUp" - repositionOnScroll - > - - - - ); - return cells; - } - - buildRows() { - const { policies = [] } = this.props; - return policies.map((policy) => { - const { name } = policy; - return {this.buildRowCells(policy)}; - }); - } - - renderPager() { - const { pager, policyPageChanged, policyPageSizeChanged } = this.props; - return ( - - ); - } - - onItemSelectionChanged = (selectedPolicies) => { - this.setState({ selectedPolicies }); - }; - - render() { - const { - totalNumberOfPolicies, - policyFilterChanged, - filter, - policyListLoaded, - policies, - } = this.props; - const { selectedPoliciesMap } = this.state; - const numSelected = Object.keys(selectedPoliciesMap).length; - let content; - let tableContent; - if (totalNumberOfPolicies || !policyListLoaded) { - if (!policyListLoaded) { - tableContent = ; - } else if (totalNumberOfPolicies > 0) { - tableContent = ( - - - - - - - {this.buildHeader()} - {this.buildRows()} - - ); - } else { - tableContent = ; - } - content = ( - - - {numSelected > 0 ? ( - - this.setState({ showDeleteConfirmation: true })} - > - - - - ) : null} - - { - policyFilterChanged(event.target.value); - }} - data-test-subj="policyTableFilterInput" - placeholder={i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.systempoliciesSearchInputPlaceholder', - { - defaultMessage: 'Search', - } - )} - aria-label={i18n.translate( - 'xpack.indexLifecycleMgmt.policyTable.systempoliciesSearchInputAriaLabel', - { - defaultMessage: 'Search policies', - } - )} - /> - - - - {tableContent} - - ); - } else { - content = this.renderEmpty(); - } - - return ( - - -
- {this.renderConfirmModal()} - {totalNumberOfPolicies || !policyListLoaded ? ( - - - - -

- -

-
-
- {totalNumberOfPolicies ? ( - {this.renderCreatePolicyButton()} - ) : null} -
- - -

- -

-
-
- ) : null} - - {content} - - {totalNumberOfPolicies && totalNumberOfPolicies > 10 ? this.renderPager() : null} -
-
-
- ); - } -} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx new file mode 100644 index 0000000000000..da36ff4df98f5 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx @@ -0,0 +1,376 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ReactElement, useState, Fragment, ReactNode } from 'react'; +import { + EuiButtonEmpty, + EuiContextMenu, + EuiLink, + EuiPopover, + EuiScreenReaderOnly, + EuiSpacer, + EuiTable, + EuiTableBody, + EuiTableHeader, + EuiTableHeaderCell, + EuiTablePagination, + EuiTableRow, + EuiTableRowCell, + EuiText, + Pager, + EuiContextMenuPanelDescriptor, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; + +import moment from 'moment'; +import { ApplicationStart } from 'kibana/public'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { RouteComponentProps } from 'react-router-dom'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; +import { getIndexListUri } from '../../../../../../index_management/public'; +import { PolicyFromES } from '../../../services/policies/types'; +import { getPolicyPath } from '../../../services/navigation'; +import { sortTable } from '../../../services'; +import { trackUiMetric } from '../../../services/ui_metric'; + +import { UIM_EDIT_CLICK } from '../../../constants'; +import { AddPolicyToTemplateConfirmModal } from './add_policy_to_template_confirm_modal'; +import { ConfirmDelete } from './confirm_delete'; + +type PolicyProperty = Extract< + keyof PolicyFromES, + 'version' | 'name' | 'linkedIndices' | 'modified_date' +>; +const COLUMNS: Array<[PolicyProperty, { label: string; width: number }]> = [ + [ + 'name', + { + label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.nameHeader', { + defaultMessage: 'Name', + }), + width: 200, + }, + ], + [ + 'linkedIndices', + { + label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.linkedIndicesHeader', { + defaultMessage: 'Linked indices', + }), + width: 120, + }, + ], + [ + 'version', + { + label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.versionHeader', { + defaultMessage: 'Version', + }), + width: 120, + }, + ], + [ + 'modified_date', + { + label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.modifiedDateHeader', { + defaultMessage: 'Modified date', + }), + width: 200, + }, + ], +]; + +interface Props { + policies: PolicyFromES[]; + totalNumber: number; + navigateToApp: ApplicationStart['navigateToApp']; + setConfirmModal: (modal: ReactElement | null) => void; + handleDelete: () => void; + history: RouteComponentProps['history']; +} +export const TableContent: React.FunctionComponent = ({ + policies, + totalNumber, + navigateToApp, + setConfirmModal, + handleDelete, + history, +}) => { + const [popoverPolicy, setPopoverPolicy] = useState(); + const [sort, setSort] = useState<{ sortField: PolicyProperty; isSortAscending: boolean }>({ + sortField: 'name', + isSortAscending: true, + }); + const [pageSize, setPageSize] = useState(10); + const [currentPage, setCurrentPage] = useState(0); + + let sortedPolicies = sortTable(policies, sort.sortField, sort.isSortAscending); + const pager = new Pager(totalNumber, pageSize, currentPage); + const { firstItemIndex, lastItemIndex } = pager; + sortedPolicies = sortedPolicies.slice(firstItemIndex, lastItemIndex + 1); + + const isPolicyPopoverOpen = (policyName: string): boolean => popoverPolicy === policyName; + const closePolicyPopover = (): void => { + setPopoverPolicy(''); + }; + const openPolicyPopover = (policyName: string): void => { + setPopoverPolicy(policyName); + }; + const togglePolicyPopover = (policyName: string): void => { + if (isPolicyPopoverOpen(policyName)) { + closePolicyPopover(); + } else { + openPolicyPopover(policyName); + } + }; + + const onSort = (column: PolicyProperty) => { + const newIsSortAscending = sort.sortField === column ? !sort.isSortAscending : true; + setSort({ sortField: column, isSortAscending: newIsSortAscending }); + }; + + const headers = []; + COLUMNS.forEach(([fieldName, { label, width }]) => { + const isSorted = sort.sortField === fieldName; + headers.push( + onSort(fieldName)} + isSorted={isSorted} + isSortAscending={sort.isSortAscending} + data-test-subj={`policyTableHeaderCell-${fieldName}`} + className={'policyTable__header--' + fieldName} + width={width} + > + {label} + + ); + }); + headers.push( + + ); + + const buildActionPanelTree = (policy: PolicyFromES): EuiContextMenuPanelDescriptor[] => { + const hasLinkedIndices = Boolean(policy.linkedIndices && policy.linkedIndices.length); + + const viewIndicesLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.viewIndicesButtonText', + { + defaultMessage: 'View indices linked to policy', + } + ); + const addPolicyToTemplateLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.addPolicyToTemplateButtonText', + { + defaultMessage: 'Add policy to index template', + } + ); + const deletePolicyLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonText', + { + defaultMessage: 'Delete policy', + } + ); + const deletePolicyTooltip = hasLinkedIndices + ? i18n.translate('xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonDisabledTooltip', { + defaultMessage: 'You cannot delete a policy that is being used by an index', + }) + : null; + const items = []; + if (hasLinkedIndices) { + items.push({ + name: viewIndicesLabel, + icon: 'list', + onClick: () => { + navigateToApp('management', { + path: `/data/index_management${getIndexListUri(`ilm.policy:${policy.name}`, true)}`, + }); + }, + }); + } + items.push({ + name: addPolicyToTemplateLabel, + icon: 'plusInCircle', + onClick: () => { + setConfirmModal(renderAddPolicyToTemplateConfirmModal(policy)); + }, + }); + items.push({ + name: deletePolicyLabel, + disabled: hasLinkedIndices, + icon: 'trash', + toolTipContent: deletePolicyTooltip, + onClick: () => { + setConfirmModal(renderDeleteConfirmModal(policy)); + }, + }); + const panelTree = { + id: 0, + title: i18n.translate('xpack.indexLifecycleMgmt.policyTable.policyActionsMenu.panelTitle', { + defaultMessage: 'Policy options', + }), + items, + }; + return [panelTree]; + }; + + const renderRowCell = (fieldName: string, value: string | number | string[]): ReactNode => { + if (fieldName === 'name') { + return ( + + trackUiMetric(METRIC_TYPE.CLICK, UIM_EDIT_CLICK) + )} + > + {value} + + ); + } else if (fieldName === 'linkedIndices') { + return ( + + {value ? (value as string[]).length : '0'} + + ); + } else if (fieldName === 'modified_date' && value) { + return moment(value).format('YYYY-MM-DD HH:mm:ss'); + } + return value; + }; + + const renderRowCells = (policy: PolicyFromES): ReactElement[] => { + const { name } = policy; + const cells = []; + COLUMNS.forEach(([fieldName, { width }]) => { + const value: any = policy[fieldName]; + + if (fieldName === 'name') { + cells.push( + +
+ {renderRowCell(fieldName, value)} +
+ + ); + } else { + cells.push( + + {renderRowCell(fieldName, value)} + + ); + } + }); + const button = ( + togglePolicyPopover(policy.name)} + color="primary" + > + {i18n.translate('xpack.indexLifecycleMgmt.policyTable.actionsButtonText', { + defaultMessage: 'Actions', + })} + + ); + cells.push( + + + + + + ); + return cells; + }; + + const rows = sortedPolicies.map((policy) => { + const { name } = policy; + return {renderRowCells(policy)}; + }); + + const renderAddPolicyToTemplateConfirmModal = (policy: PolicyFromES): ReactElement => { + return ( + setConfirmModal(null)} /> + ); + }; + + const renderDeleteConfirmModal = (policy: PolicyFromES): ReactElement => { + return ( + { + setConfirmModal(null); + }} + /> + ); + }; + + const renderPager = (): ReactNode => { + return ( + + ); + }; + + return ( + + + + + + + + {headers} + {rows} + + + {policies.length > 10 ? renderPager() : null} + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/index.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx new file mode 100644 index 0000000000000..f6471ff2da4d3 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ApplicationStart } from 'kibana/public'; +import { RouteComponentProps } from 'react-router-dom'; +import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { PolicyTable as PresentationComponent } from './policy_table'; +import { useLoadPoliciesList } from '../../services/api'; + +interface Props { + navigateToApp: ApplicationStart['navigateToApp']; +} + +export const PolicyTable: React.FunctionComponent = ({ + navigateToApp, + history, +}) => { + const { data: policies, isLoading, error, sendRequest } = useLoadPoliciesList(true); + + if (isLoading) { + return ( + } + body={ + + } + /> + ); + } + if (error) { + const { statusCode, message } = error ? error : { statusCode: '', message: '' }; + return ( + + + + } + body={ +

+ {message} ({statusCode}) +

+ } + actions={ + + + + } + /> + ); + } + + return ( + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx new file mode 100644 index 0000000000000..048ab922a65b5 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, ReactElement, ReactNode, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiButton, + EuiEmptyPrompt, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiText, + EuiPageBody, + EuiPageContent, +} from '@elastic/eui'; +import { ApplicationStart } from 'kibana/public'; +import { RouteComponentProps } from 'react-router-dom'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { PolicyFromES } from '../../services/policies/types'; +import { filterItems } from '../../services'; +import { TableContent } from './components/table_content'; + +interface Props { + policies: PolicyFromES[]; + history: RouteComponentProps['history']; + navigateToApp: ApplicationStart['navigateToApp']; + updatePolicies: () => void; +} + +export const PolicyTable: React.FunctionComponent = ({ + policies, + history, + navigateToApp, + updatePolicies, +}) => { + const [confirmModal, setConfirmModal] = useState(); + const [filter, setFilter] = useState(''); + + const createPolicyButton = ( + + + + ); + + let content: ReactElement; + + if (policies.length > 0) { + const filteredPolicies = filterItems('name', filter, policies); + let tableContent: ReactElement; + if (filteredPolicies.length > 0) { + tableContent = ( + { + updatePolicies(); + setConfirmModal(null); + }} + history={history} + /> + ); + } else { + tableContent = ( + + ); + } + + content = ( + + + + { + setFilter(event.target.value); + }} + data-test-subj="policyTableFilterInput" + placeholder={i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.systempoliciesSearchInputPlaceholder', + { + defaultMessage: 'Search', + } + )} + aria-label={i18n.translate( + 'xpack.indexLifecycleMgmt.policyTable.systempoliciesSearchInputAriaLabel', + { + defaultMessage: 'Search policies', + } + )} + /> + + + + {tableContent} + + ); + } else { + return ( + + + + + + } + body={ + +

+ +

+
+ } + actions={createPolicyButton} + /> +
+
+ ); + } + + return ( + + + {confirmModal} + + + + +

+ +

+
+
+ {createPolicyButton} +
+ + +

+ +

+
+ + + {content} +
+
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.js b/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.js deleted file mode 100644 index dcc9036463b82..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const filterItems = (fields, filter = '', items = []) => { - const lowerFilter = filter.toLowerCase(); - return items.filter((item) => { - const actualFields = fields || Object.keys(item); - const indexOfMatch = actualFields.findIndex((field) => { - const normalizedField = String(item[field]).toLowerCase(); - return normalizedField.includes(lowerFilter); - }); - return indexOfMatch !== -1; - }); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.ts new file mode 100644 index 0000000000000..237ce567707bb --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/filter_items.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const filterItems = (field: keyof T, filter: string, items: T[] = []): T[] => { + const lowerFilter = filter.toLowerCase(); + return items.filter((item: T) => { + const normalizedValue = String(item[field]).toLowerCase(); + return normalizedValue.includes(lowerFilter); + }); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/flatten_panel_tree.js b/x-pack/plugins/index_lifecycle_management/public/application/services/flatten_panel_tree.js deleted file mode 100644 index 2bb3903a6ef45..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/flatten_panel_tree.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const flattenPanelTree = (tree, array = []) => { - array.push(tree); - - if (tree.items) { - tree.items.forEach((item) => { - if (item.panel) { - flattenPanelTree(item.panel, array); - item.panel = item.panel.id; - } - }); - } - - return array; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/index.js b/x-pack/plugins/index_lifecycle_management/public/application/services/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/services/index.js rename to x-pack/plugins/index_lifecycle_management/public/application/services/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts index 6cc43042ed4ff..7fa82a004b872 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts @@ -152,9 +152,9 @@ export const validateColdPhase = (phase: ColdPhase): PhaseValidationErrors { + const phase = { ...frozenPhaseInitialization }; + if (phaseSerialized === undefined || phaseSerialized === null) { + return phase; + } + + phase.phaseEnabled = true; + + if (phaseSerialized.min_age) { + const { size: minAge, units: minAgeUnits } = splitSizeAndUnits(phaseSerialized.min_age); + phase.selectedMinimumAge = minAge; + phase.selectedMinimumAgeUnits = minAgeUnits; + } + + if (phaseSerialized.actions) { + const actions = phaseSerialized.actions; + if (actions.allocate) { + const allocate = actions.allocate; + if (allocate.require) { + Object.entries(allocate.require).forEach((entry) => { + phase.selectedNodeAttrs = entry.join(':'); + }); + if (allocate.number_of_replicas) { + phase.selectedReplicaCount = allocate.number_of_replicas.toString(); + } + } + } + + if (actions.freeze) { + phase.freezeEnabled = true; + } + + if (actions.set_priority) { + phase.phaseIndexPriority = actions.set_priority.priority + ? actions.set_priority.priority.toString() + : ''; + } + } + + return phase; +}; + +export const frozenPhaseToES = ( + phase: FrozenPhase, + originalPhase?: SerializedFrozenPhase +): SerializedFrozenPhase => { + if (!originalPhase) { + originalPhase = { ...serializedPhaseInitialization }; + } + + const esPhase = { ...originalPhase }; + + if (isNumber(phase.selectedMinimumAge)) { + esPhase.min_age = `${phase.selectedMinimumAge}${phase.selectedMinimumAgeUnits}`; + } + + esPhase.actions = esPhase.actions ? { ...esPhase.actions } : {}; + + if (phase.selectedNodeAttrs) { + const [name, value] = phase.selectedNodeAttrs.split(':'); + esPhase.actions.allocate = esPhase.actions.allocate || ({} as AllocateAction); + esPhase.actions.allocate.require = { + [name]: value, + }; + } else { + if (esPhase.actions.allocate) { + // @ts-expect-error + delete esPhase.actions.allocate.require; + } + } + + if (isNumber(phase.selectedReplicaCount)) { + esPhase.actions.allocate = esPhase.actions.allocate || ({} as AllocateAction); + esPhase.actions.allocate.number_of_replicas = parseInt(phase.selectedReplicaCount, 10); + } else { + if (esPhase.actions.allocate) { + // @ts-expect-error + delete esPhase.actions.allocate.number_of_replicas; + } + } + + if ( + esPhase.actions.allocate && + !esPhase.actions.allocate.require && + !isNumber(esPhase.actions.allocate.number_of_replicas) && + isEmpty(esPhase.actions.allocate.include) && + isEmpty(esPhase.actions.allocate.exclude) + ) { + // remove allocate action if it does not define require or number of nodes + // and both include and exclude are empty objects (ES will fail to parse if we don't) + delete esPhase.actions.allocate; + } + + if (phase.freezeEnabled) { + esPhase.actions.freeze = {}; + } else { + delete esPhase.actions.freeze; + } + + if (isNumber(phase.phaseIndexPriority)) { + esPhase.actions.set_priority = { + priority: parseInt(phase.phaseIndexPriority, 10), + }; + } else { + delete esPhase.actions.set_priority; + } + + return esPhase; +}; + +export const validateFrozenPhase = (phase: FrozenPhase): PhaseValidationErrors => { + if (!phase.phaseEnabled) { + return {}; + } + + const phaseErrors = {} as PhaseValidationErrors; + + // index priority is optional, but if it's set, it needs to be a positive number + if (phase.phaseIndexPriority) { + if (!isNumber(phase.phaseIndexPriority)) { + phaseErrors.phaseIndexPriority = [numberRequiredMessage]; + } else if (parseInt(phase.phaseIndexPriority, 10) < 0) { + phaseErrors.phaseIndexPriority = [positiveNumberRequiredMessage]; + } + } + + // min age needs to be a positive number + if (!isNumber(phase.selectedMinimumAge)) { + phaseErrors.selectedMinimumAge = [numberRequiredMessage]; + } else if (parseInt(phase.selectedMinimumAge, 10) < 0) { + phaseErrors.selectedMinimumAge = [positiveNumberRequiredMessage]; + } + + return { ...phaseErrors }; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts index 3953521df1817..807a6fe8ec395 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts @@ -9,6 +9,7 @@ import { defaultNewDeletePhase, defaultNewHotPhase, defaultNewWarmPhase, + defaultNewFrozenPhase, serializedPhaseInitialization, } from '../../constants'; @@ -17,6 +18,7 @@ import { Policy, PolicyFromES, SerializedPolicy } from './types'; import { hotPhaseFromES, hotPhaseToES } from './hot_phase'; import { warmPhaseFromES, warmPhaseToES } from './warm_phase'; import { coldPhaseFromES, coldPhaseToES } from './cold_phase'; +import { frozenPhaseFromES, frozenPhaseToES } from './frozen_phase'; import { deletePhaseFromES, deletePhaseToES } from './delete_phase'; export const splitSizeAndUnits = (field: string): { size: string; units: string } => { @@ -53,6 +55,7 @@ export const initializeNewPolicy = (newPolicyName: string = ''): Policy => { hot: { ...defaultNewHotPhase }, warm: { ...defaultNewWarmPhase }, cold: { ...defaultNewColdPhase }, + frozen: { ...defaultNewFrozenPhase }, delete: { ...defaultNewDeletePhase }, }, }; @@ -70,6 +73,7 @@ export const deserializePolicy = (policy: PolicyFromES): Policy => { hot: hotPhaseFromES(phases.hot), warm: warmPhaseFromES(phases.warm), cold: coldPhaseFromES(phases.cold), + frozen: frozenPhaseFromES(phases.frozen), delete: deletePhaseFromES(phases.delete), }, }; @@ -94,6 +98,13 @@ export const serializePolicy = ( serializedPolicy.phases.cold = coldPhaseToES(policy.phases.cold, originalEsPolicy.phases.cold); } + if (policy.phases.frozen.phaseEnabled) { + serializedPolicy.phases.frozen = frozenPhaseToES( + policy.phases.frozen, + originalEsPolicy.phases.frozen + ); + } + if (policy.phases.delete.phaseEnabled) { serializedPolicy.phases.delete = deletePhaseToES( policy.phases.delete, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts index 545488be2cd5e..6fdbc4babd3f3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts @@ -9,7 +9,17 @@ import { validateHotPhase } from './hot_phase'; import { validateWarmPhase } from './warm_phase'; import { validateColdPhase } from './cold_phase'; import { validateDeletePhase } from './delete_phase'; -import { ColdPhase, DeletePhase, HotPhase, Phase, Policy, PolicyFromES, WarmPhase } from './types'; +import { validateFrozenPhase } from './frozen_phase'; + +import { + ColdPhase, + DeletePhase, + FrozenPhase, + HotPhase, + Policy, + PolicyFromES, + WarmPhase, +} from './types'; export const propertyof = (propertyName: keyof T & string) => propertyName; @@ -100,7 +110,7 @@ export const policyNameAlreadyUsedErrorMessage = i18n.translate( defaultMessage: 'That policy name is already used.', } ); -export type PhaseValidationErrors = { +export type PhaseValidationErrors = { [P in keyof Partial]: string[]; }; @@ -108,6 +118,7 @@ export interface ValidationErrors { hot: PhaseValidationErrors; warm: PhaseValidationErrors; cold: PhaseValidationErrors; + frozen: PhaseValidationErrors; delete: PhaseValidationErrors; policyName: string[]; } @@ -148,12 +159,14 @@ export const validatePolicy = ( const hotPhaseErrors = validateHotPhase(policy.phases.hot); const warmPhaseErrors = validateWarmPhase(policy.phases.warm); const coldPhaseErrors = validateColdPhase(policy.phases.cold); + const frozenPhaseErrors = validateFrozenPhase(policy.phases.frozen); const deletePhaseErrors = validateDeletePhase(policy.phases.delete); const isValid = policyNameErrors.length === 0 && Object.keys(hotPhaseErrors).length === 0 && Object.keys(warmPhaseErrors).length === 0 && Object.keys(coldPhaseErrors).length === 0 && + Object.keys(frozenPhaseErrors).length === 0 && Object.keys(deletePhaseErrors).length === 0; return [ isValid, @@ -162,6 +175,7 @@ export const validatePolicy = ( hot: hotPhaseErrors, warm: warmPhaseErrors, cold: coldPhaseErrors, + frozen: frozenPhaseErrors, delete: deletePhaseErrors, }, ]; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts index 2e2ed5b38bb87..c191f82cf05cc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts @@ -13,6 +13,7 @@ export interface Phases { hot?: SerializedHotPhase; warm?: SerializedWarmPhase; cold?: SerializedColdPhase; + frozen?: SerializedFrozenPhase; delete?: SerializedDeletePhase; } @@ -21,6 +22,7 @@ export interface PolicyFromES { name: string; policy: SerializedPolicy; version: number; + linkedIndices?: string[]; } export interface SerializedPhase { @@ -68,6 +70,16 @@ export interface SerializedColdPhase extends SerializedPhase { }; } +export interface SerializedFrozenPhase extends SerializedPhase { + actions: { + freeze?: {}; + allocate?: AllocateAction; + set_priority?: { + priority: number | null; + }; + }; +} + export interface SerializedDeletePhase extends SerializedPhase { actions: { wait_for_snapshot?: { @@ -94,47 +106,66 @@ export interface Policy { hot: HotPhase; warm: WarmPhase; cold: ColdPhase; + frozen: FrozenPhase; delete: DeletePhase; }; } -export interface Phase { +export interface CommonPhaseSettings { phaseEnabled: boolean; } -export interface HotPhase extends Phase { + +export interface PhaseWithMinAge { + selectedMinimumAge: string; + selectedMinimumAgeUnits: string; +} + +export interface PhaseWithAllocationAction { + selectedNodeAttrs: string; + selectedReplicaCount: string; +} + +export interface PhaseWithIndexPriority { + phaseIndexPriority: string; +} + +export interface HotPhase extends CommonPhaseSettings, PhaseWithIndexPriority { rolloverEnabled: boolean; selectedMaxSizeStored: string; selectedMaxSizeStoredUnits: string; selectedMaxDocuments: string; selectedMaxAge: string; selectedMaxAgeUnits: string; - phaseIndexPriority: string; } -export interface WarmPhase extends Phase { +export interface WarmPhase + extends CommonPhaseSettings, + PhaseWithMinAge, + PhaseWithAllocationAction, + PhaseWithIndexPriority { warmPhaseOnRollover: boolean; - selectedMinimumAge: string; - selectedMinimumAgeUnits: string; - selectedNodeAttrs: string; - selectedReplicaCount: string; shrinkEnabled: boolean; selectedPrimaryShardCount: string; forceMergeEnabled: boolean; selectedForceMergeSegments: string; - phaseIndexPriority: string; } -export interface ColdPhase extends Phase { - selectedMinimumAge: string; - selectedMinimumAgeUnits: string; - selectedNodeAttrs: string; - selectedReplicaCount: string; +export interface ColdPhase + extends CommonPhaseSettings, + PhaseWithMinAge, + PhaseWithAllocationAction, + PhaseWithIndexPriority { freezeEnabled: boolean; - phaseIndexPriority: string; } -export interface DeletePhase extends Phase { - selectedMinimumAge: string; - selectedMinimumAgeUnits: string; +export interface FrozenPhase + extends CommonPhaseSettings, + PhaseWithMinAge, + PhaseWithAllocationAction, + PhaseWithIndexPriority { + freezeEnabled: boolean; +} + +export interface DeletePhase extends CommonPhaseSettings, PhaseWithMinAge { waitForSnapshotPolicy: string; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.js b/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.js deleted file mode 100644 index 1b1446bb735c1..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { sortBy } from 'lodash'; - -const stringSort = (fieldName) => (item) => item[fieldName]; -const arraySort = (fieldName) => (item) => (item[fieldName] || []).length; - -const sorters = { - version: stringSort('version'), - name: stringSort('name'), - linkedIndices: arraySort('linkedIndices'), - modified_date: stringSort('modified_date'), -}; -export const sortTable = (array = [], sortField, isSortAscending) => { - const sorted = sortBy(array, sorters[sortField]); - return isSortAscending ? sorted : sorted.reverse(); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.ts new file mode 100644 index 0000000000000..6b41d671b673f --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/sort_table.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { sortBy } from 'lodash'; +import { PolicyFromES } from './policies/types'; + +export const sortTable = ( + array: PolicyFromES[] = [], + sortField: Extract, + isSortAscending: boolean +): PolicyFromES[] => { + let sorter; + if (sortField === 'linkedIndices') { + sorter = (item: PolicyFromES) => (item[sortField] || []).length; + } else { + sorter = (item: PolicyFromES) => item[sortField]; + } + const sorted = sortBy(array, sorter); + return isSortAscending ? sorted : sorted.reverse(); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts index 7c7c0b70c0eed..81eb1c8cad135 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts @@ -14,7 +14,6 @@ import { } from '../constants/'; import { getUiMetricsForPhases } from './ui_metric'; -jest.mock('ui/new_platform'); describe('getUiMetricsForPhases', () => { test('gets cold phase', () => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/policies.js b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/policies.js deleted file mode 100644 index d47136679604f..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/policies.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { createAction } from 'redux-actions'; - -import { showApiError } from '../../services/api_errors'; -import { loadPolicies } from '../../services/api'; - -export const fetchedPolicies = createAction('FETCHED_POLICIES'); -export const setSelectedPolicy = createAction('SET_SELECTED_POLICY'); -export const unsetSelectedPolicy = createAction('UNSET_SELECTED_POLICY'); -export const setSelectedPolicyName = createAction('SET_SELECTED_POLICY_NAME'); -export const setSaveAsNewPolicy = createAction('SET_SAVE_AS_NEW_POLICY'); -export const policySortChanged = createAction('POLICY_SORT_CHANGED'); -export const policyPageSizeChanged = createAction('POLICY_PAGE_SIZE_CHANGED'); -export const policyPageChanged = createAction('POLICY_PAGE_CHANGED'); -export const policySortDirectionChanged = createAction('POLICY_SORT_DIRECTION_CHANGED'); -export const policyFilterChanged = createAction('POLICY_FILTER_CHANGED'); - -export const fetchPolicies = (withIndices, callback) => async (dispatch) => { - let policies; - try { - policies = await loadPolicies(withIndices); - } catch (err) { - const title = i18n.translate('xpack.indexLifecycleMgmt.editPolicy.loadPolicyErrorMessage', { - defaultMessage: 'Error loading policies', - }); - showApiError(err, title); - return false; - } - - dispatch(fetchedPolicies(policies)); - if (policies.length === 0) { - dispatch(setSelectedPolicy()); - } - callback && callback(); - return policies; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/index.d.ts b/x-pack/plugins/index_lifecycle_management/public/application/store/index.d.ts deleted file mode 100644 index 8617a7045a5c3..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export declare const indexLifecycleManagementStore: any; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/index.js b/x-pack/plugins/index_lifecycle_management/public/application/store/index.js deleted file mode 100644 index 808eb489bf913..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { indexLifecycleManagementStore } from './store'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/reducers/index.js b/x-pack/plugins/index_lifecycle_management/public/application/store/reducers/index.js deleted file mode 100644 index 7fe7134f5f5db..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/reducers/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { combineReducers } from 'redux'; -import { policies } from './policies'; - -export const indexLifecycleManagement = combineReducers({ - policies, -}); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/reducers/policies.js b/x-pack/plugins/index_lifecycle_management/public/application/store/reducers/policies.js deleted file mode 100644 index ca9d59e295a29..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/reducers/policies.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { handleActions } from 'redux-actions'; -import { - fetchedPolicies, - policyFilterChanged, - policyPageChanged, - policyPageSizeChanged, - policySortChanged, -} from '../actions'; - -const defaultState = { - isLoading: false, - isLoaded: false, - originalPolicyName: undefined, - selectedPolicySet: false, - policies: [], - sort: { - sortField: 'name', - isSortAscending: true, - }, - pageSize: 10, - currentPage: 0, - filter: '', -}; - -export const policies = handleActions( - { - [fetchedPolicies](state, { payload: policies }) { - return { - ...state, - isLoading: false, - isLoaded: true, - policies, - }; - }, - [policyFilterChanged](state, action) { - const { filter } = action.payload; - return { - ...state, - filter, - currentPage: 0, - }; - }, - [policySortChanged](state, action) { - const { sortField, isSortAscending } = action.payload; - - return { - ...state, - sort: { - sortField, - isSortAscending, - }, - }; - }, - [policyPageChanged](state, action) { - const { pageNumber } = action.payload; - return { - ...state, - currentPage: pageNumber, - }; - }, - [policyPageSizeChanged](state, action) { - const { pageSize } = action.payload; - return { - ...state, - pageSize, - }; - }, - }, - defaultState -); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/index.js b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/index.js deleted file mode 100644 index fef79c7782bb0..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './policies'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/policies.js b/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/policies.js deleted file mode 100644 index e1c89314a2ec5..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/selectors/policies.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createSelector } from 'reselect'; -import { Pager } from '@elastic/eui'; - -import { filterItems, sortTable } from '../../services'; - -export const getPolicies = (state) => state.policies.policies; -export const getPolicyFilter = (state) => state.policies.filter; -export const getPolicySort = (state) => state.policies.sort; -export const getPolicyCurrentPage = (state) => state.policies.currentPage; -export const getPolicyPageSize = (state) => state.policies.pageSize; -export const isPolicyListLoaded = (state) => state.policies.isLoaded; - -const getFilteredPolicies = createSelector(getPolicies, getPolicyFilter, (policies, filter) => { - return filterItems(['name'], filter, policies); -}); -export const getTotalPolicies = createSelector(getFilteredPolicies, (filteredPolicies) => { - return filteredPolicies.length; -}); -export const getPolicyPager = createSelector( - getPolicyCurrentPage, - getPolicyPageSize, - getTotalPolicies, - (currentPage, pageSize, totalPolicies) => { - return new Pager(totalPolicies, pageSize, currentPage); - } -); -export const getPageOfPolicies = createSelector( - getFilteredPolicies, - getPolicySort, - getPolicyPager, - (filteredPolicies, sort, pager) => { - const sortedPolicies = sortTable(filteredPolicies, sort.sortField, sort.isSortAscending); - const { firstItemIndex, lastItemIndex } = pager; - return sortedPolicies.slice(firstItemIndex, lastItemIndex + 1); - } -); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/store.js b/x-pack/plugins/index_lifecycle_management/public/application/store/store.js deleted file mode 100644 index c5774a3da238a..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/store.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createStore, applyMiddleware, compose } from 'redux'; -import thunk from 'redux-thunk'; - -import { indexLifecycleManagement } from './reducers/'; - -export const indexLifecycleManagementStore = (initialState = {}) => { - const enhancers = [applyMiddleware(thunk)]; - - window.__REDUX_DEVTOOLS_EXTENSION__ && enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()); - return createStore(indexLifecycleManagement, initialState, compose(...enhancers)); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js index a1eac5264bb6a..8d01f4a4c200e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js @@ -176,6 +176,12 @@ export const ilmFilterExtension = (indices) => { defaultMessage: 'Warm', }), }, + { + value: 'frozen', + view: i18n.translate('xpack.indexLifecycleMgmt.indexMgmtFilter.frozenLabel', { + defaultMessage: 'Frozen', + }), + }, { value: 'cold', view: i18n.translate('xpack.indexLifecycleMgmt.indexMgmtFilter.coldLabel', { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts index 2d02802119e47..9b51164fd4c28 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts @@ -104,6 +104,23 @@ const coldPhaseSchema = schema.maybe( }) ); +const frozenPhaseSchema = schema.maybe( + schema.object({ + min_age: minAgeSchema, + actions: schema.object({ + set_priority: setPrioritySchema, + unfollow: unfollowSchema, + allocate: allocateSchema, + freeze: schema.maybe(schema.object({})), // Freeze has no options + searchable_snapshot: schema.maybe( + schema.object({ + snapshot_repository: schema.string(), + }) + ), + }), + }) +); + const deletePhaseSchema = schema.maybe( schema.object({ min_age: minAgeSchema, @@ -129,6 +146,7 @@ const bodySchema = schema.object({ hot: hotPhaseSchema, warm: warmPhaseSchema, cold: coldPhaseSchema, + frozen: frozenPhaseSchema, delete: deletePhaseSchema, }), }); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts index a112d73230b82..2a0585d61d6f6 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts @@ -12,11 +12,6 @@ import { ComponentTemplateDeserialized } from '../../shared_imports'; const { setup } = pageHelpers.componentTemplateDetails; -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - const COMPONENT_TEMPLATE: ComponentTemplateDeserialized = { name: 'comp-1', template: { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts index bd6ac27375836..9bf7df263ec26 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts @@ -14,11 +14,6 @@ import { API_BASE_PATH } from './helpers/constants'; const { setup } = pageHelpers.componentTemplateList; -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: ComponentTemplateListTestBed; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx index 6b5a848ce85d3..95575124b6abd 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx @@ -163,7 +163,7 @@ export const EditField = React.memo(({ form, field, allFields, exitEdit, updateF - {form.isSubmitted && form.isValid === false && ( + {form.isSubmitted && !form.isValid && ( <> {i18n.translate('xpack.idxMgmt.mappingsEditor.editFieldUpdateButtonLabel', { diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx index fcc9795617ebb..56f040fc59a7b 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx @@ -17,13 +17,13 @@ import { i18n } from '@kbn/i18n'; import { useForm, + useFormData, Form, getUseField, getFormRow, Field, Forms, JsonEditorField, - FormDataProvider, } from '../../../../shared_imports'; import { documentationService } from '../../../services/documentation'; import { schemas, nameConfig, nameConfigWithoutValidations } from '../template_form_schemas'; @@ -118,9 +118,7 @@ interface LogisticsForm { } interface LogisticsFormInternal extends LogisticsForm { - __internal__: { - addMeta: boolean; - }; + addMeta: boolean; } interface Props { @@ -133,14 +131,12 @@ interface Props { function formDeserializer(formData: LogisticsForm): LogisticsFormInternal { return { ...formData, - __internal__: { - addMeta: Boolean(formData._meta && Object.keys(formData._meta).length), - }, + addMeta: Boolean(formData._meta && Object.keys(formData._meta).length), }; } function formSerializer(formData: LogisticsFormInternal): LogisticsForm { - const { __internal__, ...rest } = formData; + const { addMeta, ...rest } = formData; return rest; } @@ -153,7 +149,18 @@ export const StepLogistics: React.FunctionComponent = React.memo( serializer: formSerializer, deserializer: formDeserializer, }); - const { subscribe, submit, isSubmitted, isValid: isFormValid, getErrors: getFormErrors } = form; + const { + submit, + isSubmitted, + isValid: isFormValid, + getErrors: getFormErrors, + getFormData, + } = form; + + const [{ addMeta }] = useFormData({ + form, + watch: 'addMeta', + }); /** * When the consumer call validate() on this step, we submit the form so it enters the "isSubmitted" state @@ -164,15 +171,12 @@ export const StepLogistics: React.FunctionComponent = React.memo( }, [submit]); useEffect(() => { - const subscription = subscribe(({ data, isValid }) => { - onChange({ - isValid, - validate, - getData: data.format, - }); + onChange({ + isValid: isFormValid, + getData: getFormData, + validate, }); - return subscription.unsubscribe; - }, [onChange, validate, subscribe]); + }, [onChange, isFormValid, validate, getFormData]); const { name, indexPatterns, dataStream, order, priority, version } = getFieldsMeta( documentationService.getEsDocsBase() @@ -296,34 +300,28 @@ export const StepLogistics: React.FunctionComponent = React.memo( defaultMessage="Use the _meta field to store any metadata you want." /> - + } > - - {({ '__internal__.addMeta': addMeta }) => { - return ( - addMeta && ( - - ) - ); - }} - + {addMeta && ( + + )} )} diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx index 537f421173358..3a03835e85970 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx @@ -192,8 +192,8 @@ export const TemplateForm = ({ wizardData: WizardContent ): TemplateDeserialized => { const outputTemplate = { - ...initialTemplate, ...wizardData.logistics, + _kbnMeta: initialTemplate._kbnMeta, composedOf: wizardData.components, template: { settings: wizardData.settings, diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx index 0d9ce57a64c84..c85126f08685e 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx @@ -125,6 +125,7 @@ export const schemas: Record = { { validator: indexPatternField(i18n), type: VALIDATION_TYPES.ARRAY_ITEM, + isBlocking: false, }, ], }, @@ -213,13 +214,11 @@ export const schemas: Record = { } }, }, - __internal__: { - addMeta: { - type: FIELD_TYPES.TOGGLE, - label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.addMetadataLabel', { - defaultMessage: 'Add metadata', - }), - }, + addMeta: { + type: FIELD_TYPES.TOGGLE, + label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.addMetadataLabel', { + defaultMessage: 'Add metadata', + }), }, }, }; diff --git a/x-pack/plugins/index_management/public/shared_imports.ts b/x-pack/plugins/index_management/public/shared_imports.ts index 16dcab18c3caf..f7f992a090501 100644 --- a/x-pack/plugins/index_management/public/shared_imports.ts +++ b/x-pack/plugins/index_management/public/shared_imports.ts @@ -13,7 +13,7 @@ export { Forms, extractQueryParams, GlobalFlyout, -} from '../../../../src/plugins/es_ui_shared/public/'; +} from '../../../../src/plugins/es_ui_shared/public'; export { FormSchema, @@ -21,6 +21,7 @@ export { VALIDATION_TYPES, FieldConfig, useForm, + useFormData, Form, getUseField, UseField, diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 7cd6383a9b2e5..51f91d7189db7 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -152,9 +152,8 @@ export class InfraServerPlugin { core.http.registerRouteHandlerContext( 'infra', (context, request): InfraRequestHandlerContext => { - const mlSystem = context.ml && plugins.ml?.mlSystemProvider(context.ml?.mlClient, request); - const mlAnomalyDetectors = - context.ml && plugins.ml?.anomalyDetectorsProvider(context.ml?.mlClient, request); + const mlSystem = plugins.ml?.mlSystemProvider(request); + const mlAnomalyDetectors = plugins.ml?.anomalyDetectorsProvider(request); const spaceId = plugins.spaces?.spacesService.getSpaceId(request) || 'default'; return { diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index ad739bf9ff844..46a1c65872d1b 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -11,3 +11,4 @@ export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml'; export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package'; export { decodeCloudId } from './decode_cloud_id'; export { isValidNamespace } from './is_valid_namespace'; +export { isDiffPathProtocol } from './is_diff_path_protocol'; diff --git a/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.test.ts b/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.test.ts new file mode 100644 index 0000000000000..c488d552d7676 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.test.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; + * you may not use this file except in compliance with the Elastic License. + */ +import { isDiffPathProtocol } from './is_diff_path_protocol'; + +describe('Ingest Manager - isDiffPathProtocol', () => { + it('returns true for different paths', () => { + expect( + isDiffPathProtocol([ + 'http://localhost:8888/abc', + 'http://localhost:8888/abc', + 'http://localhost:8888/efg', + ]) + ).toBe(true); + }); + it('returns true for different protocols', () => { + expect( + isDiffPathProtocol([ + 'http://localhost:8888/abc', + 'https://localhost:8888/abc', + 'http://localhost:8888/abc', + ]) + ).toBe(true); + }); + it('returns false for same paths and protocols and different host or port', () => { + expect( + isDiffPathProtocol([ + 'http://localhost:8888/abc', + 'http://localhost2:8888/abc', + 'http://localhost:8883/abc', + ]) + ).toBe(false); + }); + it('returns false for one url', () => { + expect(isDiffPathProtocol(['http://localhost:8888/abc'])).toBe(false); + }); +}); diff --git a/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.ts b/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.ts new file mode 100644 index 0000000000000..666e886d745b1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.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; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// validates an array of urls have the same path and protocol +export function isDiffPathProtocol(kibanaUrls: string[]) { + const urlCompare = new URL(kibanaUrls[0]); + const compareProtocol = urlCompare.protocol; + const comparePathname = urlCompare.pathname; + return kibanaUrls.some((v) => { + const url = new URL(v); + const protocol = url.protocol; + const pathname = url.pathname; + return compareProtocol !== protocol || comparePathname !== pathname; + }); +} diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts index cafd0f03f66e2..d62f4fbb023dc 100644 --- a/x-pack/plugins/ingest_manager/common/types/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/index.ts @@ -15,7 +15,7 @@ export interface IngestManagerConfigType { pollingRequestTimeout: number; maxConcurrentConnections: number; kibana: { - host?: string; + host?: string[] | string; ca_sha256?: string; }; elasticsearch: { diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts b/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts index c626c85d3fb24..263e10e9d34b1 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts @@ -60,6 +60,11 @@ export interface FullAgentPolicy { [key: string]: any; }; }; + fleet?: { + kibana: { + hosts: string[]; + }; + }; inputs: FullAgentPolicyInput[]; revision?: number; agent?: { diff --git a/x-pack/plugins/ingest_manager/common/types/models/settings.ts b/x-pack/plugins/ingest_manager/common/types/models/settings.ts index 98d99911f1b3f..f554f4b392ad6 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/settings.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/settings.ts @@ -5,10 +5,10 @@ */ import { SavedObjectAttributes } from 'src/core/public'; -interface BaseSettings { - agent_auto_upgrade?: boolean; - package_auto_upgrade?: boolean; - kibana_url?: string; +export interface BaseSettings { + agent_auto_upgrade: boolean; + package_auto_upgrade: boolean; + kibana_urls: string[]; kibana_ca_sha256?: string; has_seen_add_data_notice?: boolean; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx index 9a9557f77c40c..e0d843ad773b8 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx @@ -18,14 +18,14 @@ import { EuiFlyoutFooter, EuiForm, EuiFormRow, - EuiFieldText, EuiRadioGroup, EuiComboBox, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText } from '@elastic/eui'; -import { useInput, useComboInput, useCore, useGetSettings, sendPutSettings } from '../hooks'; +import { useComboInput, useCore, useGetSettings, sendPutSettings } from '../hooks'; import { useGetOutputs, sendPutOutput } from '../hooks/use_request/outputs'; +import { isDiffPathProtocol } from '../../../../common/'; const URL_REGEX = /^(https?):\/\/[^\s$.?#].[^\s]*$/gm; @@ -36,14 +36,28 @@ interface Props { function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { const [isLoading, setIsloading] = React.useState(false); const { notifications } = useCore(); - const kibanaUrlInput = useInput('', (value) => { - if (!value.match(URL_REGEX)) { + const kibanaUrlsInput = useComboInput([], (value) => { + if (value.length === 0) { + return [ + i18n.translate('xpack.ingestManager.settings.kibanaUrlEmptyError', { + defaultMessage: 'At least one URL is required', + }), + ]; + } + if (value.some((v) => !v.match(URL_REGEX))) { return [ i18n.translate('xpack.ingestManager.settings.kibanaUrlError', { defaultMessage: 'Invalid URL', }), ]; } + if (isDiffPathProtocol(value)) { + return [ + i18n.translate('xpack.ingestManager.settings.kibanaUrlDifferentPathOrProtocolError', { + defaultMessage: 'Protocol and path must be the same for each URL', + }), + ]; + } }); const elasticsearchUrlInput = useComboInput([], (value) => { if (value.some((v) => !v.match(URL_REGEX))) { @@ -58,7 +72,7 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { return { isLoading, onSubmit: async () => { - if (!kibanaUrlInput.validate() || !elasticsearchUrlInput.validate()) { + if (!kibanaUrlsInput.validate() || !elasticsearchUrlInput.validate()) { return; } @@ -74,7 +88,7 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { throw outputResponse.error; } const settingsResponse = await sendPutSettings({ - kibana_url: kibanaUrlInput.value, + kibana_urls: kibanaUrlsInput.value, }); if (settingsResponse.error) { throw settingsResponse.error; @@ -94,14 +108,13 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { } }, inputs: { - kibanaUrl: kibanaUrlInput, + kibanaUrls: kibanaUrlsInput, elasticsearchUrl: elasticsearchUrlInput, }, }; } export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { - const core = useCore(); const settingsRequest = useGetSettings(); const settings = settingsRequest?.data?.item; const outputsRequest = useGetOutputs(); @@ -117,9 +130,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { useEffect(() => { if (settings) { - inputs.kibanaUrl.setValue( - settings.kibana_url || `${window.location.origin}${core.http.basePath.get()}` - ); + inputs.kibanaUrls.setValue(settings.kibana_urls); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [settings]); @@ -220,9 +231,9 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { label={i18n.translate('xpack.ingestManager.settings.kibanaUrlLabel', { defaultMessage: 'Kibana URL', })} - {...inputs.kibanaUrl.formRowProps} + {...inputs.kibanaUrls.formRowProps} > - + diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx index 919bb49f69aae..5d485a6e21086 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx @@ -18,6 +18,7 @@ import { EuiFlyoutFooter, EuiButtonEmpty, EuiButton, + EuiCallOut, } from '@elastic/eui'; import { useGetOneAgentPolicyFull, useGetOneAgentPolicy, useCore } from '../../../hooks'; import { Loading } from '../../../components'; @@ -32,17 +33,28 @@ const FlyoutBody = styled(EuiFlyoutBody)` export const AgentPolicyYamlFlyout = memo<{ policyId: string; onClose: () => void }>( ({ policyId, onClose }) => { const core = useCore(); - const { isLoading: isLoadingYaml, data: yamlData } = useGetOneAgentPolicyFull(policyId); + const { isLoading: isLoadingYaml, data: yamlData, error } = useGetOneAgentPolicyFull(policyId); const { data: agentPolicyData } = useGetOneAgentPolicy(policyId); - - const body = - isLoadingYaml && !yamlData ? ( - - ) : ( - - {fullAgentPolicyToYaml(yamlData!.item)} - - ); + const body = isLoadingYaml ? ( + + ) : error ? ( + + } + color="danger" + iconType="alert" + > + {error.message} + + ) : ( + + {fullAgentPolicyToYaml(yamlData!.item)} + + ); const downloadLink = core.http.basePath.prepend( agentPolicyRouteService.getInfoFullDownloadPath(policyId) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx index b02893057c9c3..9307229cdc258 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx @@ -34,8 +34,11 @@ export const ManagedInstructions: React.FunctionComponent = ({ agentPolic const settings = useGetSettings(); const apiKey = useGetOneEnrollmentAPIKey(selectedAPIKeyId); - const kibanaUrl = - settings.data?.item?.kibana_url ?? `${window.location.origin}${core.http.basePath.get()}`; + const kibanaUrlsSettings = settings.data?.item?.kibana_urls; + const kibanaUrl = kibanaUrlsSettings + ? kibanaUrlsSettings[0] + : `${window.location.origin}${core.http.basePath.get()}`; + const kibanaCASha256 = settings.data?.item?.kibana_ca_sha256; const steps: EuiContainedStepProps[] = [ diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts index 962cddb2e411e..f7b923aebb48b 100644 --- a/x-pack/plugins/ingest_manager/server/index.ts +++ b/x-pack/plugins/ingest_manager/server/index.ts @@ -32,7 +32,12 @@ export const config = { pollingRequestTimeout: schema.number({ defaultValue: 60000 }), maxConcurrentConnections: schema.number({ defaultValue: 0 }), kibana: schema.object({ - host: schema.maybe(schema.string()), + host: schema.maybe( + schema.oneOf([ + schema.uri({ scheme: ['http', 'https'] }), + schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }), { minSize: 1 }), + ]) + ), ca_sha256: schema.maybe(schema.string()), }), elasticsearch: schema.object({ diff --git a/x-pack/plugins/ingest_manager/server/routes/install_script/index.ts b/x-pack/plugins/ingest_manager/server/routes/install_script/index.ts index c2a5d77a39eb1..c767d3e80d2b7 100644 --- a/x-pack/plugins/ingest_manager/server/routes/install_script/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/install_script/index.ts @@ -38,16 +38,16 @@ export const registerRoutes = ({ const http = appContextService.getHttpSetup(); const serverInfo = http.getServerInfo(); const basePath = http.basePath; - const kibanaUrl = - (await settingsService.getSettings(soClient)).kibana_url || + const kibanaUrls = (await settingsService.getSettings(soClient)).kibana_urls || [ url.format({ protocol: serverInfo.protocol, hostname: serverInfo.hostname, port: serverInfo.port, pathname: basePath.serverBasePath, - }); + }), + ]; - const script = getScript(request.params.osType, kibanaUrl); + const script = getScript(request.params.osType, kibanaUrls[0]); return response.ok({ body: script }); } diff --git a/x-pack/plugins/ingest_manager/server/routes/settings/index.ts b/x-pack/plugins/ingest_manager/server/routes/settings/index.ts index 56e666056e8d0..aabb85dadabc2 100644 --- a/x-pack/plugins/ingest_manager/server/routes/settings/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/settings/index.ts @@ -8,7 +8,7 @@ import { TypeOf } from '@kbn/config-schema'; import { PLUGIN_ID, SETTINGS_API_ROUTES } from '../../constants'; import { PutSettingsRequestSchema, GetSettingsRequestSchema } from '../../types'; -import { settingsService } from '../../services'; +import { settingsService, agentPolicyService, appContextService } from '../../services'; export const getSettingsHandler: RequestHandler = async (context, request, response) => { const soClient = context.core.savedObjects.client; @@ -40,8 +40,12 @@ export const putSettingsHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; + const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); try { const settings = await settingsService.saveSettings(soClient, request.body); + await agentPolicyService.bumpAllAgentPolicies(soClient, { + user: user || undefined, + }); const body = { success: true, item: settings, diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts index 1bbe3b71bf919..aff8e607622d4 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts @@ -23,6 +23,7 @@ import { migrateAgentPolicyToV7100, migrateEnrollmentApiKeysToV7100, migratePackagePolicyToV7100, + migrateSettingsToV7100, } from './migrations/to_v7_10_0'; /* @@ -43,11 +44,14 @@ const savedObjectTypes: { [key: string]: SavedObjectsType } = { properties: { agent_auto_upgrade: { type: 'keyword' }, package_auto_upgrade: { type: 'keyword' }, - kibana_url: { type: 'keyword' }, + kibana_urls: { type: 'keyword' }, kibana_ca_sha256: { type: 'keyword' }, has_seen_add_data_notice: { type: 'boolean', index: false }, }, }, + migrations: { + '7.10.0': migrateSettingsToV7100, + }, }, [AGENT_SAVED_OBJECT_TYPE]: { name: AGENT_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts b/x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts index b60903dbd2bd0..5e36ce46c099b 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts @@ -5,7 +5,14 @@ */ import { SavedObjectMigrationFn } from 'kibana/server'; -import { Agent, AgentEvent, AgentPolicy, PackagePolicy, EnrollmentAPIKey } from '../../types'; +import { + Agent, + AgentEvent, + AgentPolicy, + PackagePolicy, + EnrollmentAPIKey, + Settings, +} from '../../types'; export const migrateAgentToV7100: SavedObjectMigrationFn< Exclude & { @@ -72,3 +79,16 @@ export const migratePackagePolicyToV7100: SavedObjectMigrationFn< return packagePolicyDoc; }; + +export const migrateSettingsToV7100: SavedObjectMigrationFn< + Exclude & { + kibana_url: string; + }, + Settings +> = (settingsDoc) => { + settingsDoc.attributes.kibana_urls = [settingsDoc.attributes.kibana_url]; + // @ts-expect-error + delete settingsDoc.attributes.kibana_url; + + return settingsDoc; +}; diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts b/x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts index dc2a89c661ac3..d9dffa03b2290 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts @@ -19,6 +19,24 @@ function getSavedObjectMock(agentPolicyAttributes: any) { attributes: agentPolicyAttributes, }; }); + mock.find.mockImplementation(async (options) => { + return { + saved_objects: [ + { + id: '93f74c0-e876-11ea-b7d3-8b2acec6f75c', + attributes: { + kibana_urls: ['http://localhost:5603'], + }, + type: 'ingest_manager_settings', + score: 1, + references: [], + }, + ], + total: 1, + page: 1, + per_page: 1, + }; + }); return mock; } @@ -43,7 +61,7 @@ jest.mock('./output', () => { describe('agent policy', () => { describe('getFullAgentPolicy', () => { - it('should return a policy without monitoring if not monitoring is not enabled', async () => { + it('should return a policy without monitoring if monitoring is not enabled', async () => { const soClient = getSavedObjectMock({ revision: 1, }); @@ -61,6 +79,11 @@ describe('agent policy', () => { }, inputs: [], revision: 1, + fleet: { + kibana: { + hosts: ['http://localhost:5603'], + }, + }, agent: { monitoring: { enabled: false, @@ -90,6 +113,11 @@ describe('agent policy', () => { }, inputs: [], revision: 1, + fleet: { + kibana: { + hosts: ['http://localhost:5603'], + }, + }, agent: { monitoring: { use_output: 'default', @@ -120,6 +148,11 @@ describe('agent policy', () => { }, inputs: [], revision: 1, + fleet: { + kibana: { + hosts: ['http://localhost:5603'], + }, + }, agent: { monitoring: { use_output: 'default', diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts index 21bc7b021e83a..2c97bba0cac45 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { uniq } from 'lodash'; -import { SavedObjectsClientContract } from 'src/core/server'; +import { SavedObjectsClientContract, SavedObjectsBulkUpdateResponse } from 'src/core/server'; import { AuthenticatedUser } from '../../../security/server'; import { DEFAULT_AGENT_POLICY, @@ -25,6 +25,7 @@ import { listAgents } from './agents'; import { packagePolicyService } from './package_policy'; import { outputService } from './output'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; +import { getSettings } from './settings'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; @@ -260,6 +261,25 @@ class AgentPolicyService { ): Promise { return this._update(soClient, id, {}, options?.user); } + public async bumpAllAgentPolicies( + soClient: SavedObjectsClientContract, + options?: { user?: AuthenticatedUser } + ): Promise>> { + const currentPolicies = await soClient.find({ + type: SAVED_OBJECT_TYPE, + fields: ['revision'], + }); + const bumpedPolicies = currentPolicies.saved_objects.map((policy) => { + policy.attributes = { + ...policy.attributes, + revision: policy.attributes.revision + 1, + updated_at: new Date().toISOString(), + updated_by: options?.user ? options.user.username : 'system', + }; + return policy; + }); + return soClient.bulkUpdate(bumpedPolicies); + } public async assignPackagePolicies( soClient: SavedObjectsClientContract, @@ -370,6 +390,7 @@ class AgentPolicyService { options?: { standalone: boolean } ): Promise { let agentPolicy; + const standalone = options?.standalone; try { agentPolicy = await this.get(soClient, id); @@ -435,6 +456,22 @@ class AgentPolicyService { }), }; + // only add settings if not in standalone + if (!standalone) { + let settings; + try { + settings = await getSettings(soClient); + } catch (error) { + throw new Error('Default settings is not setup'); + } + if (!settings.kibana_urls) throw new Error('kibana_urls is missing'); + fullAgentPolicy.fleet = { + kibana: { + hosts: settings.kibana_urls, + }, + }; + } + return fullAgentPolicy; } } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts index 8d1b320c89ae6..cd0dd92131230 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts @@ -9,6 +9,7 @@ import { Agent, AgentAction, AgentActionSOAttributes } from '../../../common/typ import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { savedObjectToAgentAction } from './saved_objects'; import { appContextService } from '../app_context'; +import { nodeTypes } from '../../../../../../src/plugins/data/common'; export async function createAgentAction( soClient: SavedObjectsClientContract, @@ -29,9 +30,24 @@ export async function getAgentActionsForCheckin( soClient: SavedObjectsClientContract, agentId: string ): Promise { + const filter = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode( + 'not', + nodeTypes.function.buildNode( + 'is', + `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at`, + '*' + ) + ), + nodeTypes.function.buildNode( + 'is', + `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.agent_id`, + agentId + ), + ]); const res = await soClient.find({ type: AGENT_ACTION_SAVED_OBJECT_TYPE, - filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * and ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.agent_id:${agentId}`, + filter, }); return Promise.all( @@ -78,9 +94,26 @@ export async function getAgentActionByIds( } export async function getNewActionsSince(soClient: SavedObjectsClientContract, timestamp: string) { + const filter = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode( + 'not', + nodeTypes.function.buildNode( + 'is', + `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at`, + '*' + ) + ), + nodeTypes.function.buildNode( + 'range', + `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.created_at`, + { + gte: timestamp, + } + ), + ]); const res = await soClient.find({ type: AGENT_ACTION_SAVED_OBJECT_TYPE, - filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * AND ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.created_at >= "${timestamp}"`, + filter, }); return res.saved_objects.map(savedObjectToAgentAction); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts index 764564cfa49f5..44473bf2d8d79 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts @@ -5,62 +5,52 @@ */ import { validateAgentVersion } from './enroll'; -import { appContextService } from '../app_context'; -import { IngestManagerAppContext } from '../../plugin'; describe('validateAgentVersion', () => { it('should throw with agent > kibana version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0', - } as unknown) as IngestManagerAppContext); - expect(() => - validateAgentVersion({ - local: { elastic: { agent: { version: '8.8.0' } } }, - userProvided: {}, - }) - ).toThrowError(/Agent version is not compatible with kibana version/); + expect(() => validateAgentVersion('8.8.0', '8.0.0')).toThrowError('not compatible'); }); it('should work with agent < kibana version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ local: { elastic: { agent: { version: '7.8.0' } } }, userProvided: {} }); + validateAgentVersion('7.8.0', '8.0.0'); }); it('should work with agent = kibana version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ local: { elastic: { agent: { version: '8.0.0' } } }, userProvided: {} }); + validateAgentVersion('8.0.0', '8.0.0'); }); it('should work with SNAPSHOT version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0-SNAPSHOT', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ - local: { elastic: { agent: { version: '8.0.0-SNAPSHOT' } } }, - userProvided: {}, - }); + validateAgentVersion('8.0.0-SNAPSHOT', '8.0.0-SNAPSHOT'); }); it('should work with a agent using SNAPSHOT version', () => { - appContextService.start(({ - kibanaVersion: '7.8.0', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ - local: { elastic: { agent: { version: '7.8.0-SNAPSHOT' } } }, - userProvided: {}, - }); + validateAgentVersion('7.8.0-SNAPSHOT', '7.8.0'); }); it('should work with a kibana using SNAPSHOT version', () => { - appContextService.start(({ - kibanaVersion: '7.8.0-SNAPSHOT', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ - local: { elastic: { agent: { version: '7.8.0' } } }, - userProvided: {}, - }); + validateAgentVersion('7.8.0', '7.8.0-SNAPSHOT'); + }); + + it('very close versions, e.g. patch/prerelease - all combos should work', () => { + validateAgentVersion('7.9.1', '7.9.2'); + validateAgentVersion('7.8.1', '7.8.2'); + validateAgentVersion('7.6.99', '7.6.2'); + validateAgentVersion('7.6.2', '7.6.99'); + validateAgentVersion('5.94.3', '5.94.1234-SNAPSHOT'); + validateAgentVersion('5.94.3-SNAPSHOT', '5.94.1'); + }); + + it('somewhat close versions, minor release is 1 or 2 versions back and is older than the stack', () => { + validateAgentVersion('7.9.1', '7.10.2'); + validateAgentVersion('7.9.9', '7.11.1'); + validateAgentVersion('7.6.99', '7.6.2'); + validateAgentVersion('7.6.2', '7.6.99'); + expect(() => validateAgentVersion('5.94.3-SNAPSHOT', '5.93.1')).toThrowError('not compatible'); + expect(() => validateAgentVersion('5.94.3', '5.92.99-SNAPSHOT')).toThrowError('not compatible'); + }); + + it('versions where Agent is a minor version or major version greater (newer) than the stack should not work', () => { + expect(() => validateAgentVersion('7.10.1', '7.9.99')).toThrowError('not compatible'); + expect(() => validateAgentVersion('7.9.9', '6.11.1')).toThrowError('not compatible'); + expect(() => validateAgentVersion('5.94.3', '5.92.99-SNAPSHOT')).toThrowError('not compatible'); }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts index 606d5c4dcbb90..3c5850c722e97 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts @@ -20,7 +20,8 @@ export async function enroll( metadata?: { local: any; userProvided: any }, sharedId?: string ): Promise { - validateAgentVersion(metadata); + const agentVersion = metadata?.local?.elastic?.agent?.version; + validateAgentVersion(agentVersion); const existingAgent = sharedId ? await getAgentBySharedId(soClient, sharedId) : null; @@ -89,24 +90,50 @@ async function getAgentBySharedId(soClient: SavedObjectsClientContract, sharedId return null; } -export function validateAgentVersion(metadata?: { local: any; userProvided: any }) { - const kibanaVersion = semver.parse(appContextService.getKibanaVersion()); - if (!kibanaVersion) { - throw Boom.badRequest('Kibana version is not set'); - } - const version = semver.parse(metadata?.local?.elastic?.agent?.version); - if (!version) { - throw Boom.badRequest('Agent version not provided in metadata.'); +export function validateAgentVersion( + agentVersion: string, + kibanaVersion = appContextService.getKibanaVersion() +) { + const agentVersionParsed = semver.parse(agentVersion); + if (!agentVersionParsed) { + throw Boom.badRequest('Agent version not provided'); } - if (!version || !semver.lte(formatVersion(version), formatVersion(kibanaVersion))) { - throw Boom.badRequest('Agent version is not compatible with kibana version'); + const kibanaVersionParsed = semver.parse(kibanaVersion); + if (!kibanaVersionParsed) { + throw Boom.badRequest('Kibana version is not set or provided'); } -} -/** - * used to remove prelease from version as includePrerelease in not working as expected - */ -function formatVersion(version: semver.SemVer) { - return `${version.major}.${version.minor}.${version.patch}`; + const diff = semver.diff(agentVersion, kibanaVersion); + switch (diff) { + // section 1) very close versions, only patch release differences - all combos should work + // Agent a.b.1 < Kibana a.b.2 + // Agent a.b.2 > Kibana a.b.1 + case null: + case 'prerelease': + case 'prepatch': + case 'patch': + return; // OK + + // section 2) somewhat close versions, Agent minor release is 1 or 2 versions back and is older than the stack: + // Agent a.9.x < Kibana a.10.x + // Agent a.9.x < Kibana a.11.x + case 'preminor': + case 'minor': + if ( + agentVersionParsed.minor < kibanaVersionParsed.minor && + kibanaVersionParsed.minor - agentVersionParsed.minor <= 2 + ) + return; + + // section 3) versions where Agent is a minor version or major version greater (newer) than the stack should not work: + // Agent 7.10.x > Kibana 7.9.x + // Agent 8.0.x > Kibana 7.9.x + default: + if (semver.lte(agentVersionParsed, kibanaVersionParsed)) return; + else + throw Boom.badRequest( + `Agent version ${agentVersion} is not compatible with Kibana version ${kibanaVersion}` + ); + } } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts index d2a14fcf04dff..e9c8317a6251d 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts @@ -3,9 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { pkgToPkgKey } from './index'; const cache: Map = new Map(); export const cacheGet = (key: string) => cache.get(key); export const cacheSet = (key: string, value: Buffer) => cache.set(key, value); export const cacheHas = (key: string) => cache.has(key); -export const getCacheKey = (key: string) => key + '.tar.gz'; +export const cacheClear = () => cache.clear(); +export const cacheDelete = (key: string) => cache.delete(key); + +const archiveLocationCache: Map = new Map(); +export const getArchiveLocation = (name: string, version: string) => + archiveLocationCache.get(pkgToPkgKey({ name, version })); + +export const setArchiveLocation = (name: string, version: string, location: string) => + archiveLocationCache.set(pkgToPkgKey({ name, version }), location); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts index 1f708c5edbcc7..6d029b54a6317 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts @@ -5,6 +5,7 @@ */ import tar from 'tar'; +import yauzl from 'yauzl'; import { bufferToStream, streamToBuffer } from './streams'; export interface ArchiveEntry { @@ -30,3 +31,40 @@ export async function untarBuffer( deflatedStream.pipe(inflateStream); }); } + +export async function unzipBuffer( + buffer: Buffer, + filter = (entry: ArchiveEntry): boolean => true, + onEntry = (entry: ArchiveEntry): void => {} +): Promise { + const zipfile = await yauzlFromBuffer(buffer, { lazyEntries: true }); + zipfile.readEntry(); + zipfile.on('entry', async (entry: yauzl.Entry) => { + const path = entry.fileName; + if (!filter({ path })) return zipfile.readEntry(); + + const entryBuffer = await getZipReadStream(zipfile, entry).then(streamToBuffer); + onEntry({ buffer: entryBuffer, path }); + zipfile.readEntry(); + }); + return new Promise((resolve, reject) => zipfile.on('end', resolve).on('error', reject)); +} + +function yauzlFromBuffer(buffer: Buffer, opts: yauzl.Options): Promise { + return new Promise((resolve, reject) => + yauzl.fromBuffer(buffer, opts, (err?: Error, handle?: yauzl.ZipFile) => + err ? reject(err) : resolve(handle) + ) + ); +} + +function getZipReadStream( + zipfile: yauzl.ZipFile, + entry: yauzl.Entry +): Promise { + return new Promise((resolve, reject) => + zipfile.openReadStream(entry, (err?: Error, readStream?: NodeJS.ReadableStream) => + err ? reject(err) : resolve(readStream) + ) + ); +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts index 085dc990fa376..b40638eefbae2 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts @@ -5,7 +5,17 @@ */ import { AssetParts } from '../../../types'; -import { pathParts, splitPkgKey } from './index'; +import { getBufferExtractor, pathParts, splitPkgKey } from './index'; +import { getArchiveLocation } from './cache'; +import { untarBuffer, unzipBuffer } from './extract'; + +jest.mock('./cache', () => { + return { + getArchiveLocation: jest.fn(), + }; +}); + +const mockedGetArchiveLocation = getArchiveLocation as jest.Mock; const testPaths = [ { @@ -80,3 +90,21 @@ describe('splitPkgKey tests', () => { expect(pkgVersion).toBe('0.13.0-alpha.1+abcd'); }); }); + +describe('getBufferExtractor', () => { + it('throws if the archive has not been downloaded/cached yet', () => { + expect(() => getBufferExtractor('missing', '1.2.3')).toThrow('no archive location'); + }); + + it('returns unzipBuffer if the archive key ends in .zip', () => { + mockedGetArchiveLocation.mockImplementation(() => '.zip'); + const extractor = getBufferExtractor('will-use-mocked-key', 'a.b.c'); + expect(extractor).toBe(unzipBuffer); + }); + + it('returns untarBuffer if the key ends in anything else', () => { + mockedGetArchiveLocation.mockImplementation(() => 'xyz'); + const extractor = getBufferExtractor('will-use-mocked-key', 'a.b.c'); + expect(extractor).toBe(untarBuffer); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index b635378960468..61c8cd4aabb7b 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -17,8 +17,8 @@ import { RegistrySearchResults, RegistrySearchResult, } from '../../../types'; -import { cacheGet, cacheSet, getCacheKey, cacheHas } from './cache'; -import { ArchiveEntry, untarBuffer } from './extract'; +import { cacheGet, cacheSet, cacheHas, getArchiveLocation, setArchiveLocation } from './cache'; +import { ArchiveEntry, untarBuffer, unzipBuffer } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; import { getRegistryUrl } from './registry_url'; @@ -130,7 +130,9 @@ export async function getArchiveInfo( filter = (entry: ArchiveEntry): boolean => true ): Promise { const paths: string[] = []; - const onEntry = (entry: ArchiveEntry) => { + const archiveBuffer = await getOrFetchArchiveBuffer(pkgName, pkgVersion); + const bufferExtractor = getBufferExtractor(pkgName, pkgVersion); + await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => { const { path, buffer } = entry; const { file } = pathParts(path); if (!file) return; @@ -138,9 +140,7 @@ export async function getArchiveInfo( cacheSet(path, buffer); paths.push(path); } - }; - - await extract(pkgName, pkgVersion, filter, onEntry); + }); return paths; } @@ -175,24 +175,20 @@ export function pathParts(path: string): AssetParts { } as AssetParts; } -async function extract( - pkgName: string, - pkgVersion: string, - filter = (entry: ArchiveEntry): boolean => true, - onEntry: (entry: ArchiveEntry) => void -) { - const archiveBuffer = await getOrFetchArchiveBuffer(pkgName, pkgVersion); +export function getBufferExtractor(pkgName: string, pkgVersion: string) { + const archiveLocation = getArchiveLocation(pkgName, pkgVersion); + if (!archiveLocation) throw new Error(`no archive location for ${pkgName} ${pkgVersion}`); + const isZip = archiveLocation.endsWith('.zip'); + const bufferExtractor = isZip ? unzipBuffer : untarBuffer; - return untarBuffer(archiveBuffer, filter, onEntry); + return bufferExtractor; } async function getOrFetchArchiveBuffer(pkgName: string, pkgVersion: string): Promise { - // assume .tar.gz for now. add support for .zip if/when we need it - const key = getCacheKey(`${pkgName}-${pkgVersion}`); - let buffer = cacheGet(key); + const key = getArchiveLocation(pkgName, pkgVersion); + let buffer = key && cacheGet(key); if (!buffer) { buffer = await fetchArchiveBuffer(pkgName, pkgVersion); - cacheSet(key, buffer); } if (buffer) { @@ -203,16 +199,21 @@ async function getOrFetchArchiveBuffer(pkgName: string, pkgVersion: string): Pro } export async function ensureCachedArchiveInfo(name: string, version: string) { - const pkgkey = getCacheKey(`${name}-${version}`); - if (!cacheHas(pkgkey)) { + const pkgkey = getArchiveLocation(name, version); + if (!pkgkey || !cacheHas(pkgkey)) { await getArchiveInfo(name, version); } } async function fetchArchiveBuffer(pkgName: string, pkgVersion: string): Promise { const { download: archivePath } = await fetchInfo(pkgName, pkgVersion); - const registryUrl = getRegistryUrl(); - return getResponseStream(`${registryUrl}${archivePath}`).then(streamToBuffer); + const archiveUrl = `${getRegistryUrl()}${archivePath}`; + const buffer = await getResponseStream(archiveUrl).then(streamToBuffer); + + setArchiveLocation(pkgName, pkgVersion, archivePath); + cacheSet(archivePath, buffer); + + return buffer; } export function getAsset(key: string) { diff --git a/x-pack/plugins/ingest_manager/server/services/settings.ts b/x-pack/plugins/ingest_manager/server/services/settings.ts index f1c09746d9abd..25223fbc08535 100644 --- a/x-pack/plugins/ingest_manager/server/services/settings.ts +++ b/x-pack/plugins/ingest_manager/server/services/settings.ts @@ -5,7 +5,15 @@ */ import Boom from 'boom'; import { SavedObjectsClientContract } from 'kibana/server'; -import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, SettingsSOAttributes, Settings } from '../../common'; +import url from 'url'; +import { + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + SettingsSOAttributes, + Settings, + decodeCloudId, + BaseSettings, +} from '../../common'; +import { appContextService } from './app_context'; export async function getSettings(soClient: SavedObjectsClientContract): Promise { const res = await soClient.find({ @@ -25,7 +33,7 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise export async function saveSettings( soClient: SavedObjectsClientContract, newData: Partial> -): Promise { +): Promise & Pick> { try { const settings = await getSettings(soClient); @@ -41,10 +49,11 @@ export async function saveSettings( }; } catch (e) { if (e.isBoom && e.output.statusCode === 404) { - const res = await soClient.create( - GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, - newData - ); + const defaultSettings = createDefaultSettings(); + const res = await soClient.create(GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, { + ...defaultSettings, + ...newData, + }); return { id: res.id, @@ -55,3 +64,26 @@ export async function saveSettings( throw e; } } + +export function createDefaultSettings(): BaseSettings { + const http = appContextService.getHttpSetup(); + const serverInfo = http.getServerInfo(); + const basePath = http.basePath; + + const cloud = appContextService.getCloud(); + const cloudId = cloud?.isCloudEnabled && cloud.cloudId; + const cloudUrl = cloudId && decodeCloudId(cloudId)?.kibanaUrl; + const flagsUrl = appContextService.getConfig()?.fleet?.kibana?.host; + const defaultUrl = url.format({ + protocol: serverInfo.protocol, + hostname: serverInfo.hostname, + port: serverInfo.port, + pathname: basePath.serverBasePath, + }); + + return { + agent_auto_upgrade: true, + package_auto_upgrade: true, + kibana_urls: [cloudUrl || flagsUrl || defaultUrl].flat(), + }; +} diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index fd5d94a71d672..ec3a05a4fa390 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import url from 'url'; import uuid from 'uuid'; import { SavedObjectsClientContract } from 'src/core/server'; import { CallESAsCurrentUser } from '../types'; @@ -22,14 +21,13 @@ import { Installation, Output, DEFAULT_AGENT_POLICIES_PACKAGES, - decodeCloudId, } from '../../common'; import { getPackageInfo } from './epm/packages'; import { packagePolicyService } from './package_policy'; import { generateEnrollmentAPIKey } from './api_keys'; import { settingsService } from '.'; -import { appContextService } from './app_context'; import { awaitIfPending } from './setup_utils'; +import { createDefaultSettings } from './settings'; const FLEET_ENROLL_USERNAME = 'fleet_enroll'; const FLEET_ENROLL_ROLE = 'fleet_enroll'; @@ -58,26 +56,8 @@ async function createSetupSideEffects( ensureDefaultIndices(callCluster), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { - const http = appContextService.getHttpSetup(); - const serverInfo = http.getServerInfo(); - const basePath = http.basePath; - - const cloud = appContextService.getCloud(); - const cloudId = cloud?.isCloudEnabled && cloud.cloudId; - const cloudUrl = cloudId && decodeCloudId(cloudId)?.kibanaUrl; - const flagsUrl = appContextService.getConfig()?.fleet?.kibana?.host; - const defaultUrl = url.format({ - protocol: serverInfo.protocol, - hostname: serverInfo.hostname, - port: serverInfo.port, - pathname: basePath.serverBasePath, - }); - - return settingsService.saveSettings(soClient, { - agent_auto_upgrade: true, - package_auto_upgrade: true, - kibana_url: cloudUrl || flagsUrl || defaultUrl, - }); + const defaultSettings = createDefaultSettings(); + return settingsService.saveSettings(soClient, defaultSettings); } return Promise.reject(e); diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts index baee9f79d9317..35718491c9224 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; +import { isDiffPathProtocol } from '../../../common'; export const GetSettingsRequestSchema = {}; @@ -11,7 +12,15 @@ export const PutSettingsRequestSchema = { body: schema.object({ agent_auto_upgrade: schema.maybe(schema.boolean()), package_auto_upgrade: schema.maybe(schema.boolean()), - kibana_url: schema.maybe(schema.uri({ scheme: ['http', 'https'] })), + kibana_urls: schema.maybe( + schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }), { + validate: (value) => { + if (isDiffPathProtocol(value)) { + return 'Protocol and path must be the same for each URL'; + } + }, + }) + ), kibana_ca_sha256: schema.maybe(schema.string()), has_seen_add_data_notice: schema.maybe(schema.boolean()), }), diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts index 8d6a83a625651..c0acc39ca35a1 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts @@ -13,11 +13,6 @@ import { PipelineListTestBed } from './helpers/pipelines_list.helpers'; const { setup } = pageHelpers.pipelinesList; -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: PipelineListTestBed; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/constants.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/constants.ts new file mode 100644 index 0000000000000..e75ba56277c5c --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/constants.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Pipeline } from '../../../../../common/types'; +import { VerboseTestOutput, Document } from '../types'; + +export const PROCESSORS: Pick = { + processors: [ + { + set: { + field: 'field1', + value: 'value1', + }, + }, + ], +}; + +export const DOCUMENTS: Document[] = [ + { + _index: 'index', + _id: 'id1', + _source: { + name: 'foo', + }, + }, + { + _index: 'index', + _id: 'id2', + _source: { + name: 'bar', + }, + }, +]; + +export const SIMULATE_RESPONSE: VerboseTestOutput = { + docs: [ + { + processor_results: [ + { + processor_type: 'set', + status: 'success', + tag: 'some_tag', + doc: { + _index: 'index', + _id: 'id1', + _source: { + name: 'foo', + foo: 'bar', + }, + }, + }, + ], + }, + { + processor_results: [ + { + processor_type: 'set', + status: 'success', + tag: 'some_tag', + doc: { + _index: 'index', + _id: 'id2', + _source: { + name: 'bar', + foo: 'bar', + }, + }, + }, + ], + }, + ], +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/http_requests.helpers.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/http_requests.helpers.ts new file mode 100644 index 0000000000000..541a6853a99b3 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/http_requests.helpers.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import sinon, { SinonFakeServer } from 'sinon'; + +type HttpResponse = Record | any[]; + +// Register helpers to mock HTTP Requests +const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { + const setSimulatePipelineResponse = (response?: HttpResponse, error?: any) => { + const status = error ? error.status || 400 : 200; + const body = error ? JSON.stringify(error.body) : JSON.stringify(response); + + server.respondWith('POST', '/api/ingest_pipelines/simulate', [ + status, + { 'Content-Type': 'application/json' }, + body, + ]); + }; + + return { + setSimulatePipelineResponse, + }; +}; + +export const initHttpRequests = () => { + const server = sinon.fakeServer.create(); + + server.respondImmediately = true; + + // Define default response for unhandled requests. + // We make requests to APIs which don't impact the component under test, e.g. UI metric telemetry, + // and we can mock them all with a 200 instead of mocking each one individually. + server.respondWith([200, {}, 'DefaultSinonMockServerResponse']); + + const httpRequestsMockHelpers = registerHttpRequestMockHelpers(server); + + return { + server, + httpRequestsMockHelpers, + }; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx new file mode 100644 index 0000000000000..fec3259fa019b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { act } from 'react-dom/test-utils'; +import React from 'react'; +import axios from 'axios'; +import axiosXhrAdapter from 'axios/lib/adapters/xhr'; + +import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks'; + +import { LocationDescriptorObject } from 'history'; +import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; +/* eslint-disable @kbn/eslint/no-restricted-paths */ +import { usageCollectionPluginMock } from 'src/plugins/usage_collection/public/mocks'; + +import { registerTestBed, TestBed } from '../../../../../../../test_utils'; +import { stubWebWorker } from '../../../../../../../test_utils/stub_web_worker'; + +import { + breadcrumbService, + uiMetricService, + documentationService, + apiService, +} from '../../../services'; + +import { + ProcessorsEditorContextProvider, + Props, + GlobalOnFailureProcessorsEditor, + ProcessorsEditor, +} from '../'; +import { TestPipelineActions } from '../'; + +import { initHttpRequests } from './http_requests.helpers'; + +stubWebWorker(); + +jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => { + const original = jest.requireActual('../../../../../../../../src/plugins/kibana_react/public'); + return { + ...original, + // Mocking CodeEditor, which uses React Monaco under the hood + CodeEditor: (props: any) => ( + { + props.onChange(e.jsonContent); + }} + /> + ), + }; +}); + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + // Mocking EuiCodeEditor, which uses React Ace under the hood + EuiCodeEditor: (props: any) => ( + { + props.onChange(syntheticEvent.jsonString); + }} + /> + ), + }; +}); + +jest.mock('react-virtualized', () => { + const original = jest.requireActual('react-virtualized'); + + return { + ...original, + AutoSizer: ({ children }: { children: any }) => ( +
{children({ height: 500, width: 500 })}
+ ), + }; +}); + +const history = scopedHistoryMock.create(); +history.createHref.mockImplementation((location: LocationDescriptorObject) => { + return `${location.pathname}?${location.search}`; +}); + +const appServices = { + breadcrumbs: breadcrumbService, + metric: uiMetricService, + documentation: documentationService, + api: apiService, + notifications: notificationServiceMock.createSetupContract(), + history, + uiSettings: {}, +}; + +const testBedSetup = registerTestBed( + (props: Props) => ( + + + + + + + + ), + { + doMountAsync: false, + } +); + +export interface SetupResult extends TestBed { + actions: ReturnType; +} + +const createActions = (testBed: TestBed) => { + const { find, component, form } = testBed; + + return { + clickAddDocumentsButton() { + act(() => { + find('addDocumentsButton').simulate('click'); + }); + component.update(); + }, + + async clickViewOutputButton() { + await act(async () => { + find('viewOutputButton').simulate('click'); + }); + component.update(); + }, + + closeTestPipelineFlyout() { + act(() => { + find('euiFlyoutCloseButton').simulate('click'); + }); + component.update(); + }, + + clickProcessorOutputTab() { + act(() => { + find('outputTab').simulate('click'); + }); + component.update(); + }, + + async clickRefreshOutputButton() { + await act(async () => { + find('refreshOutputButton').simulate('click'); + }); + component.update(); + }, + + async clickRunPipelineButton() { + await act(async () => { + find('runPipelineButton').simulate('click'); + }); + component.update(); + }, + + async toggleVerboseSwitch() { + await act(async () => { + form.toggleEuiSwitch('verboseOutputToggle'); + }); + component.update(); + }, + + addDocumentsJson(jsonString: string) { + find('documentsEditor').simulate('change', { + jsonString, + }); + }, + + async clickProcessor(processorSelector: string) { + await act(async () => { + find(`${processorSelector}.manageItemButton`).simulate('click'); + }); + component.update(); + }, + }; +}; + +export const setup = async (props: Props): Promise => { + const testBed = await testBedSetup(props); + return { + ...testBed, + actions: createActions(testBed), + }; +}; + +const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); + +export const setupEnvironment = () => { + // Initialize mock services + uiMetricService.setup(usageCollectionPluginMock.createSetupContract()); + // @ts-ignore + apiService.setup(mockHttpClient, uiMetricService); + + const { server, httpRequestsMockHelpers } = initHttpRequests(); + + return { + server, + httpRequestsMockHelpers, + }; +}; + +type TestSubject = + | 'addDocumentsButton' + | 'testPipelineFlyout' + | 'documentsDropdown' + | 'outputTab' + | 'documentsEditor' + | 'runPipelineButton' + | 'documentsTabContent' + | 'outputTabContent' + | 'verboseOutputToggle' + | 'refreshOutputButton' + | 'viewOutputButton' + | 'pipelineExecutionError' + | 'euiFlyoutCloseButton' + | 'processorStatusIcon' + | 'documentsTab' + | 'manageItemButton' + | 'processorSettingsForm' + | 'configurationTab' + | 'outputTab' + | 'processorOutputTabContent' + | string; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx new file mode 100644 index 0000000000000..339c840bb86f1 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Pipeline } from '../../../../../common/types'; + +import { VerboseTestOutput, Document } from '../types'; +import { setup, SetupResult, setupEnvironment } from './test_pipeline.helpers'; +import { DOCUMENTS, SIMULATE_RESPONSE, PROCESSORS } from './constants'; + +interface ReqBody { + documents: Document[]; + verbose?: boolean; + pipeline: Pick; +} + +describe('Test pipeline', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + server.restore(); + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + testBed = await setup({ + value: { + ...PROCESSORS, + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + describe('Test pipeline actions', () => { + it('should successfully add sample documents and execute the pipeline', async () => { + const { find, actions, exists } = testBed; + + httpRequestsMockHelpers.setSimulatePipelineResponse(SIMULATE_RESPONSE); + + // Flyout and document dropdown should not be visible + expect(exists('testPipelineFlyout')).toBe(false); + expect(exists('documentsDropdown')).toBe(false); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Flyout should be visible with output tab initially disabled + expect(exists('testPipelineFlyout')).toBe(true); + expect(exists('documentsTabContent')).toBe(true); + expect(exists('outputTabContent')).toBe(false); + expect(find('outputTab').props().disabled).toEqual(true); + + // Add sample documents and click run + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + + // Verify request + const latestRequest = server.requests[server.requests.length - 1]; + const requestBody: ReqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body); + const { + documents: reqDocuments, + verbose: reqVerbose, + pipeline: { processors: reqProcessors }, + } = requestBody; + + expect(reqDocuments).toEqual(DOCUMENTS); + expect(reqVerbose).toEqual(true); + + // We programatically add a unique tag field when calling the simulate API + // We do not know this value in the test, so we simply check that the field exists + // and only verify the processor configuration + reqProcessors.forEach((processor, index) => { + Object.entries(processor).forEach(([key, value]) => { + const { tag, ...config } = value; + expect(tag).toBeDefined(); + expect(config).toEqual(PROCESSORS.processors[index][key]); + }); + }); + + // Verify output tab is active + expect(find('outputTab').props().disabled).toEqual(false); + expect(exists('documentsTabContent')).toBe(false); + expect(exists('outputTabContent')).toBe(true); + + // Click reload button and verify request + const totalRequests = server.requests.length; + await actions.clickRefreshOutputButton(); + expect(server.requests.length).toBe(totalRequests + 1); + expect(server.requests[server.requests.length - 1].url).toBe( + '/api/ingest_pipelines/simulate' + ); + + // Click verbose toggle and verify request + await actions.toggleVerboseSwitch(); + expect(server.requests.length).toBe(totalRequests + 2); + expect(server.requests[server.requests.length - 1].url).toBe( + '/api/ingest_pipelines/simulate' + ); + }); + + test('should enable the output tab if cached documents exist', async () => { + const { actions, exists } = testBed; + + httpRequestsMockHelpers.setSimulatePipelineResponse(SIMULATE_RESPONSE); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Add sample documents and click run + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + + // Close flyout + actions.closeTestPipelineFlyout(); + expect(exists('testPipelineFlyout')).toBe(false); + expect(exists('addDocumentsButton')).toBe(false); + expect(exists('documentsDropdown')).toBe(true); + + // Reopen flyout and verify output tab is enabled + await actions.clickViewOutputButton(); + expect(exists('testPipelineFlyout')).toBe(true); + expect(exists('documentsTabContent')).toBe(false); + expect(exists('outputTabContent')).toBe(true); + }); + + test('should surface API errors from the request', async () => { + const { actions, find, exists } = testBed; + + const error = { + status: 400, + error: 'Bad Request', + message: + '"[parse_exception] [_source] required property is missing, with { property_name="_source" }"', + }; + + httpRequestsMockHelpers.setSimulatePipelineResponse(undefined, { body: error }); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Add invalid sample documents array and run the pipeline + actions.addDocumentsJson(JSON.stringify([{}])); + await actions.clickRunPipelineButton(); + + // Verify error rendered + expect(exists('pipelineExecutionError')).toBe(true); + expect(find('pipelineExecutionError').text()).toContain(error.message); + }); + }); + + describe('Processors', () => { + // This is a hack + // We need to provide the processor id in the mocked output; + // this is generated dynamically and not something we can stub. + // As a workaround, the value is added as a data attribute in the UI + // and we retrieve it to generate the mocked output. + const addProcessorTagtoMockOutput = (output: VerboseTestOutput) => { + const { find } = testBed; + + const docs = output.docs.map((doc) => { + const results = doc.processor_results.map((result, index) => { + const tag = find(`processors>${index}`).props()['data-processor-id']; + return { + ...result, + tag, + }; + }); + return { processor_results: results }; + }); + return { docs }; + }; + + it('should show "inactive" processor status by default', async () => { + const { find } = testBed; + + const statusIconLabel = find('processors>0.processorStatusIcon').props()['aria-label']; + + expect(statusIconLabel).toEqual('Not run'); + }); + + it('should update the processor status after execution', async () => { + const { actions, find } = testBed; + + const mockVerboseOutputWithProcessorTag = addProcessorTagtoMockOutput(SIMULATE_RESPONSE); + httpRequestsMockHelpers.setSimulatePipelineResponse(mockVerboseOutputWithProcessorTag); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Add sample documents and click run + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + actions.closeTestPipelineFlyout(); + + // Verify status + const statusIconLabel = find('processors>0.processorStatusIcon').props()['aria-label']; + expect(statusIconLabel).toEqual('Success'); + }); + + describe('Output tab', () => { + beforeEach(async () => { + const { actions } = testBed; + + const mockVerboseOutputWithProcessorTag = addProcessorTagtoMockOutput(SIMULATE_RESPONSE); + httpRequestsMockHelpers.setSimulatePipelineResponse(mockVerboseOutputWithProcessorTag); + + // Add documents and run the pipeline + actions.clickAddDocumentsButton(); + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + actions.closeTestPipelineFlyout(); + }); + + it('should show the output of the processor', async () => { + const { actions, exists } = testBed; + + // Click processor to open manage flyout + await actions.clickProcessor('processors>0'); + // Verify flyout opened + expect(exists('processorSettingsForm')).toBe(true); + + // Navigate to "Output" tab + actions.clickProcessorOutputTab(); + // Verify content + expect(exists('processorOutputTabContent')).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx index e9aa5c1d56f73..e26b6a2890fe4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx @@ -62,6 +62,7 @@ export const DocumentsDropdown: FunctionComponent = ({ updateSelectedDocument(Number(e.target.value)); }} aria-label={i18nTexts.ariaLabel} + data-test-subj="documentsDropdown" /> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx index c081f69fd41fe..c30fdad969b24 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx @@ -91,7 +91,7 @@ export const ProcessorOutput: React.FunctionComponent = ({ } = processorOutput!; return ( - <> +

{i18nTexts.tabDescription}

@@ -212,6 +212,6 @@ export const ProcessorOutput: React.FunctionComponent = ({ )} - +
); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx index 4a67e27d2ebe6..bf69f817183ab 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx @@ -141,6 +141,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo( alignItems="center" justifyContent="spaceBetween" data-test-subj={selectorToDataTestSubject(selector)} + data-processor-id={processor.id} > diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx index 26ff113b97440..a58d482022b4d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx @@ -79,7 +79,13 @@ export const PipelineProcessorsItemStatus: FunctionComponent = ({ process return ( {label}

}> - +
); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx index 361e32c77d59b..6fd1adad54f84 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx @@ -37,7 +37,7 @@ export const TestOutputButton: FunctionComponent = ({ @@ -51,7 +51,7 @@ export const TestOutputButton: FunctionComponent = ({ {i18nTexts.buttonLabel} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx index e8bb1aa1d357f..b26c6f536366d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx @@ -182,6 +182,7 @@ export const TestPipelineFlyout: React.FunctionComponent = ({ } color="danger" iconType="alert" + data-test-subj="pipelineExecutionError" >

{testingError.message}

diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx index 8968416683c3e..dd12cdab0c934 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx @@ -72,7 +72,7 @@ export const DocumentsTab: React.FunctionComponent = ({ }); return ( - <> +

= ({ path="documents" component={JsonEditorField} componentProps={{ - ['data-test-subj']: 'documentsField', euiCodeEditorProps: { + 'data-test-subj': 'documentsEditor', height: '300px', 'aria-label': i18n.translate( 'xpack.ingestPipelines.testPipelineFlyout.documentsTab.editorFieldAriaLabel', @@ -128,6 +128,7 @@ export const DocumentsTab: React.FunctionComponent = ({ = ({ )} - +

); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx index 586fc9e60017a..926bab6da993c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx @@ -56,7 +56,7 @@ export const OutputTab: React.FunctionComponent = ({ } return ( - <> +

= ({ } checked={isVerboseEnabled} onChange={(e) => onEnableVerbose(e.target.checked)} + data-test-subj="verboseOutputToggle" /> @@ -88,6 +89,7 @@ export const OutputTab: React.FunctionComponent = ({ handleTestPipeline({ documents: cachedDocuments!, verbose: isVerboseEnabled }) } iconType="refresh" + data-test-subj="refreshOutputButton" > = ({ {content} - +

); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx index d0ea226e8db80..abfb86c2afda1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx @@ -50,7 +50,7 @@ export const Tabs: React.FunctionComponent = ({ isSelected={tab.id === selectedTab} key={tab.id} disabled={getIsDisabled(tab.id)} - data-test-subj={tab.id.toLowerCase() + '_tab'} + data-test-subj={tab.id.toLowerCase() + 'Tab'} > {tab.name} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts index 9b7c2069fcddd..a70c0d281cf95 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts @@ -4,71 +4,143 @@ * you may not use this file except in compliance with the Elastic License. */ -import { deserialize } from './deserialize'; +import { deserialize, deserializeVerboseTestOutput } from './deserialize'; -describe('deserialize', () => { - it('tolerates certain bad values correctly', () => { - expect( - deserialize({ +describe('Deserialization', () => { + describe('deserialize()', () => { + it('tolerates certain bad values correctly', () => { + expect( + deserialize({ + processors: [ + { set: { field: 'test', value: 123 } }, + { badType1: null } as any, + { badType2: 1 } as any, + ], + onFailure: [ + { + gsub: { + field: '_index', + pattern: '(.monitoring-\\w+-)6(-.+)', + replacement: '$17$2', + }, + }, + ], + }) + ).toEqual({ processors: [ - { set: { field: 'test', value: 123 } }, - { badType1: null } as any, - { badType2: 1 } as any, + { + id: expect.any(String), + type: 'set', + options: { + field: 'test', + value: 123, + }, + }, + { + id: expect.any(String), + onFailure: undefined, + type: 'badType1', + options: {}, + }, + { + id: expect.any(String), + onFailure: undefined, + type: 'badType2', + options: {}, + }, ], onFailure: [ { - gsub: { + id: expect.any(String), + type: 'gsub', + onFailure: undefined, + options: { field: '_index', pattern: '(.monitoring-\\w+-)6(-.+)', replacement: '$17$2', }, }, ], - }) - ).toEqual({ - processors: [ + }); + }); + + it('throws for unacceptable values', () => { + expect(() => { + deserialize({ + processors: [{ reallyBad: undefined } as any, 1 as any], + onFailure: [], + }); + }).toThrow('Invalid processor type'); + }); + }); + + describe('deserializeVerboseOutput()', () => { + it('deserializes the verbose output of a simulated pipeline', () => { + expect( + deserializeVerboseTestOutput({ + docs: [ + { + processor_results: [ + { + doc: { + _id: 'id1', + _source: { + name: 'foo', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', + tag: 'e457615c-69c9-4d14-9e85-c477ad96e60f', + }, + ], + }, + { + processor_results: [ + { + doc: { + _id: 'id2', + _source: { + name: 'baz', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', + tag: 'e457615c-69c9-4d14-9e85-c477ad96e60f', + }, + ], + }, + ], + }) + ).toEqual([ { - id: expect.any(String), - type: 'set', - options: { - field: 'test', - value: 123, + 'e457615c-69c9-4d14-9e85-c477ad96e60f': { + doc: { + _id: 'id1', + _source: { + name: 'foo', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', }, }, { - id: expect.any(String), - onFailure: undefined, - type: 'badType1', - options: {}, - }, - { - id: expect.any(String), - onFailure: undefined, - type: 'badType2', - options: {}, - }, - ], - onFailure: [ - { - id: expect.any(String), - type: 'gsub', - onFailure: undefined, - options: { - field: '_index', - pattern: '(.monitoring-\\w+-)6(-.+)', - replacement: '$17$2', + 'e457615c-69c9-4d14-9e85-c477ad96e60f': { + doc: { + _id: 'id2', + _source: { + name: 'baz', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', }, }, - ], + ]); }); }); - - it('throws for unacceptable values', () => { - expect(() => { - deserialize({ - processors: [{ reallyBad: undefined } as any, 1 as any], - onFailure: [], - }); - }).toThrow('Invalid processor type'); - }); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts index 9083985b0ff2e..5229f5eb0bb21 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts @@ -90,7 +90,7 @@ export type ProcessorStatus = export interface ProcessorResult { processor_type: string; status: ProcessorStatus; - doc: Document; + doc?: Document; tag: string; ignored_error?: any; error?: any; diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index 79986986b217d..4e813494d7d32 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -161,7 +161,7 @@ export function PieComponent( // to account for in outer labels // This does not handle non-dashboard embeddables, which are allowed to // have different backgrounds. - textColor: chartTheme.axes?.axisTitleStyle?.fill, + textColor: chartTheme.axes?.axisTitle?.fill, }, sectorLineStroke: chartTheme.lineSeriesStyle?.point?.fill, sectorLineWidth: 1.5, diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap index f0c233b44a285..79d3528eef4c4 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap @@ -20,23 +20,47 @@ exports[`xy_expression XYChart component it renders area 1`] = ` } /> @@ -151,23 +175,47 @@ exports[`xy_expression XYChart component it renders bar 1`] = ` } /> @@ -272,23 +320,47 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = ` } /> @@ -393,23 +465,47 @@ exports[`xy_expression XYChart component it renders line 1`] = ` } /> @@ -524,23 +620,47 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = ` } /> @@ -563,6 +683,11 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = ` ] } enableHistogramMode={false} + fit={ + Object { + "type": "none", + } + } groupId="left" id="d-a" key="0-0" @@ -606,6 +731,11 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = ` ] } enableHistogramMode={false} + fit={ + Object { + "type": "none", + } + } groupId="left" id="d-b" key="0-1" @@ -653,23 +783,47 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = ` } /> @@ -782,23 +936,47 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = } /> diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx index 31ba1bc83d970..e80bb22c04a69 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx @@ -107,7 +107,7 @@ describe('XY Config panels', () => { expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry'); }); - it('should disable the select if there is no unstacked area or line series', () => { + it('should disable the select if there is no area or line series', () => { const state = testState(); const component = shallow( { setState={jest.fn()} state={{ ...state, - layers: [ - { ...state.layers[0], seriesType: 'bar' }, - { ...state.layers[0], seriesType: 'area_stacked' }, - ], + layers: [{ ...state.layers[0], seriesType: 'bar' }], fittingFunction: 'Carry', }} /> diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index d64eb9451a50e..a2488d123e13a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -135,8 +135,8 @@ export function XyToolbar(props: VisualizationToolbarProps) { const { frame, state, setState } = props; const [open, setOpen] = useState(false); - const hasNonBarSeries = state?.layers.some( - (layer) => layer.seriesType === 'line' || layer.seriesType === 'area' + const hasNonBarSeries = state?.layers.some(({ seriesType }) => + ['area_stacked', 'area', 'line'].includes(seriesType) ); const [xAxisTitle, setXAxisTitle] = useState(state?.xTitle); @@ -261,8 +261,7 @@ export function XyToolbar(props: VisualizationToolbarProps) { content={ !hasNonBarSeries && i18n.translate('xpack.lens.xyChart.fittingDisabledHelpText', { - defaultMessage: - 'This setting only applies to line charts and unstacked area charts.', + defaultMessage: 'This setting only applies to line and area charts.', }) } > diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx index ba1ff6a1df030..c9c27193c437e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx @@ -1414,7 +1414,7 @@ describe('xy_expression', () => { expect(convertSpy).toHaveBeenCalledWith('I'); }); - test('it should not pass the formatter function to the x axis if the visibility of the tick labels is off', () => { + test('it should set the tickLabel visibility on the x axis if the tick labels is hidden', () => { const { data, args } = sampleArgs(); args.tickLabelsVisibilitySettings = { x: false, y: true, type: 'lens_xy_tickLabelsConfig' }; @@ -1432,15 +1432,94 @@ describe('xy_expression', () => { /> ); - const tickFormatter = instance.find(Axis).first().prop('tickFormat'); + const axisStyle = instance.find(Axis).first().prop('style'); - if (!tickFormatter) { - throw new Error('tickFormatter prop not found'); - } + expect(axisStyle).toMatchObject({ + tickLabel: { + visible: false, + }, + }); + }); - tickFormatter('I'); + test('it should set the tickLabel visibility on the y axis if the tick labels is hidden', () => { + const { data, args } = sampleArgs(); + + args.tickLabelsVisibilitySettings = { x: true, y: false, type: 'lens_xy_tickLabelsConfig' }; - expect(convertSpy).toHaveBeenCalledTimes(0); + const instance = shallow( + + ); + + const axisStyle = instance.find(Axis).at(1).prop('style'); + + expect(axisStyle).toMatchObject({ + tickLabel: { + visible: false, + }, + }); + }); + + test('it should set the tickLabel visibility on the x axis if the tick labels is shown', () => { + const { data, args } = sampleArgs(); + + args.tickLabelsVisibilitySettings = { x: true, y: true, type: 'lens_xy_tickLabelsConfig' }; + + const instance = shallow( + + ); + + const axisStyle = instance.find(Axis).first().prop('style'); + + expect(axisStyle).toMatchObject({ + tickLabel: { + visible: true, + }, + }); + }); + + test('it should set the tickLabel visibility on the y axis if the tick labels is shown', () => { + const { data, args } = sampleArgs(); + + args.tickLabelsVisibilitySettings = { x: false, y: true, type: 'lens_xy_tickLabelsConfig' }; + + const instance = shallow( + + ); + + const axisStyle = instance.find(Axis).at(1).prop('style'); + + expect(axisStyle).toMatchObject({ + tickLabel: { + visible: true, + }, + }); }); test('it should remove invalid rows', () => { @@ -1766,8 +1845,7 @@ describe('xy_expression', () => { expect(component.find(BarSeries).prop('fit')).toEqual(undefined); expect(component.find(AreaSeries).at(0).prop('fit')).toEqual({ type: Fit.Carry }); expect(component.find(AreaSeries).at(0).prop('stackAccessors')).toEqual([]); - // stacked area series doesn't get the fit prop - expect(component.find(AreaSeries).at(1).prop('fit')).toEqual(undefined); + expect(component.find(AreaSeries).at(1).prop('fit')).toEqual({ type: Fit.Carry }); expect(component.find(AreaSeries).at(1).prop('stackAccessors')).toEqual(['c']); }); @@ -1831,7 +1909,13 @@ describe('xy_expression', () => { /> ); - expect(component.find(Axis).at(0).prop('title')).toEqual(undefined); + const axisStyle = component.find(Axis).first().prop('style'); + + expect(axisStyle).toMatchObject({ + axisTitle: { + visible: false, + }, + }); }); test('it should show the X axis gridlines if the setting is on', () => { @@ -1852,7 +1936,9 @@ describe('xy_expression', () => { /> ); - expect(component.find(Axis).at(0).prop('showGridLines')).toBeTruthy(); + expect(component.find(Axis).at(0).prop('gridLine')).toMatchObject({ + visible: true, + }); }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx index 2037a3dbe6623..31873228fb394 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -420,11 +420,21 @@ export function XYChart({ xAxisFormatter.convert(d) : () => ''} + tickFormat={(d) => xAxisFormatter.convert(d)} + style={{ + tickLabel: { + visible: tickLabelsVisibilitySettings?.x, + }, + axisTitle: { + visible: showXAxisTitle, + }, + }} /> {yAxesConfiguration.map((axis, index) => ( @@ -433,10 +443,20 @@ export function XYChart({ id={axis.groupId} groupId={axis.groupId} position={axis.position} - title={showYAxisTitle ? getYAxesTitles(axis.series, index) : undefined} - showGridLines={gridlinesVisibilitySettings?.y} + title={getYAxesTitles(axis.series, index)} + gridLine={{ + visible: gridlinesVisibilitySettings?.y, + }} hide={filteredLayers[0].hide} - tickFormat={tickLabelsVisibilitySettings?.y ? (d) => axis.formatter.convert(d) : () => ''} + tickFormat={(d) => axis.formatter.convert(d)} + style={{ + tickLabel: { + visible: tickLabelsVisibilitySettings?.y, + }, + axisTitle: { + visible: showYAxisTitle, + }, + }} /> ))} @@ -529,7 +549,9 @@ export function XYChart({ case 'bar_horizontal_stacked': return ; case 'area_stacked': - return ; + return ( + + ); case 'area': return ( diff --git a/x-pack/plugins/licensing/public/services/feature_usage_service.mock.ts b/x-pack/plugins/licensing/public/services/feature_usage_service.mock.ts index fc9d4f9381151..b2390ea35c140 100644 --- a/x-pack/plugins/licensing/public/services/feature_usage_service.mock.ts +++ b/x-pack/plugins/licensing/public/services/feature_usage_service.mock.ts @@ -15,6 +15,8 @@ const createSetupMock = (): jest.Mocked => { register: jest.fn(), }; + mock.register.mockImplementation(() => Promise.resolve()); + return mock; }; @@ -23,6 +25,8 @@ const createStartMock = (): jest.Mocked => { notifyUsage: jest.fn(), }; + mock.notifyUsage.mockImplementation(() => Promise.resolve()); + return mock; }; diff --git a/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts b/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts index 39f7aa6503b35..943516dc0409a 100644 --- a/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts +++ b/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts @@ -19,12 +19,22 @@ describe('FeatureUsageService', () => { describe('#setup', () => { describe('#register', () => { - it('throws when registering the same feature twice', () => { + it('does not throw when registering the same feature twice with the same license', () => { const setup = service.setup(); setup.register('foo', 'basic'); expect(() => { setup.register('foo', 'basic'); - }).toThrowErrorMatchingInlineSnapshot(`"Feature 'foo' has already been registered."`); + }).not.toThrow(); + }); + + it('throws when registering the same feature again with a different license', () => { + const setup = service.setup(); + setup.register('foo', 'basic'); + expect(() => { + setup.register('foo', 'enterprise'); + }).toThrowErrorMatchingInlineSnapshot( + `"Feature 'foo' has already been registered with another license type. (current: basic, new: enterprise)"` + ); }); }); }); diff --git a/x-pack/plugins/licensing/server/services/feature_usage_service.ts b/x-pack/plugins/licensing/server/services/feature_usage_service.ts index 9bfcb28f36b2a..75a0c7fa78108 100644 --- a/x-pack/plugins/licensing/server/services/feature_usage_service.ts +++ b/x-pack/plugins/licensing/server/services/feature_usage_service.ts @@ -43,14 +43,20 @@ export class FeatureUsageService { public setup(): FeatureUsageServiceSetup { return { register: (featureName, licenseType) => { - if (this.lastUsages.has(featureName)) { - throw new Error(`Feature '${featureName}' has already been registered.`); + const registered = this.lastUsages.get(featureName); + if (registered) { + if (registered.licenseType !== licenseType) { + throw new Error( + `Feature '${featureName}' has already been registered with another license type. (current: ${registered.licenseType}, new: ${licenseType})` + ); + } + } else { + this.lastUsages.set(featureName, { + name: featureName, + lastUsed: null, + licenseType, + }); } - this.lastUsages.set(featureName, { - name: featureName, - lastUsed: null, - licenseType, - }); }, }; } diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/trusted_app_list_item_agnostic.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/trusted_app_list_item_agnostic.json new file mode 100644 index 0000000000000..9f0c306a408f0 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/trusted_app_list_item_agnostic.json @@ -0,0 +1,18 @@ +{ + "list_id": "endpoint_trusted_apps", + "item_id": "endpoint_trusted_apps_item", + "_tags": ["endpoint", "os:linux", "os:windows", "os:macos", "trusted-app"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample agnostic endpoint trusted app entry", + "name": "Sample Endpoint Trusted App Entry", + "namespace_type": "agnostic", + "entries": [ + { + "field": "actingProcess.file.signer", + "operator": "included", + "type": "match", + "value": "Elastic, N.V." + } + ] +} diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 363122ac62212..a4f20caedfc9b 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -33,6 +33,12 @@ export const MAP_PATH = 'map'; export const GIS_API_PATH = `api/${APP_ID}`; export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`; export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`; +export const API_ROOT_PATH = `/${GIS_API_PATH}`; + +export const MVT_GETTILE_API_PATH = 'mvt/getTile'; +export const MVT_SOURCE_LAYER_NAME = 'source_layer'; +export const KBN_TOO_MANY_FEATURES_PROPERTY = '__kbn_too_many_features__'; +export const KBN_TOO_MANY_FEATURES_IMAGE_ID = '__kbn_too_many_features_image_id__'; const MAP_BASE_URL = `/${MAPS_APP_PATH}/${MAP_PATH}`; export function getNewMapPath() { @@ -220,6 +226,7 @@ export enum SCALING_TYPES { LIMIT = 'LIMIT', CLUSTERS = 'CLUSTERS', TOP_HITS = 'TOP_HITS', + MVT = 'MVT', } export const RGBA_0000 = 'rgba(0,0,0,0)'; diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index cd7d2d5d0f461..f3521cca2e456 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -18,7 +18,6 @@ export type MapFilters = { refreshTimerLastTriggeredAt?: string; timeFilters: TimeRange; zoom: number; - geogridPrecision?: number; }; type ESSearchSourceSyncMeta = { diff --git a/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts b/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts index 44250360e9d00..e57efca94d95e 100644 --- a/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts +++ b/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { FeatureCollection, GeoJsonProperties } from 'geojson'; import { MapExtent } from './descriptor_types'; +import { ES_GEO_FIELD_TYPE } from './constants'; export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent; @@ -13,3 +15,11 @@ export function turfBboxToBounds(turfBbox: unknown): MapExtent; export function clampToLatBounds(lat: number): number; export function clampToLonBounds(lon: number): number; + +export function hitsToGeoJson( + hits: Array>, + flattenHit: (elasticSearchHit: Record) => GeoJsonProperties, + geoFieldName: string, + geoFieldType: ES_GEO_FIELD_TYPE, + epochMillisFields: string[] +): FeatureCollection; diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index f408896853155..b00594cb7fb23 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -271,7 +271,7 @@ export function triggerRefreshTimer() { }; } -export function updateDrawState(drawState: DrawState) { +export function updateDrawState(drawState: DrawState | null) { return (dispatch: Dispatch) => { if (drawState !== null) { dispatch({ type: SET_OPEN_TOOLTIPS, openTooltips: [] }); // tooltips just get in the way diff --git a/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts b/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts index 9faa33fae5a43..543dbf6d87039 100644 --- a/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts @@ -15,18 +15,22 @@ import { IVectorSource } from '../sources/vector_source'; export class ESDocField extends AbstractField implements IField { private readonly _source: IESSource; + private readonly _canReadFromGeoJson: boolean; constructor({ fieldName, source, origin, + canReadFromGeoJson = true, }: { fieldName: string; source: IESSource; origin: FIELD_ORIGIN; + canReadFromGeoJson?: boolean; }) { super({ fieldName, origin }); this._source = source; + this._canReadFromGeoJson = canReadFromGeoJson; } canValueBeFormatted(): boolean { @@ -60,6 +64,10 @@ export class ESDocField extends AbstractField implements IField { return true; } + canReadFromGeoJson(): boolean { + return this._canReadFromGeoJson; + } + async getOrdinalFieldMetaRequest(): Promise { const indexPatternField = await this._getIndexPatternField(); diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx index faae26cac08e7..822b78aa0deff 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx @@ -128,32 +128,41 @@ describe('syncData', () => { sinon.assert.notCalled(syncContext2.stopLoading); }); - it('Should resync when changes to source params', async () => { - const layer1: TiledVectorLayer = createLayer({}, {}); - const syncContext1 = new MockSyncContext({ dataFilters: {} }); - - await layer1.syncData(syncContext1); - - const dataRequestDescriptor: DataRequestDescriptor = { - data: defaultConfig, - dataId: 'source', - }; - const layer2: TiledVectorLayer = createLayer( - { - __dataRequests: [dataRequestDescriptor], - }, - { layerName: 'barfoo' } - ); - const syncContext2 = new MockSyncContext({ dataFilters: {} }); - await layer2.syncData(syncContext2); - - // @ts-expect-error - sinon.assert.calledOnce(syncContext2.startLoading); - // @ts-expect-error - sinon.assert.calledOnce(syncContext2.stopLoading); - - // @ts-expect-error - const call = syncContext2.stopLoading.getCall(0); - expect(call.args[2]).toEqual({ ...defaultConfig, layerName: 'barfoo' }); + describe('Should resync when changes to source params: ', () => { + [ + { layerName: 'barfoo' }, + { urlTemplate: 'https://sub.example.com/{z}/{x}/{y}.pbf' }, + { minSourceZoom: 1 }, + { maxSourceZoom: 12 }, + ].forEach((changes) => { + it(`change in ${Object.keys(changes).join(',')}`, async () => { + const layer1: TiledVectorLayer = createLayer({}, {}); + const syncContext1 = new MockSyncContext({ dataFilters: {} }); + + await layer1.syncData(syncContext1); + + const dataRequestDescriptor: DataRequestDescriptor = { + data: defaultConfig, + dataId: 'source', + }; + const layer2: TiledVectorLayer = createLayer( + { + __dataRequests: [dataRequestDescriptor], + }, + changes + ); + const syncContext2 = new MockSyncContext({ dataFilters: {} }); + await layer2.syncData(syncContext2); + + // @ts-expect-error + sinon.assert.calledOnce(syncContext2.startLoading); + // @ts-expect-error + sinon.assert.calledOnce(syncContext2.stopLoading); + + // @ts-expect-error + const call = syncContext2.stopLoading.getCall(0); + expect(call.args[2]).toEqual({ ...defaultConfig, ...changes }); + }); + }); }); }); diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index c9ae1c805fa30..70bf8ea3883b7 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -63,21 +63,24 @@ export class TiledVectorLayer extends VectorLayer { ); const prevDataRequest = this.getSourceDataRequest(); + const templateWithMeta = await this._source.getUrlTemplateWithMeta(searchFilters); if (prevDataRequest) { const data: MVTSingleLayerVectorSourceConfig = prevDataRequest.getData() as MVTSingleLayerVectorSourceConfig; - const canSkipBecauseNoChanges = - data.layerName === this._source.getLayerName() && - data.minSourceZoom === this._source.getMinZoom() && - data.maxSourceZoom === this._source.getMaxZoom(); - - if (canSkipBecauseNoChanges) { - return null; + if (data) { + const canSkipBecauseNoChanges = + data.layerName === this._source.getLayerName() && + data.minSourceZoom === this._source.getMinZoom() && + data.maxSourceZoom === this._source.getMaxZoom() && + data.urlTemplate === templateWithMeta.urlTemplate; + + if (canSkipBecauseNoChanges) { + return null; + } } } startLoading(SOURCE_DATA_REQUEST_ID, requestToken, searchFilters); try { - const templateWithMeta = await this._source.getUrlTemplateWithMeta(); stopLoading(SOURCE_DATA_REQUEST_ID, requestToken, templateWithMeta, {}); } catch (error) { onLoadError(SOURCE_DATA_REQUEST_ID, requestToken, error.message); @@ -160,6 +163,11 @@ export class TiledVectorLayer extends VectorLayer { return false; } + if (!mbTileSource.tiles) { + // Expected source is not compatible, so remove. + return true; + } + const isSourceDifferent = mbTileSource.tiles[0] !== tiledSourceMeta.urlTemplate || mbTileSource.minzoom !== tiledSourceMeta.minSourceZoom || diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js index 2ba7f750e9b40..c49d0044e6ad6 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js @@ -15,9 +15,11 @@ import { SOURCE_BOUNDS_DATA_REQUEST_ID, FEATURE_VISIBLE_PROPERTY_NAME, EMPTY_FEATURE_COLLECTION, + KBN_TOO_MANY_FEATURES_PROPERTY, LAYER_TYPE, FIELD_ORIGIN, LAYER_STYLE_TYPE, + KBN_TOO_MANY_FEATURES_IMAGE_ID, } from '../../../../common/constants'; import _ from 'lodash'; import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; @@ -777,6 +779,8 @@ export class VectorLayer extends AbstractLayer { const sourceId = this.getId(); const fillLayerId = this._getMbPolygonLayerId(); const lineLayerId = this._getMbLineLayerId(); + const tooManyFeaturesLayerId = this._getMbTooManyFeaturesLayerId(); + const hasJoins = this.hasJoins(); if (!mbMap.getLayer(fillLayerId)) { const mbLayer = { @@ -802,6 +806,30 @@ export class VectorLayer extends AbstractLayer { } mbMap.addLayer(mbLayer); } + if (!mbMap.getLayer(tooManyFeaturesLayerId)) { + const mbLayer = { + id: tooManyFeaturesLayerId, + type: 'fill', + source: sourceId, + paint: {}, + }; + if (mvtSourceLayer) { + mbLayer['source-layer'] = mvtSourceLayer; + } + mbMap.addLayer(mbLayer); + mbMap.setFilter(tooManyFeaturesLayerId, [ + '==', + ['get', KBN_TOO_MANY_FEATURES_PROPERTY], + true, + ]); + mbMap.setPaintProperty( + tooManyFeaturesLayerId, + 'fill-pattern', + KBN_TOO_MANY_FEATURES_IMAGE_ID + ); + mbMap.setPaintProperty(tooManyFeaturesLayerId, 'fill-opacity', this.getAlpha()); + } + this.getCurrentStyle().setMBPaintProperties({ alpha: this.getAlpha(), mbMap, @@ -822,6 +850,9 @@ export class VectorLayer extends AbstractLayer { if (lineFilterExpr !== mbMap.getFilter(lineLayerId)) { mbMap.setFilter(lineLayerId, lineFilterExpr); } + + this.syncVisibilityWithMb(mbMap, tooManyFeaturesLayerId); + mbMap.setLayerZoomRange(tooManyFeaturesLayerId, this.getMinZoom(), this.getMaxZoom()); } _syncStylePropertiesWithMb(mbMap) { @@ -836,6 +867,19 @@ export class VectorLayer extends AbstractLayer { type: 'geojson', data: EMPTY_FEATURE_COLLECTION, }); + } else if (mbSource.type !== 'geojson') { + // Recreate source when existing source is not geojson. This can occur when layer changes from tile layer to vector layer. + this.getMbLayerIds().forEach((mbLayerId) => { + if (mbMap.getLayer(mbLayerId)) { + mbMap.removeLayer(mbLayerId); + } + }); + + mbMap.removeSource(this._getMbSourceId()); + mbMap.addSource(this._getMbSourceId(), { + type: 'geojson', + data: EMPTY_FEATURE_COLLECTION, + }); } } @@ -865,6 +909,10 @@ export class VectorLayer extends AbstractLayer { return this.makeMbLayerId('fill'); } + _getMbTooManyFeaturesLayerId() { + return this.makeMbLayerId('toomanyfeatures'); + } + getMbLayerIds() { return [ this._getMbPointLayerId(), @@ -872,6 +920,7 @@ export class VectorLayer extends AbstractLayer { this._getMbSymbolLayerId(), this._getMbLineLayerId(), this._getMbPolygonLayerId(), + this._getMbTooManyFeaturesLayerId(), ]; } diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx index 52524d0c9a5fa..c5d6ced76b5c0 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.test.tsx @@ -6,7 +6,6 @@ import { EMSFileSource } from './ems_file_source'; -jest.mock('ui/new_platform'); jest.mock('../../layers/vector_layer/vector_layer', () => {}); function makeEMSFileSource(tooltipProperties: string[]) { diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts index 87abbedfdf50e..cf0170ab7f1bd 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts @@ -11,8 +11,6 @@ import _ from 'lodash'; import { AGG_TYPE } from '../../../../common/constants'; import { AggDescriptor } from '../../../../common/descriptor_types'; -jest.mock('ui/new_platform'); - const sumFieldName = 'myFieldGettingSummed'; const metricExamples = [ { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index 37193e148bdc7..2e0ba7cf3efee 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -3,10 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { MapExtent, MapFilters } from '../../../../common/descriptor_types'; +import { MapExtent, VectorSourceRequestMeta } from '../../../../common/descriptor_types'; jest.mock('../../../kibana_services'); -jest.mock('ui/new_platform'); import { getIndexPatternService, getSearchService } from '../../../kibana_services'; import { ESGeoGridSource } from './es_geo_grid_source'; @@ -20,6 +19,7 @@ import { SearchSource } from '../../../../../../../src/plugins/data/public/searc export class MockSearchSource { setField = jest.fn(); + setParent() {} } describe('ESGeoGridSource', () => { @@ -105,6 +105,9 @@ describe('ESGeoGridSource', () => { async create() { return mockSearchSource as SearchSource; }, + createEmpty() { + return mockSearchSource as SearchSource; + }, }, }; @@ -121,7 +124,7 @@ describe('ESGeoGridSource', () => { maxLat: 80, }; - const mapFilters: MapFilters = { + const mapFilters: VectorSourceRequestMeta = { geogridPrecision: 4, filters: [], timeFilters: { @@ -129,8 +132,16 @@ describe('ESGeoGridSource', () => { to: '15m', mode: 'relative', }, - // extent, + extent, + applyGlobalQuery: true, + fieldNames: [], buffer: extent, + sourceQuery: { + query: '', + language: 'KQL', + queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', + }, + sourceMeta: null, zoom: 0, }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap index 8ebb389472f74..dd62be11c679d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should disable clusters option when clustering is not supported 1`] = ` +exports[`scaling form should disable clusters option when clustering is not supported 1`] = ` + + + + Use vector tiles for faster display of large datasets. + + } + delay="regular" + position="left" + > + + +
+ + + + + +`; + +exports[`scaling form should disable mvt option when mvt is not supported 1`] = ` + + +
+ +
+
+ + +
+ + + + + +
`; -exports[`should render 1`] = ` +exports[`scaling form should render 1`] = ` + + + + Use vector tiles for faster display of large datasets. + + } + delay="regular" + position="left" + > + +
`; -exports[`should render top hits form when scaling type is TOP_HITS 1`] = ` +exports[`scaling form should render top hits form when scaling type is TOP_HITS 1`] = ` + + + + Use vector tiles for faster display of large datasets. + + } + delay="regular" + position="left" + > + +
@@ -159,6 +162,8 @@ export class CreateSourceEditor extends Component { this.state.indexPattern, this.state.geoFieldName )} + supportsMvt={mvtSupported} + mvtDisabledReason={mvtSupported ? null : getMvtDisabledReason()} clusteringDisabledReason={ this.state.indexPattern ? getGeoTileAggNotSupportedReason( diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx index 1ec6d2a1ff671..249b9a2454d7d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx @@ -14,13 +14,18 @@ import { ESSearchSource, sourceTitle } from './es_search_source'; import { BlendedVectorLayer } from '../../layers/blended_vector_layer/blended_vector_layer'; import { VectorLayer } from '../../layers/vector_layer/vector_layer'; import { LAYER_WIZARD_CATEGORY, SCALING_TYPES } from '../../../../common/constants'; +import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer'; export function createDefaultLayerDescriptor(sourceConfig: unknown, mapColors: string[]) { const sourceDescriptor = ESSearchSource.createDescriptor(sourceConfig); - return sourceDescriptor.scalingType === SCALING_TYPES.CLUSTERS - ? BlendedVectorLayer.createDescriptor({ sourceDescriptor }, mapColors) - : VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + if (sourceDescriptor.scalingType === SCALING_TYPES.CLUSTERS) { + return BlendedVectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + } else if (sourceDescriptor.scalingType === SCALING_TYPES.MVT) { + return TiledVectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + } else { + return VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + } } export const esDocumentsLayerWizardConfig: LayerWizard = { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.d.ts index 23e3c759d73c3..67d68dc065b00 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.d.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.d.ts @@ -5,11 +5,22 @@ */ import { AbstractESSource } from '../es_source'; -import { ESSearchSourceDescriptor } from '../../../../common/descriptor_types'; +import { ESSearchSourceDescriptor, MapFilters } from '../../../../common/descriptor_types'; +import { ITiledSingleLayerVectorSource } from '../vector_source'; -export class ESSearchSource extends AbstractESSource { +export class ESSearchSource extends AbstractESSource implements ITiledSingleLayerVectorSource { static createDescriptor(sourceConfig: unknown): ESSearchSourceDescriptor; constructor(sourceDescriptor: Partial, inspectorAdapters: unknown); getFieldNames(): string[]; + + getUrlTemplateWithMeta( + searchFilters: MapFilters + ): Promise<{ + layerName: string; + urlTemplate: string; + minSourceZoom: number; + maxSourceZoom: number; + }>; + getLayerName(): string; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js index 6d61c4a7455b2..7ac2738eaeb51 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js @@ -6,9 +6,10 @@ import _ from 'lodash'; import React from 'react'; +import rison from 'rison-node'; import { AbstractESSource } from '../es_source'; -import { getSearchService } from '../../../kibana_services'; +import { getSearchService, getHttp } from '../../../kibana_services'; import { hitsToGeoJson } from '../../../../common/elasticsearch_geo_utils'; import { UpdateSourceEditor } from './update_source_editor'; import { @@ -18,6 +19,9 @@ import { SORT_ORDER, SCALING_TYPES, VECTOR_SHAPE_TYPE, + MVT_SOURCE_LAYER_NAME, + GIS_API_PATH, + MVT_GETTILE_API_PATH, } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -96,6 +100,7 @@ export class ESSearchSource extends AbstractESSource { return new ESDocField({ fieldName, source: this, + canReadFromGeoJson: this._descriptor.scalingType !== SCALING_TYPES.MVT, }); } @@ -448,9 +453,13 @@ export class ESSearchSource extends AbstractESSource { } isFilterByMapBounds() { - return this._descriptor.scalingType === SCALING_TYPES.CLUSTER - ? true - : this._descriptor.filterByMapBounds; + if (this._descriptor.scalingType === SCALING_TYPES.CLUSTER) { + return true; + } else if (this._descriptor.scalingType === SCALING_TYPES.MVT) { + return false; + } else { + return this._descriptor.filterByMapBounds; + } } async getLeftJoinFields() { @@ -553,11 +562,65 @@ export class ESSearchSource extends AbstractESSource { } getJoinsDisabledReason() { - return this._descriptor.scalingType === SCALING_TYPES.CLUSTERS - ? i18n.translate('xpack.maps.source.esSearch.joinsDisabledReason', { - defaultMessage: 'Joins are not supported when scaling by clusters', - }) - : null; + let reason; + if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) { + reason = i18n.translate('xpack.maps.source.esSearch.joinsDisabledReason', { + defaultMessage: 'Joins are not supported when scaling by clusters', + }); + } else if (this._descriptor.scalingType === SCALING_TYPES.MVT) { + reason = i18n.translate('xpack.maps.source.esSearch.joinsDisabledReasonMvt', { + defaultMessage: 'Joins are not supported when scaling by mvt vector tiles', + }); + } else { + reason = null; + } + return reason; + } + + getLayerName() { + return MVT_SOURCE_LAYER_NAME; + } + + async getUrlTemplateWithMeta(searchFilters) { + const indexPattern = await this.getIndexPattern(); + const indexSettings = await loadIndexSettings(indexPattern.title); + + const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields( + indexPattern, + searchFilters.fieldNames + ); + + const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source + + const searchSource = await this.makeSearchSource( + searchFilters, + indexSettings.maxResultWindow, + initialSearchContext + ); + searchSource.setField('fields', searchFilters.fieldNames); // Setting "fields" filters out unused scripted fields + if (sourceOnlyFields.length === 0) { + searchSource.setField('source', false); // do not need anything from _source + } else { + searchSource.setField('source', sourceOnlyFields); + } + if (this._hasSort()) { + searchSource.setField('sort', this._buildEsSort()); + } + + const dsl = await searchSource.getSearchRequestBody(); + const risonDsl = rison.encode(dsl); + + const mvtUrlServicePath = getHttp().basePath.prepend( + `/${GIS_API_PATH}/${MVT_GETTILE_API_PATH}` + ); + + const urlTemplate = `${mvtUrlServicePath}?x={x}&y={y}&z={z}&geometryFieldName=${this._descriptor.geoField}&index=${indexPattern.title}&requestBody=${risonDsl}`; + return { + layerName: this.getLayerName(), + minSourceZoom: this.getMinZoom(), + maxSourceZoom: this.getMaxZoom(), + urlTemplate: urlTemplate, + }; } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts new file mode 100644 index 0000000000000..3223d0c94178f --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.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; + * you may not use this file except in compliance with the Elastic License. + */ +import { ES_GEO_FIELD_TYPE, SCALING_TYPES } from '../../../../common/constants'; + +jest.mock('../../../kibana_services'); +jest.mock('./load_index_settings'); + +import { getIndexPatternService, getSearchService, getHttp } from '../../../kibana_services'; +import { SearchSource } from '../../../../../../../src/plugins/data/public/search/search_source'; + +// @ts-expect-error +import { loadIndexSettings } from './load_index_settings'; + +import { ESSearchSource } from './es_search_source'; +import { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; + +describe('ESSearchSource', () => { + it('constructor', () => { + const esSearchSource = new ESSearchSource({}, null); + expect(esSearchSource instanceof ESSearchSource).toBe(true); + }); + + describe('ITiledSingleLayerVectorSource', () => { + it('mb-source params', () => { + const esSearchSource = new ESSearchSource({}, null); + expect(esSearchSource.getMinZoom()).toBe(0); + expect(esSearchSource.getMaxZoom()).toBe(24); + expect(esSearchSource.getLayerName()).toBe('source_layer'); + }); + + describe('getUrlTemplateWithMeta', () => { + const geoFieldName = 'bar'; + const mockIndexPatternService = { + get() { + return { + title: 'foobar-title-*', + fields: { + getByName() { + return { + name: geoFieldName, + type: ES_GEO_FIELD_TYPE.GEO_SHAPE, + }; + }, + }, + }; + }, + }; + + beforeEach(async () => { + const mockSearchSource = { + setField: jest.fn(), + getSearchRequestBody() { + return { foobar: 'ES_DSL_PLACEHOLDER', params: this.setField.mock.calls }; + }, + setParent() {}, + }; + const mockSearchService = { + searchSource: { + async create() { + return (mockSearchSource as unknown) as SearchSource; + }, + createEmpty() { + return (mockSearchSource as unknown) as SearchSource; + }, + }, + }; + + // @ts-expect-error + getIndexPatternService.mockReturnValue(mockIndexPatternService); + // @ts-expect-error + getSearchService.mockReturnValue(mockSearchService); + loadIndexSettings.mockReturnValue({ + maxResultWindow: 1000, + }); + // @ts-expect-error + getHttp.mockReturnValue({ + basePath: { + prepend(path: string) { + return `rootdir${path};`; + }, + }, + }); + }); + + const searchFilters: VectorSourceRequestMeta = { + filters: [], + zoom: 0, + fieldNames: ['tooltipField', 'styleField'], + timeFilters: { + from: 'now', + to: '15m', + mode: 'relative', + }, + sourceQuery: { + query: 'tooltipField: foobar', + language: 'KQL', + queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', + }, + sourceMeta: null, + applyGlobalQuery: true, + }; + + it('Should only include required props', async () => { + const esSearchSource = new ESSearchSource( + { geoField: geoFieldName, indexPatternId: 'ipId' }, + null + ); + const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters); + expect(urlTemplateWithMeta.urlTemplate).toBe( + `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fields,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))` + ); + }); + }); + }); + + describe('isFilterByMapBounds', () => { + it('default', () => { + const esSearchSource = new ESSearchSource({}, null); + expect(esSearchSource.isFilterByMapBounds()).toBe(true); + }); + it('mvt', () => { + const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null); + expect(esSearchSource.isFilterByMapBounds()).toBe(false); + }); + }); + + describe('getJoinsDisabledReason', () => { + it('default', () => { + const esSearchSource = new ESSearchSource({}, null); + expect(esSearchSource.getJoinsDisabledReason()).toBe(null); + }); + it('mvt', () => { + const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null); + expect(esSearchSource.getJoinsDisabledReason()).toBe( + 'Joins are not supported when scaling by mvt vector tiles' + ); + }); + }); + + describe('getFields', () => { + it('default', () => { + const esSearchSource = new ESSearchSource({}, null); + const docField = esSearchSource.createField({ fieldName: 'prop1' }); + expect(docField.canReadFromGeoJson()).toBe(true); + }); + it('mvt', () => { + const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null); + const docField = esSearchSource.createField({ fieldName: 'prop1' }); + expect(docField.canReadFromGeoJson()).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx index 6e56c179b4ead..f57335db14c62 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx @@ -27,28 +27,46 @@ const defaultProps = { termFields: [], topHitsSplitField: null, topHitsSize: 1, + supportsMvt: true, + mvtDisabledReason: null, }; -test('should render', async () => { - const component = shallow(); +describe('scaling form', () => { + test('should render', async () => { + const component = shallow(); - expect(component).toMatchSnapshot(); -}); + expect(component).toMatchSnapshot(); + }); -test('should disable clusters option when clustering is not supported', async () => { - const component = shallow( - - ); + test('should disable clusters option when clustering is not supported', async () => { + const component = shallow( + + ); - expect(component).toMatchSnapshot(); -}); + expect(component).toMatchSnapshot(); + }); + + test('should render top hits form when scaling type is TOP_HITS', async () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); + }); -test('should render top hits form when scaling type is TOP_HITS', async () => { - const component = shallow(); + test('should disable mvt option when mvt is not supported', async () => { + const component = shallow( + + ); - expect(component).toMatchSnapshot(); + expect(component).toMatchSnapshot(); + }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx index 816db6a98d593..cc2d4d059a3a8 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx @@ -4,16 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, Component } from 'react'; +import React, { Component, Fragment } from 'react'; import { EuiFormRow, + EuiHorizontalRule, + EuiRadio, + EuiSpacer, EuiSwitch, EuiSwitchEvent, EuiTitle, - EuiSpacer, - EuiHorizontalRule, - EuiRadio, EuiToolTip, + EuiBetaBadge, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -24,8 +25,8 @@ import { ValidatedRange } from '../../../components/validated_range'; import { DEFAULT_MAX_INNER_RESULT_WINDOW, DEFAULT_MAX_RESULT_WINDOW, - SCALING_TYPES, LAYER_TYPE, + SCALING_TYPES, } from '../../../../common/constants'; // @ts-ignore import { loadIndexSettings } from './load_index_settings'; @@ -38,7 +39,9 @@ interface Props { onChange: (args: OnSourceChangeArgs) => void; scalingType: SCALING_TYPES; supportsClustering: boolean; + supportsMvt: boolean; clusteringDisabledReason?: string | null; + mvtDisabledReason?: string | null; termFields: IFieldType[]; topHitsSplitField: string | null; topHitsSize: number; @@ -80,8 +83,15 @@ export class ScalingForm extends Component { } _onScalingTypeChange = (optionId: string): void => { - const layerType = - optionId === SCALING_TYPES.CLUSTERS ? LAYER_TYPE.BLENDED_VECTOR : LAYER_TYPE.VECTOR; + let layerType; + if (optionId === SCALING_TYPES.CLUSTERS) { + layerType = LAYER_TYPE.BLENDED_VECTOR; + } else if (optionId === SCALING_TYPES.MVT) { + layerType = LAYER_TYPE.TILED_VECTOR; + } else { + layerType = LAYER_TYPE.VECTOR; + } + this.props.onChange({ propName: 'scalingType', value: optionId, newLayerType: layerType }); }; @@ -177,9 +187,47 @@ export class ScalingForm extends Component { ); } + _renderMVTRadio() { + const labelText = i18n.translate('xpack.maps.source.esSearch.useMVTVectorTiles', { + defaultMessage: 'Use vector tiles', + }); + const mvtRadio = ( + this._onScalingTypeChange(SCALING_TYPES.MVT)} + disabled={!this.props.supportsMvt} + /> + ); + + const enabledInfo = ( + <> + + + {i18n.translate('xpack.maps.source.esSearch.mvtDescription', { + defaultMessage: 'Use vector tiles for faster display of large datasets.', + })} + + ); + + return !this.props.supportsMvt ? ( + + {mvtRadio} + + ) : ( + + {mvtRadio} + + ); + } + render() { let filterByBoundsSwitch; - if (this.props.scalingType !== SCALING_TYPES.CLUSTERS) { + if ( + this.props.scalingType === SCALING_TYPES.TOP_HITS || + this.props.scalingType === SCALING_TYPES.LIMIT + ) { filterByBoundsSwitch = ( { ); } - let scalingForm = null; + let topHitsOptionsForm = null; if (this.props.scalingType === SCALING_TYPES.TOP_HITS) { - scalingForm = ( + topHitsOptionsForm = ( {this._renderTopHitsForm()} @@ -234,12 +282,12 @@ export class ScalingForm extends Component { onChange={() => this._onScalingTypeChange(SCALING_TYPES.TOP_HITS)} /> {this._renderClusteringRadio()} + {this._renderMVTRadio()}
{filterByBoundsSwitch} - - {scalingForm} + {topHitsOptionsForm} ); } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js index 0701dbbaecdd5..c123c307c4895 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js @@ -17,6 +17,8 @@ import { getTermsFields, getSourceFields, supportsGeoTileAgg, + supportsMvt, + getMvtDisabledReason, } from '../../../index_pattern_util'; import { SORT_ORDER } from '../../../../common/constants'; import { ESDocField } from '../../fields/es_doc_field'; @@ -42,6 +44,9 @@ export class UpdateSourceEditor extends Component { termFields: null, sortFields: null, supportsClustering: false, + supportsMvt: false, + mvtDisabledReason: null, + clusteringDisabledReason: null, }; componentDidMount() { @@ -94,9 +99,12 @@ export class UpdateSourceEditor extends Component { }); }); + const mvtSupported = supportsMvt(indexPattern, geoField.name); this.setState({ supportsClustering: supportsGeoTileAgg(geoField), + supportsMvt: mvtSupported, clusteringDisabledReason: getGeoTileAggNotSupportedReason(geoField), + mvtDisabledReason: mvtSupported ? null : getMvtDisabledReason(), sourceFields: sourceFields, termFields: getTermsFields(indexPattern.fields), //todo change term fields to use fields sortFields: indexPattern.fields.filter( @@ -207,7 +215,9 @@ export class UpdateSourceEditor extends Component { onChange={this.props.onChange} scalingType={this.props.scalingType} supportsClustering={this.state.supportsClustering} + supportsMvt={this.state.supportsMvt} clusteringDisabledReason={this.state.clusteringDisabledReason} + mvtDisabledReason={this.state.mvtDisabledReason} termFields={this.state.termFields} topHitsSplitField={this.props.topHitsSplitField} topHitsSize={this.props.topHitsSize} diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js index 8cc2aa018979b..56b830e9ff098 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js @@ -184,7 +184,7 @@ export class AbstractESSource extends AbstractVectorSource { const minLon = esBounds.top_left.lon; const maxLon = esBounds.bottom_right.lon; return { - minLon: minLon > maxLon ? minLon - 360 : minLon, + minLon: minLon > maxLon ? minLon - 360 : minLon, //fixes an ES bbox to straddle dateline maxLon, minLat: esBounds.bottom_right.lat, maxLat: esBounds.top_left.lat, diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js index f6779206868a5..060096157f578 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js @@ -6,7 +6,6 @@ import { ESTermSource, extractPropertiesMap } from './es_term_source'; -jest.mock('ui/new_platform'); jest.mock('../../layers/vector_layer/vector_layer', () => {}); const indexPatternTitle = 'myIndex'; diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts index 271505010f36a..fd9c179275444 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts @@ -14,6 +14,7 @@ import { MapExtent, MapFilters, MapQuery, + VectorSourceRequestMeta, VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { VECTOR_SHAPE_TYPE } from '../../../../common/constants'; @@ -64,7 +65,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc ): MapExtent | null; getGeoJsonWithMeta( layerName: string, - searchFilters: MapFilters, + searchFilters: VectorSourceRequestMeta, registerCancelCallback: (callback: () => void) => void ): Promise; @@ -79,7 +80,9 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc } export interface ITiledSingleLayerVectorSource extends IVectorSource { - getUrlTemplateWithMeta(): Promise<{ + getUrlTemplateWithMeta( + searchFilters: VectorSourceRequestMeta + ): Promise<{ layerName: string; urlTemplate: string; minSourceZoom: number; diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.tsx index aa4dbc67e8e4d..f082e67512099 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.tsx @@ -9,8 +9,6 @@ import { shallow } from 'enzyme'; import { HeatmapStyleEditor } from './heatmap_style_editor'; -jest.mock('ui/new_platform'); - describe('HeatmapStyleEditor', () => { test('is rendered', () => { const component = shallow( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/categorical_field_meta_popover.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/categorical_field_meta_popover.tsx index e49c15c68b8db..2a544b94d760a 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/categorical_field_meta_popover.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/categorical_field_meta_popover.tsx @@ -14,6 +14,7 @@ import { FieldMetaOptions } from '../../../../../../common/descriptor_types'; type Props = { fieldMetaOptions: FieldMetaOptions; onChange: (fieldMetaOptions: FieldMetaOptions) => void; + switchDisabled: boolean; }; export function CategoricalFieldMetaPopover(props: Props) { @@ -34,6 +35,7 @@ export function CategoricalFieldMetaPopover(props: Props) { checked={props.fieldMetaOptions.isEnabled} onChange={onIsEnabledChange} compressed + disabled={props.switchDisabled} /> diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx index 9086c4df31596..09be9d72af970 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx @@ -40,6 +40,7 @@ type Props = { fieldMetaOptions: FieldMetaOptions; styleName: VECTOR_STYLES; onChange: (fieldMetaOptions: FieldMetaOptions) => void; + switchDisabled: boolean; }; export function OrdinalFieldMetaPopover(props: Props) { @@ -66,6 +67,7 @@ export function OrdinalFieldMetaPopover(props: Props) { checked={props.fieldMetaOptions.isEnabled} onChange={onIsEnabledChange} compressed + disabled={props.switchDisabled} /> diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap index c722e86512e52..34d2d7fb0cbbf 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.tsx.snap @@ -325,3 +325,29 @@ exports[`ordinal Should render ordinal legend as bands 1`] = `
`; + +exports[`renderFieldMetaPopover Should disable toggle when field is not backed by geojson source 1`] = ` + +`; + +exports[`renderFieldMetaPopover Should enable toggle when field is backed by geojson-source 1`] = ` + +`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx index 62a6a59dd091b..de8f3b5c09175 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); jest.mock('../components/vector_style_editor', () => ({ VectorStyleEditor: () => { return
mockVectorStyleEditor
; @@ -578,3 +577,39 @@ test('Should read out ordinal type correctly', async () => { expect(ordinalColorStyle2.isOrdinal()).toEqual(true); expect(ordinalColorStyle2.isCategorical()).toEqual(false); }); + +describe('renderFieldMetaPopover', () => { + test('Should enable toggle when field is backed by geojson-source', () => { + const colorStyle = makeProperty( + { + color: 'Blues', + type: undefined, + fieldMetaOptions, + }, + undefined, + mockField + ); + + const legendRow = colorStyle.renderFieldMetaPopover(() => {}); + expect(legendRow).toMatchSnapshot(); + }); + + test('Should disable toggle when field is not backed by geojson source', () => { + const nonGeoJsonField = Object.create(mockField); + nonGeoJsonField.canReadFromGeoJson = () => { + return false; + }; + const colorStyle = makeProperty( + { + color: 'Blues', + type: undefined, + fieldMetaOptions, + }, + undefined, + nonGeoJsonField + ); + + const legendRow = colorStyle.renderFieldMetaPopover(() => {}); + expect(legendRow).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx index af93c8e0c9d6d..06987ab8bcc48 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx @@ -6,7 +6,6 @@ import { shallow } from 'enzyme'; -jest.mock('ui/new_platform'); jest.mock('../components/vector_style_editor', () => ({ VectorStyleEditor: () => { return
mockVectorStyleEditor
; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx index db44ae0da562d..c5298067f6cbe 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); jest.mock('../components/vector_style_editor', () => ({ VectorStyleEditor: () => { return
mockVectorStyleEditor
; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index b16755e69f92d..f6ab052497723 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -328,16 +328,20 @@ export class DynamicStyleProperty return null; } + const switchDisabled = !!this._field && !this._field.canReadFromGeoJson(); + return this.isCategorical() ? ( ) : ( ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js index a85cd0cc86407..28801a402ca14 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js @@ -14,7 +14,6 @@ import { } from '../../../../common/constants'; jest.mock('../../../kibana_services'); -jest.mock('ui/new_platform'); class MockField { constructor({ fieldName }) { diff --git a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts index 8da6fa2318de9..0da6f632eb4a8 100644 --- a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts +++ b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts @@ -4,32 +4,48 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GEO_JSON_TYPE, FEATURE_VISIBLE_PROPERTY_NAME } from '../../../common/constants'; +import { + GEO_JSON_TYPE, + FEATURE_VISIBLE_PROPERTY_NAME, + KBN_TOO_MANY_FEATURES_PROPERTY, +} from '../../../common/constants'; + +export const EXCLUDE_TOO_MANY_FEATURES_BOX = ['!=', ['get', KBN_TOO_MANY_FEATURES_PROPERTY], true]; const VISIBILITY_FILTER_CLAUSE = ['all', ['==', ['get', FEATURE_VISIBLE_PROPERTY_NAME], true]]; +const TOO_MANY_FEATURES_FILTER = ['all', EXCLUDE_TOO_MANY_FEATURES_BOX]; const CLOSED_SHAPE_MB_FILTER = [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + ...TOO_MANY_FEATURES_FILTER, + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + ], ]; const VISIBLE_CLOSED_SHAPE_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, CLOSED_SHAPE_MB_FILTER]; const ALL_SHAPE_MB_FILTER = [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING], + ...TOO_MANY_FEATURES_FILTER, + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING], + ], ]; const VISIBLE_ALL_SHAPE_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, ALL_SHAPE_MB_FILTER]; const POINT_MB_FILTER = [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], + ...TOO_MANY_FEATURES_FILTER, + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], + ], ]; const VISIBLE_POINT_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, POINT_MB_FILTER]; diff --git a/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx b/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx index cbeb4e79a38d7..7e18088444129 100644 --- a/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx +++ b/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx @@ -27,6 +27,8 @@ export type FieldProps = { name: string; }; +type FieldOptions = Array>; + function sortByLabel(a: EuiSelectableOption, b: EuiSelectableOption): number { return a.label.localeCompare(b.label); } @@ -66,7 +68,7 @@ interface Props { interface State { isPopoverOpen: boolean; checkedFields: string[]; - options?: EuiSelectableOption[]; + options?: FieldOptions; prevFields?: FieldProps[]; prevSelectedFields?: FieldProps[]; } @@ -105,13 +107,15 @@ export class AddTooltipFieldPopover extends Component { }); }; - _onSelect = (options: EuiSelectableOption[]) => { + _onSelect = (selectableOptions: EuiSelectableOption[]) => { + // EUI team to remove casting: https://github.com/elastic/eui/issues/3966 + const options = selectableOptions as FieldOptions; const checkedFields: string[] = options .filter((option) => { return option.checked === 'on'; }) .map((option) => { - return option.value as string; + return option.value!; }); this.setState({ diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap index b005e3ca6b17d..99cad61d6b2b4 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap @@ -57,6 +57,8 @@ exports[`LayerPanel is rendered 1`] = ` buttonContent="Source details" id="accordion1" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > { + mbMap.addImage(KBN_TOO_MANY_FEATURES_IMAGE_ID, tooManyFeaturesImage); + }; + tooManyFeaturesImage.src = tooManyFeaturesImageSrc; + let emptyImage; mbMap.on('styleimagemissing', (e) => { if (emptyImage) { diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.js.snap b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap similarity index 99% rename from x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.js.snap rename to x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap index d7fa099fe9dbe..afeee04358e15 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.js.snap +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap @@ -20,7 +20,7 @@ exports[`Should render cancel button when drawing 1`] = ` closePopover={[Function]} display="inlineBlock" hasArrow={true} - id="contextMenu" + id="toolsControlPopover" isOpen={false} ownFocus={false} panelPaddingSize="none" @@ -146,7 +146,7 @@ exports[`renders 1`] = ` closePopover={[Function]} display="inlineBlock" hasArrow={true} - id="contextMenu" + id="toolsControlPopover" isOpen={false} ownFocus={false} panelPaddingSize="none" diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.ts similarity index 63% rename from x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.js rename to x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.ts index 5812604a91b87..70101f1ce6eba 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/index.ts @@ -4,24 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AnyAction, Dispatch } from 'redux'; import { connect } from 'react-redux'; import { ToolsControl } from './tools_control'; import { isDrawingFilter } from '../../../selectors/map_selectors'; import { updateDrawState } from '../../../actions'; +import { MapStoreState } from '../../../reducers/store'; +import { DrawState } from '../../../../common/descriptor_types'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { isDrawingFilter: isDrawingFilter(state), }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: Dispatch) { return { - initiateDraw: (options) => { - dispatch(updateDrawState(options)); + initiateDraw: (drawState: DrawState) => { + dispatch(updateDrawState(drawState)); }, cancelDraw: () => { - dispatch(updateDrawState(null)); + dispatch(updateDrawState(null)); }, }; } diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.tsx similarity index 97% rename from x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.js rename to x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.tsx index f0e6dd43e68a1..544d789468e89 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.js +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.test.tsx @@ -19,6 +19,7 @@ const defaultProps = { indexPatternId: '1', }, ], + isDrawingFilter: false, }; test('renders', async () => { diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx similarity index 81% rename from x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js rename to x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx index 017f0369e0b73..fa0864ce680a2 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx @@ -14,10 +14,14 @@ import { EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DRAW_TYPE, ES_GEO_FIELD_TYPE } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; +import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../../../common/constants'; +// @ts-expect-error import { GeometryFilterForm } from '../../../components/geometry_filter_form'; import { DistanceFilterForm } from '../../../components/distance_filter_form'; +import { GeoFieldWithIndex } from '../../../components/geo_field_with_index'; +import { DrawState } from '../../../../common/descriptor_types'; const DRAW_SHAPE_LABEL = i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', { defaultMessage: 'Draw shape to filter data', @@ -46,8 +50,21 @@ const DRAW_DISTANCE_LABEL_SHORT = i18n.translate( } ); -export class ToolsControl extends Component { - state = { +interface Props { + cancelDraw: () => void; + geoFields: GeoFieldWithIndex[]; + initiateDraw: (drawState: DrawState) => void; + isDrawingFilter: boolean; + getFilterActions?: () => Promise; + getActionContext?: () => ActionExecutionContext; +} + +interface State { + isPopoverOpen: boolean; +} + +export class ToolsControl extends Component { + state: State = { isPopoverOpen: false, }; @@ -61,7 +78,14 @@ export class ToolsControl extends Component { this.setState({ isPopoverOpen: false }); }; - _initiateShapeDraw = (options) => { + _initiateShapeDraw = (options: { + actionId: string; + geometryLabel: string; + indexPatternId: string; + geoFieldName: string; + geoFieldType: ES_GEO_FIELD_TYPE; + relation: ES_SPATIAL_RELATIONS; + }) => { this.props.initiateDraw({ drawType: DRAW_TYPE.POLYGON, ...options, @@ -69,7 +93,14 @@ export class ToolsControl extends Component { this._closePopover(); }; - _initiateBoundsDraw = (options) => { + _initiateBoundsDraw = (options: { + actionId: string; + geometryLabel: string; + indexPatternId: string; + geoFieldName: string; + geoFieldType: ES_GEO_FIELD_TYPE; + relation: ES_SPATIAL_RELATIONS; + }) => { this.props.initiateDraw({ drawType: DRAW_TYPE.BOUNDS, ...options, @@ -77,7 +108,12 @@ export class ToolsControl extends Component { this._closePopover(); }; - _initiateDistanceDraw = (options) => { + _initiateDistanceDraw = (options: { + actionId: string; + filterLabel: string; + indexPatternId: string; + geoFieldName: string; + }) => { this.props.initiateDraw({ drawType: DRAW_TYPE.DISTANCE, ...options, @@ -194,7 +230,7 @@ export class ToolsControl extends Component { render() { const toolsPopoverButton = ( { render() { return ( { diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 239a2898a06fc..c1dfb61e9f3b6 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import { MapsLegacyConfigType } from '../../../../src/plugins/maps_legacy/public'; +import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config'; import { MapsConfigType } from '../config'; import { MapsPluginStartDependencies } from './plugin'; import { CoreStart } from '../../../../src/core/public'; @@ -64,9 +64,8 @@ export const getShowMapsInspectorAdapter = () => getMapAppConfig().showMapsInspe export const getPreserveDrawingBuffer = () => getMapAppConfig().preserveDrawingBuffer; // map.* kibana.yml settings from maps_legacy plugin that are shared between OSS map visualizations and maps app -let kibanaCommonConfig: MapsLegacyConfigType; -export const setKibanaCommonConfig = (config: MapsLegacyConfigType) => - (kibanaCommonConfig = config); +let kibanaCommonConfig: MapsLegacyConfig; +export const setKibanaCommonConfig = (config: MapsLegacyConfig) => (kibanaCommonConfig = config); export const getKibanaCommonConfig = () => kibanaCommonConfig; export const getIsEmsEnabled = () => getKibanaCommonConfig().includeElasticMapsService; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index e03a085e9bc88..b08135b4e486c 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -44,7 +44,7 @@ import { MapsStartApi } from './api'; import { createSecurityLayerDescriptors, registerLayerWizard, registerSource } from './api'; import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; -import { MapsLegacyConfigType } from '../../../../src/plugins/maps_legacy/public'; +import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { LicensingPluginStart } from '../../licensing/public'; import { StartContract as FileUploadStartContract } from '../../file_upload/public'; @@ -54,7 +54,7 @@ export interface MapsPluginSetupDependencies { home?: HomePublicPluginSetup; visualizations: VisualizationsSetup; embeddable: EmbeddableSetup; - mapsLegacy: { config: MapsLegacyConfigType }; + mapsLegacy: { config: MapsLegacyConfig }; share: SharePluginSetup; } diff --git a/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.js b/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.js index e9229883d708d..8fe6866cd7834 100644 --- a/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.js +++ b/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.js @@ -12,6 +12,7 @@ import { getUiSettings, getToasts, getCoreChrome, + getNavigateToApp, } from '../../../kibana_services'; import { EuiTitle, @@ -32,11 +33,18 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { addHelpMenuToAppChrome } from '../../../help_menu_util'; -import { Link } from 'react-router-dom'; import { goToSpecifiedPath } from '../../maps_router'; +import { APP_ID, MAP_PATH } from '../../../../common/constants'; export const EMPTY_FILTER = ''; +function navigateToNewMap() { + const navigateToApp = getNavigateToApp(); + navigateToApp(APP_ID, { + path: MAP_PATH, + }); +} + export class MapsListView extends React.Component { state = { hasInitialFetchReturned: false, @@ -388,14 +396,12 @@ export class MapsListView extends React.Component { let createButton; if (!this.state.readOnly) { createButton = ( - - - - - + + + ); } return ( diff --git a/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_search.json b/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_search.json new file mode 100644 index 0000000000000..0fc99ffd811f7 --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_search.json @@ -0,0 +1 @@ +{"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":0,"hits":[{"_index":"poly","_id":"G7PRMXQBgyyZ-h5iYibj","_score":0,"_source":{"coordinates":{"coordinates":[[[-106.171875,36.59788913307022],[-50.625,-22.91792293614603],[4.921875,42.8115217450979],[-33.046875,63.54855223203644],[-66.796875,63.860035895395306],[-106.171875,36.59788913307022]]],"type":"polygon"}}}]}} diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0.pbf new file mode 100644 index 0000000000000..9a9296e2ece3f Binary files /dev/null and b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0.pbf differ diff --git a/x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts b/x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts new file mode 100644 index 0000000000000..317d6434cf81e --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as path from 'path'; +import * as fs from 'fs'; + +const search000path = path.resolve(__dirname, './json/0_0_0_search.json'); +const search000raw = fs.readFileSync(search000path); +const search000json = JSON.parse((search000raw as unknown) as string); + +export const TILE_SEARCHES = { + '0.0.0': { + countResponse: { + count: 1, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + }, + searchResponse: search000json, + }, + '1.1.0': {}, +}; diff --git a/x-pack/plugins/maps/server/mvt/get_tile.test.ts b/x-pack/plugins/maps/server/mvt/get_tile.test.ts new file mode 100644 index 0000000000000..b9c928d594539 --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/get_tile.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getTile } from './get_tile'; +import { TILE_SEARCHES } from './__tests__/tile_searches'; +import { Logger } from 'src/core/server'; +import * as path from 'path'; +import * as fs from 'fs'; + +describe('getTile', () => { + const mockCallElasticsearch = jest.fn(); + + const requestBody = { + _source: { excludes: [] }, + docvalue_fields: [], + query: { bool: { filter: [{ match_all: {} }], must: [], must_not: [], should: [] } }, + script_fields: {}, + size: 10000, + stored_fields: ['*'], + }; + const geometryFieldName = 'coordinates'; + + beforeEach(() => { + mockCallElasticsearch.mockReset(); + }); + + test('0.0.0 - under limit', async () => { + mockCallElasticsearch.mockImplementation((type) => { + if (type === 'count') { + return TILE_SEARCHES['0.0.0'].countResponse; + } else if (type === 'search') { + return TILE_SEARCHES['0.0.0'].searchResponse; + } else { + throw new Error(`${type} not recognized`); + } + }); + + const tile = await getTile({ + x: 0, + y: 0, + z: 0, + index: 'world_countries', + requestBody, + geometryFieldName, + logger: ({ + info: () => {}, + } as unknown) as Logger, + callElasticsearch: mockCallElasticsearch, + }); + + if (tile === null) { + throw new Error('Tile should be created'); + } + + const expectedPath = path.resolve(__dirname, './__tests__/pbf/0_0_0.pbf'); + const expectedBin = fs.readFileSync(expectedPath, 'binary'); + const expectedTile = Buffer.from(expectedBin, 'binary'); + expect(expectedTile.equals(tile)).toBe(true); + }); +}); diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts new file mode 100644 index 0000000000000..9621f7f174a30 --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -0,0 +1,226 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-expect-error +import geojsonvt from 'geojson-vt'; +// @ts-expect-error +import vtpbf from 'vt-pbf'; +import { Logger } from 'src/core/server'; +import { Feature, FeatureCollection, Polygon } from 'geojson'; +import { + ES_GEO_FIELD_TYPE, + FEATURE_ID_PROPERTY_NAME, + KBN_TOO_MANY_FEATURES_PROPERTY, + MVT_SOURCE_LAYER_NAME, +} from '../../common/constants'; + +import { hitsToGeoJson } from '../../common/elasticsearch_geo_utils'; +import { flattenHit } from './util'; + +interface ESBounds { + top_left: { + lon: number; + lat: number; + }; + bottom_right: { + lon: number; + lat: number; + }; +} + +export async function getTile({ + logger, + callElasticsearch, + index, + geometryFieldName, + x, + y, + z, + requestBody = {}, +}: { + x: number; + y: number; + z: number; + geometryFieldName: string; + index: string; + callElasticsearch: (type: string, ...args: any[]) => Promise; + logger: Logger; + requestBody: any; +}): Promise { + const geojsonBbox = tileToGeoJsonPolygon(x, y, z); + + let resultFeatures: Feature[]; + try { + let result; + try { + const geoShapeFilter = { + geo_shape: { + [geometryFieldName]: { + shape: geojsonBbox, + relation: 'INTERSECTS', + }, + }, + }; + requestBody.query.bool.filter.push(geoShapeFilter); + + const esSearchQuery = { + index, + body: requestBody, + }; + + const esCountQuery = { + index, + body: { + query: requestBody.query, + }, + }; + + const countResult = await callElasticsearch('count', esCountQuery); + + // @ts-expect-error + if (countResult.count > requestBody.size) { + // Generate "too many features"-bounds + const bboxAggName = 'data_bounds'; + const bboxQuery = { + index, + body: { + size: 0, + query: requestBody.query, + aggs: { + [bboxAggName]: { + geo_bounds: { + field: geometryFieldName, + }, + }, + }, + }, + }; + + const bboxResult = await callElasticsearch('search', bboxQuery); + + // @ts-expect-error + const bboxForData = esBboxToGeoJsonPolygon(bboxResult.aggregations[bboxAggName].bounds); + + resultFeatures = [ + { + type: 'Feature', + properties: { + [KBN_TOO_MANY_FEATURES_PROPERTY]: true, + }, + geometry: bboxForData, + }, + ]; + } else { + // Perform actual search + result = await callElasticsearch('search', esSearchQuery); + + // Todo: pass in epochMillies-fields + const featureCollection = hitsToGeoJson( + // @ts-expect-error + result.hits.hits, + (hit: Record) => { + return flattenHit(geometryFieldName, hit); + }, + geometryFieldName, + ES_GEO_FIELD_TYPE.GEO_SHAPE, + [] + ); + + resultFeatures = featureCollection.features; + + // Correct system-fields. + for (let i = 0; i < resultFeatures.length; i++) { + const props = resultFeatures[i].properties; + if (props !== null) { + props[FEATURE_ID_PROPERTY_NAME] = resultFeatures[i].id; + } + } + } + } catch (e) { + logger.warn(e.message); + throw e; + } + + const featureCollection: FeatureCollection = { + features: resultFeatures, + type: 'FeatureCollection', + }; + + const tileIndex = geojsonvt(featureCollection, { + maxZoom: 24, // max zoom to preserve detail on; can't be higher than 24 + tolerance: 3, // simplification tolerance (higher means simpler) + extent: 4096, // tile extent (both width and height) + buffer: 64, // tile buffer on each side + debug: 0, // logging level (0 to disable, 1 or 2) + lineMetrics: false, // whether to enable line metrics tracking for LineString/MultiLineString features + promoteId: null, // name of a feature property to promote to feature.id. Cannot be used with `generateId` + generateId: false, // whether to generate feature ids. Cannot be used with `promoteId` + indexMaxZoom: 5, // max zoom in the initial tile index + indexMaxPoints: 100000, // max number of points per tile in the index + }); + const tile = tileIndex.getTile(z, x, y); + + if (tile) { + const pbf = vtpbf.fromGeojsonVt({ [MVT_SOURCE_LAYER_NAME]: tile }, { version: 2 }); + return Buffer.from(pbf); + } else { + return null; + } + } catch (e) { + logger.warn(`Cannot generate tile for ${z}/${x}/${y}: ${e.message}`); + return null; + } +} + +function tileToGeoJsonPolygon(x: number, y: number, z: number): Polygon { + const wLon = tile2long(x, z); + const sLat = tile2lat(y + 1, z); + const eLon = tile2long(x + 1, z); + const nLat = tile2lat(y, z); + + return { + type: 'Polygon', + coordinates: [ + [ + [wLon, sLat], + [wLon, nLat], + [eLon, nLat], + [eLon, sLat], + [wLon, sLat], + ], + ], + }; +} + +function tile2long(x: number, z: number): number { + return (x / Math.pow(2, z)) * 360 - 180; +} + +function tile2lat(y: number, z: number): number { + const n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z); + return (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); +} + +function esBboxToGeoJsonPolygon(esBounds: ESBounds): Polygon { + let minLon = esBounds.top_left.lon; + const maxLon = esBounds.bottom_right.lon; + minLon = minLon > maxLon ? minLon - 360 : minLon; // fixes an ES bbox to straddle dateline + const minLat = esBounds.bottom_right.lat; + const maxLat = esBounds.top_left.lat; + + return { + type: 'Polygon', + coordinates: [ + [ + [minLon, minLat], + [minLon, maxLat], + [maxLon, maxLat], + [maxLon, minLat], + [minLon, minLat], + ], + ], + }; +} diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts new file mode 100644 index 0000000000000..32c14a355ba2a --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import rison from 'rison-node'; +import { schema } from '@kbn/config-schema'; +import { Logger } from 'src/core/server'; +import { IRouter } from 'src/core/server'; +import { MVT_GETTILE_API_PATH, API_ROOT_PATH } from '../../common/constants'; +import { getTile } from './get_tile'; + +const CACHE_TIMEOUT = 0; // Todo. determine good value. Unsure about full-implications (e.g. wrt. time-based data). + +export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRouter }) { + router.get( + { + path: `${API_ROOT_PATH}/${MVT_GETTILE_API_PATH}`, + validate: { + query: schema.object({ + x: schema.number(), + y: schema.number(), + z: schema.number(), + geometryFieldName: schema.string(), + requestBody: schema.string(), + index: schema.string(), + }), + }, + }, + async (context, request, response) => { + const { query } = request; + + const callElasticsearch = async (type: string, ...args: any[]): Promise => { + return await context.core.elasticsearch.legacy.client.callAsCurrentUser(type, ...args); + }; + + const requestBodyDSL = rison.decode(query.requestBody); + + const tile = await getTile({ + logger, + callElasticsearch, + geometryFieldName: query.geometryFieldName, + x: query.x, + y: query.y, + z: query.z, + index: query.index, + requestBody: requestBodyDSL, + }); + + if (tile) { + return response.ok({ + body: tile, + headers: { + 'content-disposition': 'inline', + 'content-length': `${tile.length}`, + 'Content-Type': 'application/x-protobuf', + 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, + }, + }); + } else { + return response.ok({ + headers: { + 'content-disposition': 'inline', + 'content-length': '0', + 'Content-Type': 'application/x-protobuf', + 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, + }, + }); + } + } + ); +} diff --git a/x-pack/plugins/maps/server/mvt/util.ts b/x-pack/plugins/maps/server/mvt/util.ts new file mode 100644 index 0000000000000..eb85468dd770d --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/util.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// This implementation: +// - does not include meta-fields +// - does not validate the schema against the index-pattern (e.g. nested fields) +// In the context of .mvt this is sufficient: +// - only fields from the response are packed in the tile (more efficient) +// - query-dsl submitted from the client, which was generated by the IndexPattern +// todo: Ideally, this should adapt/reuse from https://github.com/elastic/kibana/blob/52b42a81faa9dd5c102b9fbb9a645748c3623121/src/plugins/data/common/index_patterns/index_patterns/flatten_hit.ts#L26 +import { GeoJsonProperties } from 'geojson'; + +export function flattenHit(geometryField: string, hit: Record): GeoJsonProperties { + const flat: GeoJsonProperties = {}; + if (hit) { + flattenSource(flat, '', hit._source as Record, geometryField); + if (hit.fields) { + flattenFields(flat, hit.fields as Array>); + } + + // Attach meta fields + flat._index = hit._index; + flat._id = hit._id; + } + return flat; +} + +function flattenSource( + accum: GeoJsonProperties, + path: string, + properties: Record = {}, + geometryField: string +): GeoJsonProperties { + accum = accum || {}; + for (const key in properties) { + if (properties.hasOwnProperty(key)) { + const newKey = path ? path + '.' + key : key; + let value; + if (geometryField === newKey) { + value = properties[key]; // do not deep-copy the geometry + } else if (properties[key] !== null && typeof value === 'object' && !Array.isArray(value)) { + value = flattenSource( + accum, + newKey, + properties[key] as Record, + geometryField + ); + } else { + value = properties[key]; + } + accum[newKey] = value; + } + } + return accum; +} + +function flattenFields(accum: GeoJsonProperties = {}, fields: Array>) { + accum = accum || {}; + for (const key in fields) { + if (fields.hasOwnProperty(key)) { + const value = fields[key]; + if (Array.isArray(value)) { + accum[key] = value[0]; + } else { + accum[key] = value; + } + } + } +} diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 7d091099c1aaa..6862e7536b07f 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -27,6 +27,7 @@ import { ILicense } from '../../licensing/common/types'; import { LicensingPluginSetup } from '../../licensing/server'; import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; import { MapsLegacyPluginSetup } from '../../../../src/plugins/maps_legacy/server'; +import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config'; interface SetupDeps { features: FeaturesPluginSetupContract; @@ -50,7 +51,7 @@ export class MapsPlugin implements Plugin { _initHomeData( home: HomeServerPluginSetup, prependBasePath: (path: string) => string, - mapConfig: any + mapsLegacyConfig: MapsLegacyConfig ) { const sampleDataLinkLabel = i18n.translate('xpack.maps.sampleDataLinkLabel', { defaultMessage: 'Map', @@ -123,7 +124,7 @@ export class MapsPlugin implements Plugin { home.tutorials.registerTutorial( emsBoundariesSpecProvider({ prependBasePath, - emsLandingPageUrl: mapConfig.emsLandingPageUrl, + emsLandingPageUrl: mapsLegacyConfig.emsLandingPageUrl, }) ); } @@ -160,7 +161,7 @@ export class MapsPlugin implements Plugin { } }); - this._initHomeData(home, core.http.basePath.prepend, currentConfig); + this._initHomeData(home, core.http.basePath.prepend, mapsLegacyConfig); features.registerFeature({ id: APP_ID, diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index 1876c0de19c56..6b19103b59722 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -22,6 +22,7 @@ import { EMS_SPRITES_PATH, INDEX_SETTINGS_API_PATH, FONTS_API_PATH, + API_ROOT_PATH, } from '../common/constants'; import { EMSClient } from '@elastic/ems-client'; import fetch from 'node-fetch'; @@ -30,8 +31,7 @@ import { getIndexPatternSettings } from './lib/get_index_pattern_settings'; import { schema } from '@kbn/config-schema'; import fs from 'fs'; import path from 'path'; - -const ROOT = `/${GIS_API_PATH}`; +import { initMVTRoutes } from './mvt/mvt_routes'; export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { let emsClient; @@ -69,7 +69,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_FILES_API_PATH}/${EMS_FILES_DEFAULT_JSON_PATH}`, + path: `${API_ROOT_PATH}/${EMS_FILES_API_PATH}/${EMS_FILES_DEFAULT_JSON_PATH}`, validate: { query: schema.object({ id: schema.maybe(schema.string()), @@ -109,7 +109,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_RASTER_TILE_PATH}`, + path: `${API_ROOT_PATH}/${EMS_TILES_API_PATH}/${EMS_TILES_RASTER_TILE_PATH}`, validate: false, }, async (context, request, response) => { @@ -145,7 +145,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_CATALOGUE_PATH}`, + path: `${API_ROOT_PATH}/${EMS_CATALOGUE_PATH}`, validate: false, }, async (context, request, { ok, badRequest }) => { @@ -181,7 +181,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_FILES_CATALOGUE_PATH}/{emsVersion}/manifest`, + path: `${API_ROOT_PATH}/${EMS_FILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, async (context, request, { ok, badRequest }) => { @@ -213,7 +213,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_CATALOGUE_PATH}/{emsVersion}/manifest`, + path: `${API_ROOT_PATH}/${EMS_TILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, async (context, request, { ok, badRequest }) => { @@ -257,7 +257,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_RASTER_STYLE_PATH}`, + path: `${API_ROOT_PATH}/${EMS_TILES_API_PATH}/${EMS_TILES_RASTER_STYLE_PATH}`, validate: { query: schema.object({ id: schema.maybe(schema.string()), @@ -293,7 +293,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_VECTOR_STYLE_PATH}`, + path: `${API_ROOT_PATH}/${EMS_TILES_API_PATH}/${EMS_TILES_VECTOR_STYLE_PATH}`, validate: { query: schema.object({ id: schema.string(), @@ -341,7 +341,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_VECTOR_SOURCE_PATH}`, + path: `${API_ROOT_PATH}/${EMS_TILES_API_PATH}/${EMS_TILES_VECTOR_SOURCE_PATH}`, validate: { query: schema.object({ id: schema.string(), @@ -379,7 +379,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_VECTOR_TILE_PATH}`, + path: `${API_ROOT_PATH}/${EMS_TILES_API_PATH}/${EMS_TILES_VECTOR_TILE_PATH}`, validate: { query: schema.object({ id: schema.string(), @@ -417,7 +417,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_GLYPHS_PATH}/{fontstack}/{range}`, + path: `${API_ROOT_PATH}/${EMS_TILES_API_PATH}/${EMS_GLYPHS_PATH}/{fontstack}/{range}`, validate: { params: schema.object({ fontstack: schema.string(), @@ -439,7 +439,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { router.get( { - path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_SPRITES_PATH}/{id}/sprite{scaling?}.{extension}`, + path: `${API_ROOT_PATH}/${EMS_TILES_API_PATH}/${EMS_SPRITES_PATH}/{id}/sprite{scaling?}.{extension}`, validate: { query: schema.object({ elastic_tile_service_tos: schema.maybe(schema.string()), @@ -591,4 +591,6 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return response.badRequest(`Cannot connect to EMS`); } } + + initMVTRoutes({ router, logger }); } diff --git a/x-pack/plugins/maps/server/tutorials/ems/index.ts b/x-pack/plugins/maps/server/tutorials/ems/index.ts index be15120cb19e1..867daddfd7b03 100644 --- a/x-pack/plugins/maps/server/tutorials/ems/index.ts +++ b/x-pack/plugins/maps/server/tutorials/ems/index.ts @@ -60,7 +60,7 @@ Indexing EMS administrative boundaries in Elasticsearch allows for search on bou }), textPre: i18n.translate('xpack.maps.tutorials.ems.uploadStepText', { defaultMessage: - '1. Open [Elastic Maps]({newMapUrl}).\n\ + '1. Open [Maps]({newMapUrl}).\n\ 2. Click `Add layer`, then select `Upload GeoJSON`.\n\ 3. Upload the GeoJSON file and click `Import file`.', values: { diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap index 708bddd145393..a132e6682ee25 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap @@ -64,20 +64,12 @@ exports[`DeleteRuleModal renders modal after clicking delete rule link 1`] = ` onConfirm={[Function]} title={ } - > -

- -

- + />
`; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js index 9fcd457df008f..5140fe77ff979 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js @@ -47,7 +47,7 @@ export class DeleteRuleModal extends Component { title={ } onCancel={this.closeModal} @@ -66,14 +66,7 @@ export class DeleteRuleModal extends Component { /> } defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - > -

- -

- + /> ); } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx index 5ffa5e304b996..5db8446dec32f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx @@ -14,7 +14,6 @@ import { EuiFlexItem, EUI_MODAL_CONFIRM_BUTTON, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; import { DeleteAction } from './use_delete_action'; @@ -40,7 +39,7 @@ export const DeleteActionModal: FC = ({ = ({ defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} buttonColor="danger" > -

- -

- {userCanDeleteIndex && ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx index fa559e807f5ea..2048d1144952d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx @@ -17,7 +17,7 @@ export const StartActionModal: FC = ({ closeModal, item, startAndCl = ({ closeModal, item, startAndCl

{i18n.translate('xpack.ml.dataframe.analyticsList.startModalBody', { defaultMessage: - 'A data frame analytics job will increase search and indexing load in your cluster. Please stop the analytics job if excessive load is experienced. Are you sure you want to start this analytics job?', + 'A data frame analytics job increases search and indexing load in your cluster. If excessive load occurs, stop the job.', })}

diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index c4c7a8a4ca11a..88287b963a028 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -5,17 +5,13 @@ */ import React, { FC, useCallback, useState, useEffect } from 'react'; - import { i18n } from '@kbn/i18n'; - import { - Direction, - EuiButton, EuiCallOut, - EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, - EuiInMemoryTable, + EuiBasicTable, + EuiSearchBar, EuiSearchBarProps, EuiSpacer, } from '@elastic/eui'; @@ -43,6 +39,39 @@ import { getGroupQueryText, } from '../../../../../jobs/jobs_list/components/utils'; import { SourceSelection } from '../source_selection'; +import { filterAnalytics, AnalyticsSearchBar } from '../analytics_search_bar'; +import { AnalyticsEmptyPrompt } from './empty_prompt'; +import { useTableSettings } from './use_table_settings'; +import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button'; + +const filters: EuiSearchBarProps['filters'] = [ + { + type: 'field_value_selection', + field: 'job_type', + name: i18n.translate('xpack.ml.dataframe.analyticsList.typeFilter', { + defaultMessage: 'Type', + }), + multiSelect: 'or', + options: Object.values(ANALYSIS_CONFIG_TYPE).map((val) => ({ + value: val, + name: val, + view: getJobTypeBadge(val), + })), + }, + { + type: 'field_value_selection', + field: 'state', + name: i18n.translate('xpack.ml.dataframe.analyticsList.statusFilter', { + defaultMessage: 'Status', + }), + multiSelect: 'or', + options: Object.values(DATA_FRAME_TASK_STATE).map((val) => ({ + value: val, + name: val, + view: getTaskStateBadge(val), + })), + }, +]; function getItemIdToExpandedRowMap( itemIds: DataFrameAnalyticsId[], @@ -70,23 +99,23 @@ export const DataFrameAnalyticsList: FC = ({ const [isInitialized, setIsInitialized] = useState(false); const [isSourceIndexModalVisible, setIsSourceIndexModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); - + const [filteredAnalytics, setFilteredAnalytics] = useState<{ + active: boolean; + items: DataFrameAnalyticsListRow[]; + }>({ + active: false, + items: [], + }); const [searchQueryText, setSearchQueryText] = useState(''); - const [analytics, setAnalytics] = useState([]); const [analyticsStats, setAnalyticsStats] = useState( undefined ); const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); - const [errorMessage, setErrorMessage] = useState(undefined); - const [searchError, setSearchError] = useState(undefined); - - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(10); - - const [sortField, setSortField] = useState(DataFrameAnalyticsListColumn.id); - const [sortDirection, setSortDirection] = useState('asc'); + // Query text/job_id based on url but only after getAnalytics is done first + // selectedJobIdFromUrlInitialized makes sure the query is only run once since analytics is being refreshed constantly + const [selectedIdFromUrlInitialized, setSelectedIdFromUrlInitialized] = useState(false); const disabled = !checkPermission('canCreateDataFrameAnalytics') || @@ -100,9 +129,29 @@ export const DataFrameAnalyticsList: FC = ({ blockRefresh ); - // Query text/job_id based on url but only after getAnalytics is done first - // selectedJobIdFromUrlInitialized makes sure the query is only run once since analytics is being refreshed constantly - const [selectedIdFromUrlInitialized, setSelectedIdFromUrlInitialized] = useState(false); + const setQueryClauses = (queryClauses: any) => { + if (queryClauses.length) { + const filtered = filterAnalytics(analytics, queryClauses); + setFilteredAnalytics({ active: true, items: filtered }); + } else { + setFilteredAnalytics({ active: false, items: [] }); + } + }; + + const filterList = () => { + if (searchQueryText !== '' && selectedIdFromUrlInitialized === true) { + // trigger table filtering with query for job id to trigger table filter + const query = EuiSearchBar.Query.parse(searchQueryText); + let clauses: any = []; + if (query && query.ast !== undefined && query.ast.clauses !== undefined) { + clauses = query.ast.clauses; + } + setQueryClauses(clauses); + } else { + setQueryClauses([]); + } + }; + useEffect(() => { if (selectedIdFromUrlInitialized === false && analytics.length > 0) { const { jobId, groupIds } = getSelectedIdFromUrl(window.location.href); @@ -116,9 +165,15 @@ export const DataFrameAnalyticsList: FC = ({ setSelectedIdFromUrlInitialized(true); setSearchQueryText(queryText); + } else { + filterList(); } }, [selectedIdFromUrlInitialized, analytics]); + useEffect(() => { + filterList(); + }, [selectedIdFromUrlInitialized, searchQueryText]); + const getAnalyticsCallback = useCallback(() => getAnalytics(true), []); // Subscribe to the refresh observable to trigger reloading the analytics list. @@ -137,6 +192,10 @@ export const DataFrameAnalyticsList: FC = ({ isMlEnabledInSpace ); + const { onTableChange, pageOfItems, pagination, sorting } = useTableSettings( + filteredAnalytics.active ? filteredAnalytics.items : analytics + ); + // Before the analytics have been loaded for the first time, display the loading indicator only. // Otherwise a user would see 'No data frame analytics found' during the initial loading. if (!isInitialized) { @@ -160,34 +219,10 @@ export const DataFrameAnalyticsList: FC = ({ if (analytics.length === 0) { return ( <> - - {i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', { - defaultMessage: 'Create your first data frame analytics job', - })} - - } - actions={ - !isManagementTable - ? [ - setIsSourceIndexModalVisible(true)} - isDisabled={disabled} - color="primary" - iconType="plusInCircle" - fill - data-test-subj="mlAnalyticsCreateFirstButton" - > - {i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', { - defaultMessage: 'Create job', - })} - , - ] - : [] - } - data-test-subj="mlNoDataFrameAnalyticsFound" + setIsSourceIndexModalVisible(true)} /> {isSourceIndexModalVisible === true && ( setIsSourceIndexModalVisible(false)} /> @@ -196,95 +231,32 @@ export const DataFrameAnalyticsList: FC = ({ ); } - const sorting = { - sort: { - field: sortField, - direction: sortDirection, - }, - }; - const itemIdToExpandedRowMap = getItemIdToExpandedRowMap(expandedRowItemIds, analytics); - const pagination = { - initialPageIndex: pageIndex, - initialPageSize: pageSize, - totalItemCount: analytics.length, - pageSizeOptions: [10, 20, 50], - hidePerPageOptions: false, - }; - - const handleSearchOnChange: EuiSearchBarProps['onChange'] = (search) => { - if (search.error !== null) { - setSearchError(search.error.message); - return false; - } - - setSearchError(undefined); - setSearchQueryText(search.queryText); - return true; - }; - - const search: EuiSearchBarProps = { - query: searchQueryText, - onChange: handleSearchOnChange, - box: { - incremental: true, - }, - filters: [ - { - type: 'field_value_selection', - field: 'job_type', - name: i18n.translate('xpack.ml.dataframe.analyticsList.typeFilter', { - defaultMessage: 'Type', - }), - multiSelect: 'or', - options: Object.values(ANALYSIS_CONFIG_TYPE).map((val) => ({ - value: val, - name: val, - view: getJobTypeBadge(val), - })), - }, - { - type: 'field_value_selection', - field: 'state', - name: i18n.translate('xpack.ml.dataframe.analyticsList.statusFilter', { - defaultMessage: 'Status', - }), - multiSelect: 'or', - options: Object.values(DATA_FRAME_TASK_STATE).map((val) => ({ - value: val, - name: val, - view: getTaskStateBadge(val), - })), - }, - ], - }; - - const onTableChange: EuiInMemoryTable['onTableChange'] = ({ - page = { index: 0, size: 10 }, - sort = { field: DataFrameAnalyticsListColumn.id, direction: 'asc' }, - }) => { - const { index, size } = page; - setPageIndex(index); - setPageSize(size); + const stats = analyticsStats && ( + + + + ); - const { field, direction } = sort; - setSortField(field); - setSortDirection(direction); - }; + const managementStats = ( + + + {stats} + + + + + + ); return ( <> {modals} - + {!isManagementTable && } - - {analyticsStats && ( - - - - )} - + {!isManagementTable && stats} + {isManagementTable && managementStats} {!isManagementTable && ( @@ -300,22 +272,25 @@ export const DataFrameAnalyticsList: FC = ({
- + + className="mlAnalyticsTable" columns={columns} - error={searchError} hasActions={false} isExpandable={true} isSelectable={false} - items={analytics} + items={pageOfItems} itemId={DataFrameAnalyticsListColumn.id} itemIdToExpandedRowMap={itemIdToExpandedRowMap} loading={isLoading} - onTableChange={onTableChange} - pagination={pagination} + onChange={onTableChange} + pagination={pagination!} sorting={sorting} - search={search} data-test-subj={isLoading ? 'mlAnalyticsTable loading' : 'mlAnalyticsTable loaded'} rowProps={(item) => ({ 'data-test-subj': `mlAnalyticsTableRow row-${item.id}`, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index 774864ae964a8..994357412510d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -26,6 +26,7 @@ export type Clause = Parameters[0]; type ExtractClauseType = T extends (x: any) => x is infer Type ? Type : never; export type TermClause = ExtractClauseType; export type FieldClause = ExtractClauseType; +export type Value = Parameters[0]; interface ProgressSection { phase: string; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/empty_prompt.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/empty_prompt.tsx new file mode 100644 index 0000000000000..fb173697b4572 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/empty_prompt.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + disabled: boolean; + isManagementTable: boolean; + onCreateFirstJobClick: () => void; +} + +export const AnalyticsEmptyPrompt: FC = ({ + disabled, + isManagementTable, + onCreateFirstJobClick, +}) => ( + + {i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', { + defaultMessage: 'Create your first data frame analytics job', + })} + + } + actions={ + !isManagementTable + ? [ + + {i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', { + defaultMessage: 'Create job', + })} + , + ] + : [] + } + data-test-subj="mlNoDataFrameAnalyticsFound" + /> +); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx index 7001681b6917a..ef1d373a55a12 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx @@ -23,7 +23,6 @@ import { getJobIdUrl, TAB_IDS } from '../../../../../util/get_selected_ids_url'; import { getAnalysisType, DataFrameAnalyticsId } from '../../../../common'; import { - getDataFrameAnalyticsProgress, getDataFrameAnalyticsProgressPhase, isDataFrameAnalyticsFailed, isDataFrameAnalyticsRunning, @@ -76,7 +75,6 @@ export const progressColumn = { name: i18n.translate('xpack.ml.dataframe.analyticsList.progress', { defaultMessage: 'Progress', }), - sortable: (item: DataFrameAnalyticsListRow) => getDataFrameAnalyticsProgress(item.stats), truncateText: true, render(item: DataFrameAnalyticsListRow) { const { currentPhase, progress, totalPhases } = getDataFrameAnalyticsProgressPhase(item.stats); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts new file mode 100644 index 0000000000000..57eb9f6857053 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState } from 'react'; +import { Direction, EuiBasicTableProps, EuiTableSortingType } from '@elastic/eui'; +import sortBy from 'lodash/sortBy'; +import get from 'lodash/get'; +import { DataFrameAnalyticsListColumn, DataFrameAnalyticsListRow } from './common'; + +const PAGE_SIZE = 10; +const PAGE_SIZE_OPTIONS = [10, 25, 50]; + +const jobPropertyMap = { + ID: 'id', + Status: 'state', + Type: 'job_type', +}; + +interface AnalyticsBasicTableSettings { + pageIndex: number; + pageSize: number; + totalItemCount: number; + hidePerPageOptions: boolean; + sortField: string; + sortDirection: Direction; +} + +interface UseTableSettingsReturnValue { + onTableChange: EuiBasicTableProps['onChange']; + pageOfItems: DataFrameAnalyticsListRow[]; + pagination: EuiBasicTableProps['pagination']; + sorting: EuiTableSortingType; +} + +export function useTableSettings(items: DataFrameAnalyticsListRow[]): UseTableSettingsReturnValue { + const [tableSettings, setTableSettings] = useState({ + pageIndex: 0, + pageSize: PAGE_SIZE, + totalItemCount: 0, + hidePerPageOptions: false, + sortField: DataFrameAnalyticsListColumn.id, + sortDirection: 'asc', + }); + + const getPageOfItems = ( + list: any[], + index: number, + size: number, + sortField: string, + sortDirection: Direction + ) => { + list = sortBy(list, (item) => + get(item, jobPropertyMap[sortField as keyof typeof jobPropertyMap] || sortField) + ); + list = sortDirection === 'asc' ? list : list.reverse(); + const listLength = list.length; + + let pageStart = index * size; + if (pageStart >= listLength && listLength !== 0) { + // if the page start is larger than the number of items due to + // filters being applied or items being deleted, calculate a new page start + pageStart = Math.floor((listLength - 1) / size) * size; + + setTableSettings({ ...tableSettings, pageIndex: pageStart / size }); + } + return { + pageOfItems: list.slice(pageStart, pageStart + size), + totalItemCount: listLength, + }; + }; + + const onTableChange = ({ + page = { index: 0, size: PAGE_SIZE }, + sort = { field: DataFrameAnalyticsListColumn.id, direction: 'asc' }, + }: { + page?: { index: number; size: number }; + sort?: { field: string; direction: Direction }; + }) => { + const { index, size } = page; + const { field, direction } = sort; + + setTableSettings({ + ...tableSettings, + pageIndex: index, + pageSize: size, + sortField: field, + sortDirection: direction, + }); + }; + + const { pageIndex, pageSize, sortField, sortDirection } = tableSettings; + + const { pageOfItems, totalItemCount } = getPageOfItems( + items, + pageIndex, + pageSize, + sortField, + sortDirection + ); + + const pagination = { + pageIndex, + pageSize, + totalItemCount, + pageSizeOptions: PAGE_SIZE_OPTIONS, + }; + + const sorting = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + + return { onTableChange, pageOfItems, pagination, sorting }; +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_search_bar/analytics_search_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_search_bar/analytics_search_bar.tsx new file mode 100644 index 0000000000000..44a6572a3766c --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_search_bar/analytics_search_bar.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Dispatch, SetStateAction, FC, Fragment, useState } from 'react'; +import { + EuiSearchBar, + EuiSearchBarProps, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { stringMatch } from '../../../../../util/string_utils'; +import { + TermClause, + FieldClause, + Value, + DataFrameAnalyticsListRow, +} from '../analytics_list/common'; + +export function filterAnalytics( + items: DataFrameAnalyticsListRow[], + clauses: Array +) { + if (clauses.length === 0) { + return items; + } + + // keep count of the number of matches we make as we're looping over the clauses + // we only want to return items which match all clauses, i.e. each search term is ANDed + const matches: Record = items.reduce((p: Record, c) => { + p[c.id] = { + job: c, + count: 0, + }; + return p; + }, {}); + + clauses.forEach((c) => { + // the search term could be negated with a minus, e.g. -bananas + const bool = c.match === 'must'; + let js = []; + + if (c.type === 'term') { + // filter term based clauses, e.g. bananas + // match on id, description and memory_status + // if the term has been negated, AND the matches + if (bool === true) { + js = items.filter( + (item) => + stringMatch(item.id, c.value) === bool || + stringMatch(item.config.description, c.value) === bool || + stringMatch(item.stats?.memory_usage?.status, c.value) === bool + ); + } else { + js = items.filter( + (item) => + stringMatch(item.id, c.value) === bool && + stringMatch(item.config.description, c.value) === bool && + stringMatch(item.stats?.memory_usage?.status, c.value) === bool + ); + } + } else { + // filter other clauses, i.e. the filters for type and status + if (Array.isArray(c.value)) { + // job type value and status value are an array of string(s) e.g. c.value => ['failed', 'stopped'] + js = items.filter((item) => + (c.value as Value[]).includes( + item[c.field as keyof Pick] + ) + ); + } else { + js = items.filter( + (item) => item[c.field as keyof Pick] === c.value + ); + } + } + + js.forEach((j) => matches[j.id].count++); + }); + + // loop through the matches and return only those items which have match all the clauses + const filtered = Object.values(matches) + .filter((m) => (m && m.count) >= clauses.length) + .map((m) => m.job); + + return filtered; +} + +function getError(errorMessage: string | null) { + if (errorMessage) { + return i18n.translate('xpack.ml.analyticList.searchBar.invalidSearchErrorMessage', { + defaultMessage: 'Invalid search: {errorMessage}', + values: { errorMessage }, + }); + } + + return ''; +} + +interface Props { + filters: EuiSearchBarProps['filters']; + searchQueryText: string; + setSearchQueryText: Dispatch>; +} + +export const AnalyticsSearchBar: FC = ({ filters, searchQueryText, setSearchQueryText }) => { + const [errorMessage, setErrorMessage] = useState(null); + + const onChange: EuiSearchBarProps['onChange'] = ({ query, error }) => { + if (error) { + setErrorMessage(error.message); + } else if (query !== null && query.text !== undefined) { + setSearchQueryText(query.text); + setErrorMessage(null); + } + }; + + return ( + + + {searchQueryText === undefined && ( + + )} + {searchQueryText !== undefined && ( + + )} + + + + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/no_match/index.js b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_search_bar/index.ts similarity index 75% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/no_match/index.js rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_search_bar/index.ts index 63e8cdebd9771..3b901f5063eb1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/no_match/index.js +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_search_bar/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { NoMatch } from './components/no_match'; +export { AnalyticsSearchBar, filterAnalytics } from './analytics_search_bar'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx index 83010c684473e..3c2ba13a1db26 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -15,7 +15,6 @@ import { EuiModalFooter, EuiButtonEmpty, EuiButton, - EuiSpacer, EuiCallOut, } from '@elastic/eui'; import { ModelItemFull } from './models_list'; @@ -37,7 +36,7 @@ export const DeleteModelsModal: FC = ({ models, onClose = ({ models, onClose - - {modelsWithPipelines.length > 0 && ( = ({ error }) => { const errorObj = toString(error); return ( - -

{errorObj.msg}

+ <> +

{errorObj.msg}

{errorObj.more !== undefined && ( )} -
+ ); -} +}; function toString(error: any): ImportError { if (typeof error === 'string') { @@ -127,11 +127,11 @@ function toString(error: any): ImportError { return { msg: error.msg }; } else if (error.error !== undefined) { if (typeof error.error === 'object') { - if (error.error.msg !== undefined) { + if (error.error.reason !== undefined) { // this will catch a bulk ingest failure - const errorObj: ImportError = { msg: error.error.msg }; - if (error.error.body !== undefined) { - errorObj.more = error.error.response; + const errorObj: ImportError = { msg: error.error.reason }; + if (error.error.root_cause !== undefined) { + errorObj.more = JSON.stringify(error.error.root_cause); } return errorObj; } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js index 1e3ec6241311b..f80578cb18341 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.js @@ -98,7 +98,7 @@ export class DeleteJobModal extends Component { const title = ( -

- -

} - > -

- -

- + />
`; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js index 7f8bf06ecb8a3..99cd14063e86a 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js @@ -58,7 +58,7 @@ export class DeleteFilterListModal extends Component { const title = ( -

- -

-
+ /> ); } diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 4f8ceb8effe98..214b393a0fda9 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -96,12 +96,7 @@ export class MlPlugin implements Plugin { uiActions: pluginsStart.uiActions, kibanaVersion, }, - { - element: params.element, - appBasePath: params.appBasePath, - onAppLeave: params.onAppLeave, - history: params.history, - } + params ); }, }); diff --git a/x-pack/plugins/ml/server/client/elasticsearch_ml.test.ts b/x-pack/plugins/ml/server/client/elasticsearch_ml.test.ts deleted file mode 100644 index 5ad0db3c58ce4..0000000000000 --- a/x-pack/plugins/ml/server/client/elasticsearch_ml.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { elasticsearchJsPlugin } from './elasticsearch_ml'; - -interface Endpoint { - fmt: string; -} - -interface ClientAction { - urls?: Endpoint[]; - url: Endpoint; -} - -describe('ML - Endpoints', () => { - // Check all paths in the ML elasticsearchJsPlugin start with a leading forward slash - // so they work if Kibana is run behind a reverse proxy - const PATH_START: string = '/'; - const urls: string[] = []; - - // Stub objects - const Client = { - prototype: {}, - }; - - const components = { - clientAction: { - factory(obj: ClientAction) { - // add each endpoint URL to a list - if (obj.urls) { - obj.urls.forEach((url) => { - urls.push(url.fmt); - }); - } - if (obj.url) { - urls.push(obj.url.fmt); - } - }, - namespaceFactory() { - return { - prototype: {}, - }; - }, - }, - }; - - // Stub elasticsearchJsPlugin - elasticsearchJsPlugin(Client, null, components); - - describe('paths', () => { - it(`should start with ${PATH_START}`, () => { - urls.forEach((url) => { - expect(url[0]).toEqual(PATH_START); - }); - }); - }); -}); diff --git a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts deleted file mode 100644 index 63153d18cb10b..0000000000000 --- a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts +++ /dev/null @@ -1,929 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { - const ca = components.clientAction.factory; - - Client.prototype.ml = components.clientAction.namespaceFactory(); - const ml = Client.prototype.ml.prototype; - - /** - * Perform a [ml.authenticate](Retrieve details about the currently authenticated user) request - * - * @param {Object} params - An object with parameters used to carry out this action - */ - ml.jobs = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>', - req: { - jobId: { - type: 'list', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/', - }, - ], - method: 'GET', - }); - - ml.jobStats = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/_stats', - req: { - jobId: { - type: 'list', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/_stats', - }, - ], - method: 'GET', - }); - - ml.addJob = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'PUT', - }); - - ml.openJob = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/_open', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ml.closeJob = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/_close?force=<%=force%>', - req: { - jobId: { - type: 'string', - }, - force: { - type: 'boolean', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/_close', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - // Currently the endpoint uses a default size of 100 unless a size is supplied. - // So until paging is supported in the UI, explicitly supply a size of 1000 - // to match the max number of docs that the endpoint can return. - ml.getDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/<%=analyticsId%>', - req: { - analyticsId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/data_frame/analytics/_all?size=1000', - }, - ], - method: 'GET', - }); - - ml.getDataFrameAnalyticsStats = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/<%=analyticsId%>/_stats', - req: { - analyticsId: { - type: 'string', - }, - }, - }, - { - // Currently the endpoint uses a default size of 100 unless a size is supplied. - // So until paging is supported in the UI, explicitly supply a size of 1000 - // to match the max number of docs that the endpoint can return. - fmt: '/_ml/data_frame/analytics/_all/_stats?size=1000', - }, - ], - method: 'GET', - }); - - ml.createDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/<%=analyticsId%>', - req: { - analyticsId: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'PUT', - }); - - ml.evaluateDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/_evaluate', - }, - ], - needBody: true, - method: 'POST', - }); - - ml.explainDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/_explain', - }, - ], - needBody: true, - method: 'POST', - }); - - ml.deleteDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/<%=analyticsId%>', - req: { - analyticsId: { - type: 'string', - }, - }, - }, - ], - method: 'DELETE', - }); - - ml.startDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/<%=analyticsId%>/_start', - req: { - analyticsId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ml.stopDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/<%=analyticsId%>/_stop?&force=<%=force%>', - req: { - analyticsId: { - type: 'string', - }, - force: { - type: 'boolean', - }, - }, - }, - ], - method: 'POST', - }); - - ml.updateDataFrameAnalytics = ca({ - urls: [ - { - fmt: '/_ml/data_frame/analytics/<%=analyticsId%>/_update', - req: { - analyticsId: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'POST', - }); - - ml.deleteJob = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>?&force=<%=force%>&wait_for_completion=false', - req: { - jobId: { - type: 'string', - }, - force: { - type: 'boolean', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>?&wait_for_completion=false', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - method: 'DELETE', - }); - - ml.updateJob = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/_update', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'POST', - }); - - ml.datafeeds = ca({ - urls: [ - { - fmt: '/_ml/datafeeds/<%=datafeedId%>', - req: { - datafeedId: { - type: 'list', - }, - }, - }, - { - fmt: '/_ml/datafeeds/', - }, - ], - method: 'GET', - }); - - ml.datafeedStats = ca({ - urls: [ - { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_stats', - req: { - datafeedId: { - type: 'list', - }, - }, - }, - { - fmt: '/_ml/datafeeds/_stats', - }, - ], - method: 'GET', - }); - - ml.addDatafeed = ca({ - urls: [ - { - fmt: '/_ml/datafeeds/<%=datafeedId%>', - req: { - datafeedId: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'PUT', - }); - - ml.updateDatafeed = ca({ - urls: [ - { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_update', - req: { - datafeedId: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'POST', - }); - - ml.deleteDatafeed = ca({ - urls: [ - { - fmt: '/_ml/datafeeds/<%=datafeedId%>?force=<%=force%>', - req: { - datafeedId: { - type: 'string', - }, - force: { - type: 'boolean', - }, - }, - }, - { - fmt: '/_ml/datafeeds/<%=datafeedId%>', - req: { - datafeedId: { - type: 'string', - }, - }, - }, - ], - method: 'DELETE', - }); - - ml.startDatafeed = ca({ - urls: [ - { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_start?&start=<%=start%>&end=<%=end%>', - req: { - datafeedId: { - type: 'string', - }, - start: { - type: 'string', - }, - end: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_start?&start=<%=start%>', - req: { - datafeedId: { - type: 'string', - }, - start: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_start', - req: { - datafeedId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ml.stopDatafeed = ca({ - urls: [ - { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_stop?force=<%=force%>', - req: { - datafeedId: { - type: 'string', - }, - force: { - type: 'boolean', - }, - }, - }, - { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_stop', - req: { - datafeedId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ml.validateDetector = ca({ - url: { - fmt: '/_ml/anomaly_detectors/_validate/detector', - }, - needBody: true, - method: 'POST', - }); - - ml.estimateModelMemory = ca({ - url: { - fmt: '/_ml/anomaly_detectors/_estimate_model_memory', - }, - needBody: true, - method: 'POST', - }); - - ml.datafeedPreview = ca({ - url: { - fmt: '/_ml/datafeeds/<%=datafeedId%>/_preview', - req: { - datafeedId: { - type: 'string', - }, - }, - }, - method: 'GET', - }); - - ml.forecast = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/_forecast?&duration=<%=duration%>', - req: { - jobId: { - type: 'string', - }, - duration: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/_forecast', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ml.records = ca({ - url: { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/results/records', - req: { - jobId: { - type: 'string', - }, - }, - }, - method: 'POST', - }); - - ml.buckets = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/results/buckets', - req: { - jobId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/results/buckets/<%=timestamp%>', - req: { - jobId: { - type: 'string', - }, - timestamp: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ml.overallBuckets = ca({ - url: { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/results/overall_buckets', - req: { - jobId: { - type: 'string', - }, - }, - }, - method: 'POST', - }); - - ml.privilegeCheck = ca({ - url: { - fmt: '/_security/user/_has_privileges', - }, - needBody: true, - method: 'POST', - }); - // Currently the endpoint uses a default size of 100 unless a size is supplied. So until paging is supported in the UI, explicitly supply a size of 1000 - ml.calendars = ca({ - urls: [ - { - fmt: '/_ml/calendars/<%=calendarId%>', - req: { - calendarId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/calendars?size=1000', - }, - ], - method: 'GET', - }); - - ml.deleteCalendar = ca({ - url: { - fmt: '/_ml/calendars/<%=calendarId%>', - req: { - calendarId: { - type: 'string', - }, - }, - }, - method: 'DELETE', - }); - - ml.addCalendar = ca({ - url: { - fmt: '/_ml/calendars/<%=calendarId%>', - req: { - calendarId: { - type: 'string', - }, - }, - }, - needBody: true, - method: 'PUT', - }); - - ml.addJobToCalendar = ca({ - url: { - fmt: '/_ml/calendars/<%=calendarId%>/jobs/<%=jobId%>', - req: { - calendarId: { - type: 'string', - }, - jobId: { - type: 'string', - }, - }, - }, - method: 'PUT', - }); - - ml.removeJobFromCalendar = ca({ - url: { - fmt: '/_ml/calendars/<%=calendarId%>/jobs/<%=jobId%>', - req: { - calendarId: { - type: 'string', - }, - jobId: { - type: 'string', - }, - }, - }, - method: 'DELETE', - }); - - ml.events = ca({ - urls: [ - { - fmt: '/_ml/calendars/<%=calendarId%>/events', - req: { - calendarId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/calendars/<%=calendarId%>/events?&job_id=<%=jobId%>', - req: { - calendarId: { - type: 'string', - }, - jobId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/calendars/<%=calendarId%>/events?&after=<%=start%>&before=<%=end%>', - req: { - calendarId: { - type: 'string', - }, - start: { - type: 'string', - }, - end: { - type: 'string', - }, - }, - }, - { - fmt: - '/_ml/calendars/<%=calendarId%>/events?&after=<%=start%>&before=<%=end%>&job_id=<%=jobId%>', - req: { - calendarId: { - type: 'string', - }, - start: { - type: 'string', - }, - end: { - type: 'string', - }, - jobId: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - ml.addEvent = ca({ - url: { - fmt: '/_ml/calendars/<%=calendarId%>/events', - req: { - calendarId: { - type: 'string', - }, - }, - }, - needBody: true, - method: 'POST', - }); - - ml.deleteEvent = ca({ - url: { - fmt: '/_ml/calendars/<%=calendarId%>/events/<%=eventId%>', - req: { - calendarId: { - type: 'string', - }, - eventId: { - type: 'string', - }, - }, - }, - method: 'DELETE', - }); - // Currently the endpoint uses a default size of 100 unless a size is supplied. So until paging is supported in the UI, explicitly supply a size of 1000 - ml.filters = ca({ - urls: [ - { - fmt: '/_ml/filters/<%=filterId%>', - req: { - filterId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/filters?size=1000', - }, - ], - method: 'GET', - }); - - ml.addFilter = ca({ - url: { - fmt: '/_ml/filters/<%=filterId%>', - req: { - filterId: { - type: 'string', - }, - }, - }, - needBody: true, - method: 'PUT', - }); - - ml.updateFilter = ca({ - urls: [ - { - fmt: '/_ml/filters/<%=filterId%>/_update', - req: { - filterId: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'POST', - }); - - ml.deleteFilter = ca({ - url: { - fmt: '/_ml/filters/<%=filterId%>', - req: { - filterId: { - type: 'string', - }, - }, - }, - method: 'DELETE', - }); - - ml.info = ca({ - url: { - fmt: '/_ml/info', - }, - method: 'GET', - }); - - ml.fileStructure = ca({ - urls: [ - { - fmt: - '/_ml/find_file_structure?&explain=true&charset=<%=charset%>&format=<%=format%>&has_header_row=<%=has_header_row%>&column_names=<%=column_names%>&delimiter=<%=delimiter%>"e=<%=quote%>&should_trim_fields=<%=should_trim_fields%>&grok_pattern=<%=grok_pattern%>×tamp_field=<%=timestamp_field%>×tamp_format=<%=timestamp_format%>&lines_to_sample=<%=lines_to_sample%>', - req: { - charset: { - type: 'string', - }, - format: { - type: 'string', - }, - has_header_row: { - type: 'string', - }, - column_names: { - type: 'string', - }, - delimiter: { - type: 'string', - }, - quote: { - type: 'string', - }, - should_trim_fields: { - type: 'string', - }, - grok_pattern: { - type: 'string', - }, - timestamp_field: { - type: 'string', - }, - timestamp_format: { - type: 'string', - }, - lines_to_sample: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/find_file_structure?&explain=true', - }, - ], - needBody: true, - method: 'POST', - }); - - ml.rollupIndexCapabilities = ca({ - urls: [ - { - fmt: '/<%=indexPattern%>/_rollup/data', - req: { - indexPattern: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - ml.categories = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/results/categories/<%=categoryId%>', - req: { - jobId: { - type: 'string', - }, - categoryId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/results/categories', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - ml.modelSnapshots = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>', - req: { - jobId: { - type: 'string', - }, - snapshotId: { - type: 'string', - }, - }, - }, - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/model_snapshots', - req: { - jobId: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - ml.updateModelSnapshot = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>/_update', - req: { - jobId: { - type: 'string', - }, - snapshotId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - needBody: true, - }); - - ml.deleteModelSnapshot = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>', - req: { - jobId: { - type: 'string', - }, - snapshotId: { - type: 'string', - }, - }, - }, - ], - method: 'DELETE', - }); - - ml.revertModelSnapshot = ca({ - urls: [ - { - fmt: '/_ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>/_revert', - req: { - jobId: { - type: 'string', - }, - snapshotId: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); -}; diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts index 21d32813c0d51..4dd17f8cf4889 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { getAdminCapabilities, getUserCapabilities } from './__mocks__/ml_capabilities'; import { capabilitiesProvider } from './check_capabilities'; import { MlLicense } from '../../../common/license'; @@ -24,16 +24,28 @@ const mlIsEnabled = async () => true; const mlIsNotEnabled = async () => false; const mlClusterClientNonUpgrade = ({ - callAsInternalUser: async () => ({ - upgrade_mode: false, - }), -} as unknown) as ILegacyScopedClusterClient; + asInternalUser: { + ml: { + info: async () => ({ + body: { + upgrade_mode: false, + }, + }), + }, + }, +} as unknown) as IScopedClusterClient; const mlClusterClientUpgrade = ({ - callAsInternalUser: async () => ({ - upgrade_mode: true, - }), -} as unknown) as ILegacyScopedClusterClient; + asInternalUser: { + ml: { + info: async () => ({ + body: { + upgrade_mode: true, + }, + }), + }, + }, +} as unknown) as IScopedClusterClient; describe('check_capabilities', () => { describe('getCapabilities() - right number of capabilities', () => { diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts index c976ab598b28c..c591ec07c7c3b 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, KibanaRequest } from 'kibana/server'; +import { IScopedClusterClient, KibanaRequest } from 'kibana/server'; import { mlLog } from '../../client/log'; import { MlCapabilities, @@ -22,12 +22,12 @@ import { } from './errors'; export function capabilitiesProvider( - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, capabilities: MlCapabilities, mlLicense: MlLicense, isMlEnabledInSpace: () => Promise ) { - const { isUpgradeInProgress } = upgradeCheckProvider(mlClusterClient); + const { isUpgradeInProgress } = upgradeCheckProvider(client); async function getCapabilities(): Promise { const upgradeInProgress = await isUpgradeInProgress(); const isPlatinumOrTrialLicense = mlLicense.isFullLicense(); diff --git a/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts b/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts index 6df4d0c87ecf5..defb70429fa0c 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts @@ -4,17 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { mlLog } from '../../client/log'; -export function upgradeCheckProvider({ callAsInternalUser }: ILegacyScopedClusterClient) { +export function upgradeCheckProvider({ asInternalUser }: IScopedClusterClient) { async function isUpgradeInProgress(): Promise { let upgradeInProgress = false; try { - const info = await callAsInternalUser('ml.info'); + const { body } = await asInternalUser.ml.info(); // if ml indices are currently being migrated, upgrade_mode will be set to true // pass this back with the privileges to allow for the disabling of UI controls. - upgradeInProgress = info.upgrade_mode === true; + upgradeInProgress = body.upgrade_mode === true; } catch (error) { // if the ml.info check fails, it could be due to the user having insufficient privileges // most likely they do not have the ml_user role and therefore will be blocked from using diff --git a/x-pack/plugins/ml/server/lib/check_annotations/index.ts b/x-pack/plugins/ml/server/lib/check_annotations/index.ts index de19f0ead6791..964f9d0b92261 100644 --- a/x-pack/plugins/ml/server/lib/check_annotations/index.ts +++ b/x-pack/plugins/ml/server/lib/check_annotations/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { mlLog } from '../../client/log'; import { @@ -17,18 +17,16 @@ import { // - ML_ANNOTATIONS_INDEX_PATTERN index is present // - ML_ANNOTATIONS_INDEX_ALIAS_READ alias is present // - ML_ANNOTATIONS_INDEX_ALIAS_WRITE alias is present -export async function isAnnotationsFeatureAvailable({ - callAsInternalUser, -}: ILegacyScopedClusterClient) { +export async function isAnnotationsFeatureAvailable({ asInternalUser }: IScopedClusterClient) { try { const indexParams = { index: ML_ANNOTATIONS_INDEX_PATTERN }; - const annotationsIndexExists = await callAsInternalUser('indices.exists', indexParams); + const { body: annotationsIndexExists } = await asInternalUser.indices.exists(indexParams); if (!annotationsIndexExists) { return false; } - const annotationsReadAliasExists = await callAsInternalUser('indices.existsAlias', { + const { body: annotationsReadAliasExists } = await asInternalUser.indices.existsAlias({ index: ML_ANNOTATIONS_INDEX_ALIAS_READ, name: ML_ANNOTATIONS_INDEX_ALIAS_READ, }); @@ -37,7 +35,7 @@ export async function isAnnotationsFeatureAvailable({ return false; } - const annotationsWriteAliasExists = await callAsInternalUser('indices.existsAlias', { + const { body: annotationsWriteAliasExists } = await asInternalUser.indices.existsAlias({ index: ML_ANNOTATIONS_INDEX_ALIAS_WRITE, name: ML_ANNOTATIONS_INDEX_ALIAS_WRITE, }); diff --git a/x-pack/plugins/ml/server/lib/license/ml_server_license.ts b/x-pack/plugins/ml/server/lib/license/ml_server_license.ts index bd0a29721248a..6e3019b303b88 100644 --- a/x-pack/plugins/ml/server/lib/license/ml_server_license.ts +++ b/x-pack/plugins/ml/server/lib/license/ml_server_license.ts @@ -7,7 +7,6 @@ import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext, - ILegacyScopedClusterClient, IScopedClusterClient, RequestHandler, } from 'kibana/server'; @@ -15,7 +14,6 @@ import { import { MlLicense } from '../../../common/license'; type Handler = (handlerParams: { - legacyClient: ILegacyScopedClusterClient; client: IScopedClusterClient; request: KibanaRequest; response: KibanaResponseFactory; @@ -42,7 +40,6 @@ function guard(check: () => boolean, handler: Handler) { } return handler({ - legacyClient: context.ml!.mlClient, client: context.core.elasticsearch.client, request, response, diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts index 5be443266ffe1..4c511b567615d 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts @@ -22,19 +22,15 @@ describe('annotation_service', () => { let mlClusterClientSpy = {} as any; beforeEach(() => { - const callAs = jest.fn((action: string) => { - switch (action) { - case 'delete': - case 'index': - return Promise.resolve(acknowledgedResponseMock); - case 'search': - return Promise.resolve(getAnnotationsResponseMock); - } - }); + const callAs = { + delete: jest.fn(() => Promise.resolve({ body: acknowledgedResponseMock })), + index: jest.fn(() => Promise.resolve({ body: acknowledgedResponseMock })), + search: jest.fn(() => Promise.resolve({ body: getAnnotationsResponseMock })), + }; mlClusterClientSpy = { - callAsCurrentUser: callAs, - callAsInternalUser: callAs, + asCurrentUser: callAs, + asInternalUser: callAs, }; }); @@ -52,8 +48,7 @@ describe('annotation_service', () => { const response = await deleteAnnotation(annotationMockId); - expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('delete'); - expect(mockFunct.callAsInternalUser.mock.calls[0][1]).toEqual(deleteParamsMock); + expect(mockFunct.asInternalUser.delete.mock.calls[0][0]).toStrictEqual(deleteParamsMock); expect(response).toBe(acknowledgedResponseMock); done(); }); @@ -73,8 +68,9 @@ describe('annotation_service', () => { const response: GetResponse = await getAnnotations(indexAnnotationArgsMock); - expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('search'); - expect(mockFunct.callAsInternalUser.mock.calls[0][1]).toEqual(getAnnotationsRequestMock); + expect(mockFunct.asInternalUser.search.mock.calls[0][0]).toStrictEqual( + getAnnotationsRequestMock + ); expect(Object.keys(response.annotations)).toHaveLength(1); expect(response.annotations[jobIdMock]).toHaveLength(2); expect(isAnnotations(response.annotations[jobIdMock])).toBeTruthy(); @@ -89,9 +85,9 @@ describe('annotation_service', () => { }; const mlClusterClientSpyError: any = { - callAsInternalUser: jest.fn(() => { - return Promise.resolve(mockEsError); - }), + asInternalUser: { + search: jest.fn(() => Promise.resolve({ body: mockEsError })), + }, }; const { getAnnotations } = annotationServiceProvider(mlClusterClientSpyError); @@ -124,10 +120,8 @@ describe('annotation_service', () => { const response = await indexAnnotation(annotationMock, usernameMock); - expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('index'); - // test if the annotation has been correctly augmented - const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[0][1]; + const indexParamsCheck = mockFunct.asInternalUser.index.mock.calls[0][0]; const annotation = indexParamsCheck.body; expect(annotation.create_username).toBe(usernameMock); expect(annotation.modified_username).toBe(usernameMock); @@ -154,10 +148,8 @@ describe('annotation_service', () => { const response = await indexAnnotation(annotationMock, usernameMock); - expect(mockFunct.callAsInternalUser.mock.calls[0][0]).toBe('index'); - // test if the annotation has been correctly augmented - const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[0][1]; + const indexParamsCheck = mockFunct.asInternalUser.index.mock.calls[0][0]; const annotation = indexParamsCheck.body; expect(annotation.create_username).toBe(usernameMock); expect(annotation.modified_username).toBe(usernameMock); @@ -196,9 +188,8 @@ describe('annotation_service', () => { await indexAnnotation(annotation, modifiedUsernameMock); - expect(mockFunct.callAsInternalUser.mock.calls[1][0]).toBe('index'); // test if the annotation has been correctly updated - const indexParamsCheck = mockFunct.callAsInternalUser.mock.calls[1][1]; + const indexParamsCheck = mockFunct.asInternalUser.index.mock.calls[0][0]; const modifiedAnnotation = indexParamsCheck.body; expect(modifiedAnnotation.annotation).toBe(modifiedAnnotationText); expect(modifiedAnnotation.create_username).toBe(originalUsernameMock); diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts index a585449db0a25..24f1d6951c940 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts @@ -7,7 +7,7 @@ import Boom from 'boom'; import each from 'lodash/each'; import get from 'lodash/get'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { ANNOTATION_EVENT_USER, ANNOTATION_TYPE } from '../../../common/constants/annotations'; import { PARTITION_FIELDS } from '../../../common/constants/anomalies'; @@ -67,17 +67,17 @@ export interface GetResponse { export interface IndexParams { index: string; body: Annotation; - refresh?: string; + refresh: boolean | 'wait_for' | undefined; id?: string; } export interface DeleteParams { index: string; - refresh?: string; + refresh: boolean | 'wait_for' | undefined; id: string; } -export function annotationProvider({ callAsInternalUser }: ILegacyScopedClusterClient) { +export function annotationProvider({ asInternalUser }: IScopedClusterClient) { async function indexAnnotation(annotation: Annotation, username: string) { if (isAnnotation(annotation) === false) { // No need to translate, this will not be exposed in the UI. @@ -104,7 +104,8 @@ export function annotationProvider({ callAsInternalUser }: ILegacyScopedClusterC delete params.body.key; } - return await callAsInternalUser('index', params); + const { body } = await asInternalUser.index(params); + return body; } async function getAnnotations({ @@ -287,14 +288,14 @@ export function annotationProvider({ callAsInternalUser }: ILegacyScopedClusterC }; try { - const resp = await callAsInternalUser('search', params); + const { body } = await asInternalUser.search(params); - if (resp.error !== undefined && resp.message !== undefined) { + if (body.error !== undefined && body.message !== undefined) { // No need to translate, this will not be exposed in the UI. throw new Error(`Annotations couldn't be retrieved from Elasticsearch.`); } - const docs: Annotations = get(resp, ['hits', 'hits'], []).map((d: EsResult) => { + const docs: Annotations = get(body, ['hits', 'hits'], []).map((d: EsResult) => { // get the original source document and the document id, we need it // to identify the annotation when editing/deleting it. // if original `event` is undefined then substitute with 'user` by default @@ -306,7 +307,7 @@ export function annotationProvider({ callAsInternalUser }: ILegacyScopedClusterC } as Annotation; }); - const aggregations = get(resp, ['aggregations'], {}) as EsAggregationResult; + const aggregations = get(body, ['aggregations'], {}) as EsAggregationResult; if (fields) { obj.aggregations = aggregations; } @@ -330,13 +331,14 @@ export function annotationProvider({ callAsInternalUser }: ILegacyScopedClusterC } async function deleteAnnotation(id: string) { - const param: DeleteParams = { + const params: DeleteParams = { index: ML_ANNOTATIONS_INDEX_ALIAS_WRITE, id, refresh: 'wait_for', }; - return await callAsInternalUser('delete', param); + const { body } = await asInternalUser.delete(params); + return body; } return { diff --git a/x-pack/plugins/ml/server/models/annotation_service/index.ts b/x-pack/plugins/ml/server/models/annotation_service/index.ts index e17af2a154b87..9fcb84e2938ae 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/index.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/index.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { annotationProvider } from './annotation'; -export function annotationServiceProvider(mlClusterClient: ILegacyScopedClusterClient) { +export function annotationServiceProvider(client: IScopedClusterClient) { return { - ...annotationProvider(mlClusterClient), + ...annotationProvider(client), }; } diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts index eeabb24d9be3b..5b52414d6753a 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { ES_AGGREGATION } from '../../../common/constants/aggregation_types'; export interface BucketSpanEstimatorData { @@ -21,6 +21,6 @@ export interface BucketSpanEstimatorData { } export function estimateBucketSpanFactory({ - callAsCurrentUser, - callAsInternalUser, -}: ILegacyScopedClusterClient): (config: BucketSpanEstimatorData) => Promise; + asCurrentUser, + asInternalUser, +}: IScopedClusterClient): (config: BucketSpanEstimatorData) => Promise; diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js index 381c615051e3b..1d59db8fa564f 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js @@ -16,10 +16,10 @@ import { INTERVALS } from './intervals'; import { singleSeriesCheckerFactory } from './single_series_checker'; import { polledDataCheckerFactory } from './polled_data_checker'; -export function estimateBucketSpanFactory(mlClusterClient) { - const { callAsCurrentUser, callAsInternalUser } = mlClusterClient; - const PolledDataChecker = polledDataCheckerFactory(mlClusterClient); - const SingleSeriesChecker = singleSeriesCheckerFactory(mlClusterClient); +export function estimateBucketSpanFactory(client) { + const { asCurrentUser, asInternalUser } = client; + const PolledDataChecker = polledDataCheckerFactory(client); + const SingleSeriesChecker = singleSeriesCheckerFactory(client); class BucketSpanEstimator { constructor( @@ -246,21 +246,22 @@ export function estimateBucketSpanFactory(mlClusterClient) { const getFieldCardinality = function (index, field) { return new Promise((resolve, reject) => { - callAsCurrentUser('search', { - index, - size: 0, - body: { - aggs: { - field_count: { - cardinality: { - field, + asCurrentUser + .search({ + index, + size: 0, + body: { + aggs: { + field_count: { + cardinality: { + field, + }, }, }, }, - }, - }) - .then((resp) => { - const value = get(resp, ['aggregations', 'field_count', 'value'], 0); + }) + .then(({ body }) => { + const value = get(body, ['aggregations', 'field_count', 'value'], 0); resolve(value); }) .catch((resp) => { @@ -278,28 +279,29 @@ export function estimateBucketSpanFactory(mlClusterClient) { getFieldCardinality(index, field) .then((value) => { const numPartitions = Math.floor(value / NUM_PARTITIONS) || 1; - callAsCurrentUser('search', { - index, - size: 0, - body: { - query, - aggs: { - fields_bucket_counts: { - terms: { - field, - include: { - partition: 0, - num_partitions: numPartitions, + asCurrentUser + .search({ + index, + size: 0, + body: { + query, + aggs: { + fields_bucket_counts: { + terms: { + field, + include: { + partition: 0, + num_partitions: numPartitions, + }, }, }, }, }, - }, - }) - .then((partitionResp) => { + }) + .then(({ body }) => { // eslint-disable-next-line camelcase - if (partitionResp.aggregations?.fields_bucket_counts?.buckets !== undefined) { - const buckets = partitionResp.aggregations.fields_bucket_counts.buckets; + if (body.aggregations?.fields_bucket_counts?.buckets !== undefined) { + const buckets = body.aggregations.fields_bucket_counts.buckets; fieldValues = buckets.map((b) => b.key); } resolve(fieldValues); @@ -338,21 +340,21 @@ export function estimateBucketSpanFactory(mlClusterClient) { return new Promise((resolve, reject) => { // fetch the `search.max_buckets` cluster setting so we're able to // adjust aggregations to not exceed that limit. - callAsInternalUser('cluster.getSettings', { - flatSettings: true, - includeDefaults: true, - filterPath: '*.*max_buckets', - }) - .then((settings) => { - if (typeof settings !== 'object') { + asInternalUser.cluster + .getSettings({ + flat_settings: true, + include_defaults: true, + filter_path: '*.*max_buckets', + }) + .then(({ body }) => { + if (typeof body !== 'object') { reject('Unable to retrieve cluster settings'); } // search.max_buckets could exist in default, persistent or transient cluster settings - const maxBucketsSetting = (settings.defaults || - settings.persistent || - settings.transient || - {})['search.max_buckets']; + const maxBucketsSetting = (body.defaults || body.persistent || body.transient || {})[ + 'search.max_buckets' + ]; if (maxBucketsSetting === undefined) { reject('Unable to retrieve cluster setting search.max_buckets'); diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts index f7c7dd8172ea5..35c4f1a0a741b 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts @@ -4,22 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { ES_AGGREGATION } from '../../../common/constants/aggregation_types'; import { estimateBucketSpanFactory, BucketSpanEstimatorData } from './bucket_span_estimator'; -const callAs = () => { - return new Promise((resolve) => { - resolve({}); - }) as Promise; +const callAs = { + search: () => Promise.resolve({ body: {} }), + cluster: { getSettings: () => Promise.resolve({ body: {} }) }, }; -const mlClusterClient: ILegacyScopedClusterClient = { - callAsCurrentUser: callAs, - callAsInternalUser: callAs, -}; +const mlClusterClient = ({ + asCurrentUser: callAs, + asInternalUser: callAs, +} as unknown) as IScopedClusterClient; // mock configuration to be passed to the estimator const formConfig: BucketSpanEstimatorData = { diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js index d3bbd59f3cf9b..fd0cab7c0625d 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js @@ -12,7 +12,7 @@ import get from 'lodash/get'; -export function polledDataCheckerFactory({ callAsCurrentUser }) { +export function polledDataCheckerFactory({ asCurrentUser }) { class PolledDataChecker { constructor(index, timeField, duration, query) { this.index = index; @@ -65,14 +65,15 @@ export function polledDataCheckerFactory({ callAsCurrentUser }) { return search; } - performSearch(intervalMs) { - const body = this.createSearch(intervalMs); + async performSearch(intervalMs) { + const searchBody = this.createSearch(intervalMs); - return callAsCurrentUser('search', { + const { body } = await asCurrentUser.search({ index: this.index, size: 0, - body, + body: searchBody, }); + return body; } // test that the coefficient of variation of time difference between non-empty buckets is small diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js index a5449395501dc..750f0cfc0b4a8 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js @@ -13,7 +13,7 @@ import { mlLog } from '../../client/log'; import { INTERVALS, LONG_INTERVALS } from './intervals'; -export function singleSeriesCheckerFactory({ callAsCurrentUser }) { +export function singleSeriesCheckerFactory({ asCurrentUser }) { const REF_DATA_INTERVAL = { name: '1h', ms: 3600000 }; class SingleSeriesChecker { @@ -184,14 +184,15 @@ export function singleSeriesCheckerFactory({ callAsCurrentUser }) { return search; } - performSearch(intervalMs) { - const body = this.createSearch(intervalMs); + async performSearch(intervalMs) { + const searchBody = this.createSearch(intervalMs); - return callAsCurrentUser('search', { + const { body } = await asCurrentUser.search({ index: this.index, size: 0, - body, + body: searchBody, }); + return body; } getFullBuckets(buckets) { diff --git a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts index bc3c326e7d070..2a2c30601e2ca 100644 --- a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts +++ b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts @@ -5,13 +5,13 @@ */ import numeral from '@elastic/numeral'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { MLCATEGORY } from '../../../common/constants/field_types'; import { AnalysisConfig } from '../../../common/types/anomaly_detection_jobs'; import { fieldsServiceProvider } from '../fields_service'; import { MlInfoResponse } from '../../../common/types/ml_server_info'; -interface ModelMemoryEstimationResult { +export interface ModelMemoryEstimationResult { /** * Result model memory limit */ @@ -29,15 +29,15 @@ interface ModelMemoryEstimationResult { /** * Response of the _estimate_model_memory endpoint. */ -export interface ModelMemoryEstimate { +export interface ModelMemoryEstimateResponse { model_memory_estimate: string; } /** * Retrieves overall and max bucket cardinalities. */ -const cardinalityCheckProvider = (mlClusterClient: ILegacyScopedClusterClient) => { - const fieldsService = fieldsServiceProvider(mlClusterClient); +const cardinalityCheckProvider = (client: IScopedClusterClient) => { + const fieldsService = fieldsServiceProvider(client); return async ( analysisConfig: AnalysisConfig, @@ -123,9 +123,9 @@ const cardinalityCheckProvider = (mlClusterClient: ILegacyScopedClusterClient) = }; }; -export function calculateModelMemoryLimitProvider(mlClusterClient: ILegacyScopedClusterClient) { - const { callAsInternalUser } = mlClusterClient; - const getCardinalities = cardinalityCheckProvider(mlClusterClient); +export function calculateModelMemoryLimitProvider(client: IScopedClusterClient) { + const { asInternalUser } = client; + const getCardinalities = cardinalityCheckProvider(client); /** * Retrieves an estimated size of the model memory limit used in the job config @@ -141,7 +141,7 @@ export function calculateModelMemoryLimitProvider(mlClusterClient: ILegacyScoped latestMs: number, allowMMLGreaterThanMax = false ): Promise { - const info = (await callAsInternalUser('ml.info')) as MlInfoResponse; + const { body: info } = await asInternalUser.ml.info(); const maxModelMemoryLimit = info.limits.max_model_memory_limit?.toUpperCase(); const effectiveMaxModelMemoryLimit = info.limits.effective_max_model_memory_limit?.toUpperCase(); @@ -154,13 +154,14 @@ export function calculateModelMemoryLimitProvider(mlClusterClient: ILegacyScoped latestMs ); - const estimatedModelMemoryLimit = ((await callAsInternalUser('ml.estimateModelMemory', { + const { body } = await asInternalUser.ml.estimateModelMemory({ body: { analysis_config: analysisConfig, overall_cardinality: overallCardinality, max_bucket_cardinality: maxBucketCardinality, }, - })) as ModelMemoryEstimate).model_memory_estimate.toUpperCase(); + }); + const estimatedModelMemoryLimit = body.model_memory_estimate.toUpperCase(); let modelMemoryLimit = estimatedModelMemoryLimit; let mmlCappedAtMax = false; diff --git a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts index b461e71c006ff..95684e790636f 100644 --- a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts @@ -5,7 +5,7 @@ */ import { difference } from 'lodash'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { EventManager, CalendarEvent } from './event_manager'; interface BasicCalendar { @@ -23,30 +23,30 @@ export interface FormCalendar extends BasicCalendar { } export class CalendarManager { - private _callAsInternalUser: ILegacyScopedClusterClient['callAsInternalUser']; + private _asInternalUser: IScopedClusterClient['asInternalUser']; private _eventManager: EventManager; - constructor(mlClusterClient: ILegacyScopedClusterClient) { - this._callAsInternalUser = mlClusterClient.callAsInternalUser; - this._eventManager = new EventManager(mlClusterClient); + constructor(client: IScopedClusterClient) { + this._asInternalUser = client.asInternalUser; + this._eventManager = new EventManager(client); } async getCalendar(calendarId: string) { - const resp = await this._callAsInternalUser('ml.calendars', { - calendarId, + const { body } = await this._asInternalUser.ml.getCalendars({ + calendar_id: calendarId, }); - const calendars = resp.calendars; + const calendars = body.calendars; const calendar = calendars[0]; // Endpoint throws a 404 if calendar is not found. calendar.events = await this._eventManager.getCalendarEvents(calendarId); return calendar; } async getAllCalendars() { - const calendarsResp = await this._callAsInternalUser('ml.calendars'); + const { body } = await this._asInternalUser.ml.getCalendars({ size: 1000 }); const events: CalendarEvent[] = await this._eventManager.getAllEvents(); - const calendars: Calendar[] = calendarsResp.calendars; + const calendars: Calendar[] = body.calendars; calendars.forEach((cal) => (cal.events = [])); // loop events and combine with related calendars @@ -71,8 +71,8 @@ export class CalendarManager { async newCalendar(calendar: FormCalendar) { const { calendarId, events, ...newCalendar } = calendar; - await this._callAsInternalUser('ml.addCalendar', { - calendarId, + await this._asInternalUser.ml.putCalendar({ + calendar_id: calendarId, body: newCalendar, }); @@ -106,17 +106,17 @@ export class CalendarManager { // add all new jobs if (jobsToAdd.length) { - await this._callAsInternalUser('ml.addJobToCalendar', { - calendarId, - jobId: jobsToAdd.join(','), + await this._asInternalUser.ml.putCalendarJob({ + calendar_id: calendarId, + job_id: jobsToAdd.join(','), }); } // remove all removed jobs if (jobsToRemove.length) { - await this._callAsInternalUser('ml.removeJobFromCalendar', { - calendarId, - jobId: jobsToRemove.join(','), + await this._asInternalUser.ml.deleteCalendarJob({ + calendar_id: calendarId, + job_id: jobsToRemove.join(','), }); } @@ -137,6 +137,7 @@ export class CalendarManager { } async deleteCalendar(calendarId: string) { - return this._callAsInternalUser('ml.deleteCalendar', { calendarId }); + const { body } = await this._asInternalUser.ml.deleteCalendar({ calendar_id: calendarId }); + return body; } } diff --git a/x-pack/plugins/ml/server/models/calendar/event_manager.ts b/x-pack/plugins/ml/server/models/calendar/event_manager.ts index b670bbe187544..b42068e748394 100644 --- a/x-pack/plugins/ml/server/models/calendar/event_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/event_manager.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; export interface CalendarEvent { @@ -16,39 +16,42 @@ export interface CalendarEvent { } export class EventManager { - private _callAsInternalUser: ILegacyScopedClusterClient['callAsInternalUser']; - constructor({ callAsInternalUser }: ILegacyScopedClusterClient) { - this._callAsInternalUser = callAsInternalUser; + private _asInternalUser: IScopedClusterClient['asInternalUser']; + constructor({ asInternalUser }: IScopedClusterClient) { + this._asInternalUser = asInternalUser; } async getCalendarEvents(calendarId: string) { - const resp = await this._callAsInternalUser('ml.events', { calendarId }); + const { body } = await this._asInternalUser.ml.getCalendarEvents({ calendar_id: calendarId }); - return resp.events; + return body.events; } // jobId is optional async getAllEvents(jobId?: string) { const calendarId = GLOBAL_CALENDAR; - const resp = await this._callAsInternalUser('ml.events', { - calendarId, - jobId, + const { body } = await this._asInternalUser.ml.getCalendarEvents({ + calendar_id: calendarId, + job_id: jobId, }); - return resp.events; + return body.events; } async addEvents(calendarId: string, events: CalendarEvent[]) { const body = { events }; - return await this._callAsInternalUser('ml.addEvent', { - calendarId, + return await this._asInternalUser.ml.postCalendarEvents({ + calendar_id: calendarId, body, }); } async deleteEvent(calendarId: string, eventId: string) { - return this._callAsInternalUser('ml.deleteEvent', { calendarId, eventId }); + return this._asInternalUser.ml.deleteCalendarEvent({ + calendar_id: calendarId, + event_id: eventId, + }); } isEqual(ev1: CalendarEvent, ev2: CalendarEvent) { diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts index 1cb0656e88a0b..0f4cac37d2e8f 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns'; import { JobMessage } from '../../../common/types/audit_message'; @@ -23,7 +23,7 @@ interface BoolQuery { bool: { [key: string]: any }; } -export function analyticsAuditMessagesProvider({ callAsInternalUser }: ILegacyScopedClusterClient) { +export function analyticsAuditMessagesProvider({ asInternalUser }: IScopedClusterClient) { // search for audit messages, // analyticsId is optional. without it, all analytics will be listed. async function getAnalyticsAuditMessages(analyticsId: string) { @@ -68,27 +68,23 @@ export function analyticsAuditMessagesProvider({ callAsInternalUser }: ILegacySc }); } - try { - const resp = await callAsInternalUser('search', { - index: ML_NOTIFICATION_INDEX_PATTERN, - ignore_unavailable: true, - rest_total_hits_as_int: true, - size: SIZE, - body: { - sort: [{ timestamp: { order: 'desc' } }, { job_id: { order: 'asc' } }], - query, - }, - }); + const { body } = await asInternalUser.search({ + index: ML_NOTIFICATION_INDEX_PATTERN, + ignore_unavailable: true, + rest_total_hits_as_int: true, + size: SIZE, + body: { + sort: [{ timestamp: { order: 'desc' } }, { job_id: { order: 'asc' } }], + query, + }, + }); - let messages = []; - if (resp.hits.total !== 0) { - messages = resp.hits.hits.map((hit: Message) => hit._source); - messages.reverse(); - } - return messages; - } catch (e) { - throw e; + let messages = []; + if (body.hits.total !== 0) { + messages = body.hits.hits.map((hit: Message) => hit._source); + messages.reverse(); } + return messages; } return { diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts index 82d7707464308..ab879d4c93b29 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts @@ -4,13 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; +import { SavedObjectsClientContract, KibanaRequest, IScopedClusterClient } from 'kibana/server'; import { Module } from '../../../common/types/modules'; import { DataRecognizer } from '../data_recognizer'; +const callAs = () => Promise.resolve({ body: {} }); + +const mlClusterClient = ({ + asCurrentUser: callAs, + asInternalUser: callAs, +} as unknown) as IScopedClusterClient; + describe('ML - data recognizer', () => { const dr = new DataRecognizer( - { callAsCurrentUser: jest.fn(), callAsInternalUser: jest.fn() }, + mlClusterClient, ({ find: jest.fn(), bulkCreate: jest.fn(), diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 206baacd98322..820fcfa9253b6 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -7,15 +7,11 @@ import fs from 'fs'; import Boom from 'boom'; import numeral from '@elastic/numeral'; -import { - KibanaRequest, - ILegacyScopedClusterClient, - SavedObjectsClientContract, -} from 'kibana/server'; +import { KibanaRequest, IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; import moment from 'moment'; import { IndexPatternAttributes } from 'src/plugins/data/server'; import { merge } from 'lodash'; -import { AnalysisLimits, CombinedJobWithStats } from '../../../common/types/anomaly_detection_jobs'; +import { AnalysisLimits } from '../../../common/types/anomaly_detection_jobs'; import { getAuthorizationHeader } from '../../lib/request_authorization'; import { MlInfoResponse } from '../../../common/types/ml_server_info'; import { @@ -46,6 +42,7 @@ import { fieldsServiceProvider } from '../fields_service'; import { jobServiceProvider } from '../job_service'; import { resultsServiceProvider } from '../results_service'; import { JobExistResult, JobStat } from '../../../common/types/data_recognizer'; +import { MlJobsStatsResponse } from '../job_service/jobs'; const ML_DIR = 'ml'; const KIBANA_DIR = 'kibana'; @@ -74,10 +71,6 @@ interface RawModuleConfig { }; } -interface MlJobStats { - jobs: CombinedJobWithStats[]; -} - interface Config { dirName: any; json: RawModuleConfig; @@ -111,9 +104,9 @@ interface SaveResults { } export class DataRecognizer { - private _callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']; - private _callAsInternalUser: ILegacyScopedClusterClient['callAsInternalUser']; - private _mlClusterClient: ILegacyScopedClusterClient; + private _asCurrentUser: IScopedClusterClient['asCurrentUser']; + private _asInternalUser: IScopedClusterClient['asInternalUser']; + private _client: IScopedClusterClient; private _authorizationHeader: object; private _modulesDir = `${__dirname}/modules`; private _indexPatternName: string = ''; @@ -124,13 +117,13 @@ export class DataRecognizer { jobsForModelMemoryEstimation: Array<{ job: ModuleJob; query: any }> = []; constructor( - mlClusterClient: ILegacyScopedClusterClient, + mlClusterClient: IScopedClusterClient, private savedObjectsClient: SavedObjectsClientContract, request: KibanaRequest ) { - this._mlClusterClient = mlClusterClient; - this._callAsCurrentUser = mlClusterClient.callAsCurrentUser; - this._callAsInternalUser = mlClusterClient.callAsInternalUser; + this._client = mlClusterClient; + this._asCurrentUser = mlClusterClient.asCurrentUser; + this._asInternalUser = mlClusterClient.asInternalUser; this._authorizationHeader = getAuthorizationHeader(request); } @@ -249,18 +242,18 @@ export class DataRecognizer { const index = indexPattern; const size = 0; - const body = { + const searchBody = { query: moduleConfig.query, }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, rest_total_hits_as_int: true, size, - body, + body: searchBody, }); - return resp.hits.total !== 0; + return body.hits.total !== 0; } async listModules() { @@ -518,7 +511,7 @@ export class DataRecognizer { // Add a wildcard at the front of each of the job IDs in the module, // as a prefix may have been supplied when creating the jobs in the module. const jobIds = module.jobs.map((job) => `*${job.id}`); - const { jobsExist } = jobServiceProvider(this._mlClusterClient); + const { jobsExist } = jobServiceProvider(this._client); const jobInfo = await jobsExist(jobIds); // Check if the value for any of the jobs is false. @@ -527,16 +520,16 @@ export class DataRecognizer { if (doJobsExist === true) { // Get the IDs of the jobs created from the module, and their earliest / latest timestamps. - const jobStats: MlJobStats = await this._callAsInternalUser('ml.jobStats', { - jobId: jobIds, + const { body } = await this._asInternalUser.ml.getJobStats({ + job_id: jobIds.join(), }); const jobStatsJobs: JobStat[] = []; - if (jobStats.jobs && jobStats.jobs.length > 0) { - const foundJobIds = jobStats.jobs.map((job) => job.job_id); - const { getLatestBucketTimestampByJob } = resultsServiceProvider(this._mlClusterClient); + if (body.jobs && body.jobs.length > 0) { + const foundJobIds = body.jobs.map((job) => job.job_id); + const { getLatestBucketTimestampByJob } = resultsServiceProvider(this._client); const latestBucketTimestampsByJob = await getLatestBucketTimestampByJob(foundJobIds); - jobStats.jobs.forEach((job) => { + body.jobs.forEach((job) => { const jobStat = { id: job.job_id, } as JobStat; @@ -704,16 +697,15 @@ export class DataRecognizer { job.id = jobId; await this.saveJob(job); return { id: jobId, success: true }; - } catch (error) { - return { id: jobId, success: false, error }; + } catch ({ body }) { + return { id: jobId, success: false, error: body }; } }) ); } async saveJob(job: ModuleJob) { - const { id: jobId, config: body } = job; - return this._callAsInternalUser('ml.addJob', { jobId, body }); + return this._asInternalUser.ml.putJob({ job_id: job.id, body: job.config }); } // save the datafeeds. @@ -725,20 +717,21 @@ export class DataRecognizer { try { await this.saveDatafeed(datafeed); return { id: datafeed.id, success: true, started: false }; - } catch (error) { - return { id: datafeed.id, success: false, started: false, error }; + } catch ({ body }) { + return { id: datafeed.id, success: false, started: false, error: body }; } }) ); } async saveDatafeed(datafeed: ModuleDataFeed) { - const { id: datafeedId, config: body } = datafeed; - return this._callAsInternalUser('ml.addDatafeed', { - datafeedId, - body, - ...this._authorizationHeader, - }); + return this._asInternalUser.ml.putDatafeed( + { + datafeed_id: datafeed.id, + body: datafeed.config, + }, + this._authorizationHeader + ); } async startDatafeeds( @@ -761,10 +754,10 @@ export class DataRecognizer { const result = { started: false } as DatafeedResponse; let opened = false; try { - const openResult = await this._callAsInternalUser('ml.openJob', { - jobId: datafeed.config.job_id, + const { body } = await this._asInternalUser.ml.openJob({ + job_id: datafeed.config.job_id, }); - opened = openResult.opened; + opened = body.opened; } catch (error) { // if the job is already open, a 409 will be returned. if (error.statusCode === 409) { @@ -772,27 +765,27 @@ export class DataRecognizer { } else { opened = false; result.started = false; - result.error = error; + result.error = error.body; } } if (opened) { try { - const duration: { start: number; end?: number } = { start: 0 }; + const duration: { start: string; end?: string } = { start: '0' }; if (start !== undefined) { - duration.start = start; + duration.start = (start as unknown) as string; } if (end !== undefined) { - duration.end = end; + duration.end = (end as unknown) as string; } - await this._callAsInternalUser('ml.startDatafeed', { - datafeedId: datafeed.id, + await this._asInternalUser.ml.startDatafeed({ + datafeed_id: datafeed.id, ...duration, }); result.started = true; - } catch (error) { + } catch ({ body }) { result.started = false; - result.error = error; + result.error = body; } } return result; @@ -995,7 +988,7 @@ export class DataRecognizer { timeField: string, query?: any ): Promise<{ start: number; end: number }> { - const fieldsService = fieldsServiceProvider(this._mlClusterClient); + const fieldsService = fieldsServiceProvider(this._client); const timeFieldRange = await fieldsService.getTimeFieldRange( this._indexPatternName, @@ -1025,7 +1018,7 @@ export class DataRecognizer { if (estimateMML && this.jobsForModelMemoryEstimation.length > 0) { try { - const calculateModelMemoryLimit = calculateModelMemoryLimitProvider(this._mlClusterClient); + const calculateModelMemoryLimit = calculateModelMemoryLimitProvider(this._client); // Checks if all jobs in the module have the same time field configured const firstJobTimeField = this.jobsForModelMemoryEstimation[0].job.config.data_description @@ -1074,11 +1067,13 @@ export class DataRecognizer { job.config.analysis_limits.model_memory_limit = modelMemoryLimit; } } catch (error) { - mlLog.warn(`Data recognizer could not estimate model memory limit ${error}`); + mlLog.warn(`Data recognizer could not estimate model memory limit ${error.body}`); } } - const { limits } = (await this._callAsInternalUser('ml.info')) as MlInfoResponse; + const { + body: { limits }, + } = await this._asInternalUser.ml.info(); const maxMml = limits.max_model_memory_limit; if (!maxMml) { diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 838315d8d272c..dbfa4b5656e5f 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import get from 'lodash/get'; import each from 'lodash/each'; import last from 'lodash/last'; @@ -183,7 +183,7 @@ type BatchStats = | FieldExamples; const getAggIntervals = async ( - { callAsCurrentUser }: ILegacyScopedClusterClient, + { asCurrentUser }: IScopedClusterClient, indexPatternTitle: string, query: any, fields: HistogramField[], @@ -207,7 +207,7 @@ const getAggIntervals = async ( return aggs; }, {} as Record); - const respStats = await callAsCurrentUser('search', { + const { body } = await asCurrentUser.search({ index: indexPatternTitle, size: 0, body: { @@ -218,8 +218,7 @@ const getAggIntervals = async ( }); const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); - const aggregations = - aggsPath.length > 0 ? get(respStats.aggregations, aggsPath) : respStats.aggregations; + const aggregations = aggsPath.length > 0 ? get(body.aggregations, aggsPath) : body.aggregations; return Object.keys(aggregations).reduce((p, aggName) => { const stats = [aggregations[aggName].min, aggregations[aggName].max]; @@ -241,15 +240,15 @@ const getAggIntervals = async ( // export for re-use by transforms plugin export const getHistogramsForFields = async ( - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, indexPatternTitle: string, query: any, fields: HistogramField[], samplerShardSize: number ) => { - const { callAsCurrentUser } = mlClusterClient; + const { asCurrentUser } = client; const aggIntervals = await getAggIntervals( - mlClusterClient, + client, indexPatternTitle, query, fields, @@ -291,7 +290,7 @@ export const getHistogramsForFields = async ( return []; } - const respChartsData = await callAsCurrentUser('search', { + const { body } = await asCurrentUser.search({ index: indexPatternTitle, size: 0, body: { @@ -302,8 +301,7 @@ export const getHistogramsForFields = async ( }); const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); - const aggregations = - aggsPath.length > 0 ? get(respChartsData.aggregations, aggsPath) : respChartsData.aggregations; + const aggregations = aggsPath.length > 0 ? get(body.aggregations, aggsPath) : body.aggregations; const chartsData: ChartData[] = fields.map( (field): ChartData => { @@ -350,12 +348,12 @@ export const getHistogramsForFields = async ( }; export class DataVisualizer { - private _mlClusterClient: ILegacyScopedClusterClient; - private _callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']; + private _client: IScopedClusterClient; + private _asCurrentUser: IScopedClusterClient['asCurrentUser']; - constructor(mlClusterClient: ILegacyScopedClusterClient) { - this._callAsCurrentUser = mlClusterClient.callAsCurrentUser; - this._mlClusterClient = mlClusterClient; + constructor(client: IScopedClusterClient) { + this._asCurrentUser = client.asCurrentUser; + this._client = client; } // Obtains overall stats on the fields in the supplied index pattern, returning an object @@ -451,7 +449,7 @@ export class DataVisualizer { samplerShardSize: number ): Promise { return await getHistogramsForFields( - this._mlClusterClient, + this._client, indexPatternTitle, query, fields, @@ -621,7 +619,7 @@ export class DataVisualizer { }; }); - const body = { + const searchBody = { query: { bool: { filter: filterCriteria, @@ -630,14 +628,14 @@ export class DataVisualizer { aggs: buildSamplerAggregation(aggs, samplerShardSize), }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, rest_total_hits_as_int: true, size, - body, + body: searchBody, }); - const aggregations = resp.aggregations; - const totalCount = get(resp, ['hits', 'total'], 0); + const aggregations = body.aggregations; + const totalCount = get(body, ['hits', 'total'], 0); const stats = { totalCount, aggregatableExistsFields: [] as FieldData[], @@ -688,7 +686,7 @@ export class DataVisualizer { const size = 0; const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, query); - const body = { + const searchBody = { query: { bool: { filter: filterCriteria, @@ -697,13 +695,13 @@ export class DataVisualizer { }; filterCriteria.push({ exists: { field } }); - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, rest_total_hits_as_int: true, size, - body, + body: searchBody, }); - return resp.hits.total > 0; + return body.hits.total > 0; } async getDocumentCountStats( @@ -730,7 +728,7 @@ export class DataVisualizer { }, }; - const body = { + const searchBody = { query: { bool: { filter: filterCriteria, @@ -739,15 +737,15 @@ export class DataVisualizer { aggs, }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, size, - body, + body: searchBody, }); const buckets: { [key: string]: number } = {}; const dataByTimeBucket: Array<{ key: string; doc_count: number }> = get( - resp, + body, ['aggregations', 'eventRate', 'buckets'], [] ); @@ -833,7 +831,7 @@ export class DataVisualizer { } }); - const body = { + const searchBody = { query: { bool: { filter: filterCriteria, @@ -842,12 +840,12 @@ export class DataVisualizer { aggs: buildSamplerAggregation(aggs, samplerShardSize), }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, size, - body, + body: searchBody, }); - const aggregations = resp.aggregations; + const aggregations = body.aggregations; const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); const batchStats: NumericFieldStats[] = []; fields.forEach((field, i) => { @@ -954,7 +952,7 @@ export class DataVisualizer { } }); - const body = { + const searchBody = { query: { bool: { filter: filterCriteria, @@ -963,12 +961,12 @@ export class DataVisualizer { aggs: buildSamplerAggregation(aggs, samplerShardSize), }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, size, - body, + body: searchBody, }); - const aggregations = resp.aggregations; + const aggregations = body.aggregations; const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); const batchStats: StringFieldStats[] = []; fields.forEach((field, i) => { @@ -1028,7 +1026,7 @@ export class DataVisualizer { }; }); - const body = { + const searchBody = { query: { bool: { filter: filterCriteria, @@ -1037,12 +1035,12 @@ export class DataVisualizer { aggs: buildSamplerAggregation(aggs, samplerShardSize), }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, size, - body, + body: searchBody, }); - const aggregations = resp.aggregations; + const aggregations = body.aggregations; const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); const batchStats: DateFieldStats[] = []; fields.forEach((field, i) => { @@ -1095,7 +1093,7 @@ export class DataVisualizer { }; }); - const body = { + const searchBody = { query: { bool: { filter: filterCriteria, @@ -1104,12 +1102,12 @@ export class DataVisualizer { aggs: buildSamplerAggregation(aggs, samplerShardSize), }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, size, - body, + body: searchBody, }); - const aggregations = resp.aggregations; + const aggregations = body.aggregations; const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); const batchStats: BooleanFieldStats[] = []; fields.forEach((field, i) => { @@ -1157,7 +1155,7 @@ export class DataVisualizer { exists: { field }, }); - const body = { + const searchBody = { _source: field, query: { bool: { @@ -1166,18 +1164,18 @@ export class DataVisualizer { }, }; - const resp = await this._callAsCurrentUser('search', { + const { body } = await this._asCurrentUser.search({ index, rest_total_hits_as_int: true, size, - body, + body: searchBody, }); const stats = { fieldName: field, examples: [] as any[], }; - if (resp.hits.total !== 0) { - const hits = resp.hits.hits; + if (body.hits.total !== 0) { + const hits = body.hits.hits; for (let i = 0; i < hits.length; i++) { // Look in the _source for the field value. // If the field is not in the _source (as will happen if the diff --git a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts index 43a6876f76c49..68427e98750eb 100644 --- a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts +++ b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { duration } from 'moment'; import { parseInterval } from '../../../common/util/parse_interval'; import { initCardinalityFieldsCache } from './fields_aggs_cache'; @@ -14,7 +14,7 @@ import { initCardinalityFieldsCache } from './fields_aggs_cache'; * Service for carrying out queries to obtain data * specific to fields in Elasticsearch indices. */ -export function fieldsServiceProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) { +export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { const fieldsAggsCache = initCardinalityFieldsCache(); /** @@ -37,13 +37,13 @@ export function fieldsServiceProvider({ callAsCurrentUser }: ILegacyScopedCluste index: string | string[], fieldNames: string[] ): Promise { - const fieldCapsResp = await callAsCurrentUser('fieldCaps', { + const { body } = await asCurrentUser.fieldCaps({ index, fields: fieldNames, }); const aggregatableFields: string[] = []; fieldNames.forEach((fieldName) => { - const fieldInfo = fieldCapsResp.fields[fieldName]; + const fieldInfo = body.fields[fieldName]; const typeKeys = fieldInfo !== undefined ? Object.keys(fieldInfo) : []; if (typeKeys.length > 0) { const fieldType = typeKeys[0]; @@ -130,12 +130,12 @@ export function fieldsServiceProvider({ callAsCurrentUser }: ILegacyScopedCluste aggs, }; - const aggregations = ( - await callAsCurrentUser('search', { - index, - body, - }) - )?.aggregations; + const { + body: { aggregations }, + } = await asCurrentUser.search({ + index, + body, + }); if (!aggregations) { return {}; @@ -170,7 +170,9 @@ export function fieldsServiceProvider({ callAsCurrentUser }: ILegacyScopedCluste }> { const obj = { success: true, start: { epoch: 0, string: '' }, end: { epoch: 0, string: '' } }; - const resp = await callAsCurrentUser('search', { + const { + body: { aggregations }, + } = await asCurrentUser.search({ index, size: 0, body: { @@ -190,12 +192,12 @@ export function fieldsServiceProvider({ callAsCurrentUser }: ILegacyScopedCluste }, }); - if (resp.aggregations && resp.aggregations.earliest && resp.aggregations.latest) { - obj.start.epoch = resp.aggregations.earliest.value; - obj.start.string = resp.aggregations.earliest.value_as_string; + if (aggregations && aggregations.earliest && aggregations.latest) { + obj.start.epoch = aggregations.earliest.value; + obj.start.string = aggregations.earliest.value_as_string; - obj.end.epoch = resp.aggregations.latest.value; - obj.end.string = resp.aggregations.latest.value_as_string; + obj.end.epoch = aggregations.latest.value; + obj.end.string = aggregations.latest.value_as_string; } return obj; } @@ -338,12 +340,12 @@ export function fieldsServiceProvider({ callAsCurrentUser }: ILegacyScopedCluste }, }; - const aggregations = ( - await callAsCurrentUser('search', { - index, - body, - }) - )?.aggregations; + const { + body: { aggregations }, + } = await asCurrentUser.search({ + index, + body, + }); if (!aggregations) { return cachedValues; diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts index 9cd71c046b66c..9e502c04fbb7b 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts @@ -4,18 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { AnalysisResult, FormattedOverrides, InputOverrides, + FindFileStructureResponse, } from '../../../common/types/file_datavisualizer'; export type InputData = any[]; -export function fileDataVisualizerProvider({ callAsInternalUser }: ILegacyScopedClusterClient) { - async function analyzeFile(data: any, overrides: any): Promise { - const results = await callAsInternalUser('ml.fileStructure', { +export function fileDataVisualizerProvider({ asInternalUser }: IScopedClusterClient) { + async function analyzeFile(data: InputData, overrides: InputOverrides): Promise { + overrides.explain = overrides.explain === undefined ? 'true' : overrides.explain; + const { body } = await asInternalUser.ml.findFileStructure({ body: data, ...overrides, }); @@ -24,7 +26,7 @@ export function fileDataVisualizerProvider({ callAsInternalUser }: ILegacyScoped return { ...(hasOverrides && { overrides: reducedOverrides }), - results, + results: body, }; } diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts index fc9b333298c9d..6108454c08aa7 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { INDEX_META_DATA_CREATED_BY } from '../../../common/constants/file_datavisualizer'; import { ImportResponse, @@ -15,7 +15,7 @@ import { } from '../../../common/types/file_datavisualizer'; import { InputData } from './file_data_visualizer'; -export function importDataProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) { +export function importDataProvider({ asCurrentUser }: IScopedClusterClient) { async function importData( id: string, index: string, @@ -40,9 +40,9 @@ export function importDataProvider({ callAsCurrentUser }: ILegacyScopedClusterCl // create the pipeline if one has been supplied if (pipelineId !== undefined) { - const success = await createPipeline(pipelineId, pipeline); - if (success.acknowledged !== true) { - throw success; + const resp = await createPipeline(pipelineId, pipeline); + if (resp.acknowledged !== true) { + throw resp; } } createdPipelineId = pipelineId; @@ -80,7 +80,7 @@ export function importDataProvider({ callAsCurrentUser }: ILegacyScopedClusterCl id, index: createdIndex, pipelineId: createdPipelineId, - error: error.error !== undefined ? error.error : error, + error: error.body !== undefined ? error.body : error, docCount, ingestError: error.ingestError, failures: error.failures || [], @@ -102,7 +102,7 @@ export function importDataProvider({ callAsCurrentUser }: ILegacyScopedClusterCl body.settings = settings; } - await callAsCurrentUser('indices.create', { index, body }); + await asCurrentUser.indices.create({ index, body }); } async function indexData(index: string, pipelineId: string, data: InputData) { @@ -118,7 +118,7 @@ export function importDataProvider({ callAsCurrentUser }: ILegacyScopedClusterCl settings.pipeline = pipelineId; } - const resp = await callAsCurrentUser('bulk', settings); + const { body: resp } = await asCurrentUser.bulk(settings); if (resp.errors) { throw resp; } else { @@ -151,7 +151,8 @@ export function importDataProvider({ callAsCurrentUser }: ILegacyScopedClusterCl } async function createPipeline(id: string, pipeline: any) { - return await callAsCurrentUser('ingest.putPipeline', { id, body: pipeline }); + const { body } = await asCurrentUser.ingest.putPipeline({ id, body: pipeline }); + return body; } function getFailures(items: any[], data: InputData): ImportFailure[] { diff --git a/x-pack/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts index 768ca1f893b68..19ba1b76f8a60 100644 --- a/x-pack/plugins/ml/server/models/filter/filter_manager.ts +++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules'; @@ -58,26 +58,26 @@ interface PartialJob { } export class FilterManager { - private _callAsInternalUser: ILegacyScopedClusterClient['callAsInternalUser']; - constructor({ callAsInternalUser }: ILegacyScopedClusterClient) { - this._callAsInternalUser = callAsInternalUser; + private _asInternalUser: IScopedClusterClient['asInternalUser']; + constructor({ asInternalUser }: IScopedClusterClient) { + this._asInternalUser = asInternalUser; } async getFilter(filterId: string) { try { const [JOBS, FILTERS] = [0, 1]; const results = await Promise.all([ - this._callAsInternalUser('ml.jobs'), - this._callAsInternalUser('ml.filters', { filterId }), + this._asInternalUser.ml.getJobs(), + this._asInternalUser.ml.getFilters({ filter_id: filterId }), ]); - if (results[FILTERS] && results[FILTERS].filters.length) { + if (results[FILTERS] && results[FILTERS].body.filters.length) { let filtersInUse: FiltersInUse = {}; - if (results[JOBS] && results[JOBS].jobs) { - filtersInUse = this.buildFiltersInUse(results[JOBS].jobs); + if (results[JOBS] && results[JOBS].body.jobs) { + filtersInUse = this.buildFiltersInUse(results[JOBS].body.jobs); } - const filter = results[FILTERS].filters[0]; + const filter = results[FILTERS].body.filters[0]; filter.used_by = filtersInUse[filter.filter_id]; return filter; } else { @@ -90,8 +90,8 @@ export class FilterManager { async getAllFilters() { try { - const filtersResp = await this._callAsInternalUser('ml.filters'); - return filtersResp.filters; + const { body } = await this._asInternalUser.ml.getFilters({ size: 1000 }); + return body.filters; } catch (error) { throw Boom.badRequest(error); } @@ -101,14 +101,14 @@ export class FilterManager { try { const [JOBS, FILTERS] = [0, 1]; const results = await Promise.all([ - this._callAsInternalUser('ml.jobs'), - this._callAsInternalUser('ml.filters'), + this._asInternalUser.ml.getJobs(), + this._asInternalUser.ml.getFilters({ size: 1000 }), ]); // Build a map of filter_ids against jobs and detectors using that filter. let filtersInUse: FiltersInUse = {}; - if (results[JOBS] && results[JOBS].jobs) { - filtersInUse = this.buildFiltersInUse(results[JOBS].jobs); + if (results[JOBS] && results[JOBS].body.jobs) { + filtersInUse = this.buildFiltersInUse(results[JOBS].body.jobs); } // For each filter, return just @@ -117,8 +117,8 @@ export class FilterManager { // item_count // jobs using the filter const filterStats: FilterStats[] = []; - if (results[FILTERS] && results[FILTERS].filters) { - results[FILTERS].filters.forEach((filter: Filter) => { + if (results[FILTERS] && results[FILTERS].body.filters) { + results[FILTERS].body.filters.forEach((filter: Filter) => { const stats: FilterStats = { filter_id: filter.filter_id, description: filter.description, @@ -139,7 +139,8 @@ export class FilterManager { const { filterId, ...body } = filter; try { // Returns the newly created filter. - return await this._callAsInternalUser('ml.addFilter', { filterId, body }); + const { body: resp } = await this._asInternalUser.ml.putFilter({ filter_id: filterId, body }); + return resp; } catch (error) { throw Boom.badRequest(error); } @@ -159,17 +160,19 @@ export class FilterManager { } // Returns the newly updated filter. - return await this._callAsInternalUser('ml.updateFilter', { - filterId, + const { body: resp } = await this._asInternalUser.ml.updateFilter({ + filter_id: filterId, body, }); + return resp; } catch (error) { throw Boom.badRequest(error); } } async deleteFilter(filterId: string) { - return this._callAsInternalUser('ml.deleteFilter', { filterId }); + const { body } = await this._asInternalUser.ml.deleteFilter({ filter_id: filterId }); + return body; } buildFiltersInUse(jobsList: PartialJob[]) { diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts index d72552b548b82..afdd3e9bb8ce9 100644 --- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts +++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; export function jobAuditMessagesProvider( - mlClusterClient: ILegacyScopedClusterClient + client: IScopedClusterClient ): { getJobAuditMessages: (jobId?: string, from?: string) => any; getAuditMessagesSummary: (jobIds?: string[]) => any; diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js index 86d80c394137f..3fd5ebf3f68f4 100644 --- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js +++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js @@ -34,14 +34,14 @@ const anomalyDetectorTypeFilter = { }, }; -export function jobAuditMessagesProvider({ callAsInternalUser }) { +export function jobAuditMessagesProvider({ asInternalUser }) { // search for audit messages, // jobId is optional. without it, all jobs will be listed. // from is optional and should be a string formatted in ES time units. e.g. 12h, 1d, 7d async function getJobAuditMessages(jobId, from) { let gte = null; if (jobId !== undefined && from === undefined) { - const jobs = await callAsInternalUser('ml.jobs', { jobId }); + const jobs = await asInternalUser.ml.getJobs({ job_id: jobId }); if (jobs.count > 0 && jobs.jobs !== undefined) { gte = moment(jobs.jobs[0].create_time).valueOf(); } @@ -99,26 +99,22 @@ export function jobAuditMessagesProvider({ callAsInternalUser }) { }); } - try { - const resp = await callAsInternalUser('search', { - index: ML_NOTIFICATION_INDEX_PATTERN, - ignore_unavailable: true, - rest_total_hits_as_int: true, - size: SIZE, - body: { - sort: [{ timestamp: { order: 'desc' } }, { job_id: { order: 'asc' } }], - query, - }, - }); + const { body } = await asInternalUser.search({ + index: ML_NOTIFICATION_INDEX_PATTERN, + ignore_unavailable: true, + rest_total_hits_as_int: true, + size: SIZE, + body: { + sort: [{ timestamp: { order: 'desc' } }, { job_id: { order: 'asc' } }], + query, + }, + }); - let messages = []; - if (resp.hits.total !== 0) { - messages = resp.hits.hits.map((hit) => hit._source); - } - return messages; - } catch (e) { - throw e; + let messages = []; + if (body.hits.total !== 0) { + messages = body.hits.hits.map((hit) => hit._source); } + return messages; } // search highest, most recent audit messages for all jobs for the last 24hrs. @@ -128,65 +124,63 @@ export function jobAuditMessagesProvider({ callAsInternalUser }) { const maxBuckets = 10000; let levelsPerJobAggSize = maxBuckets; - try { - const query = { - bool: { - filter: [ - { - range: { - timestamp: { - gte: 'now-1d', - }, + const query = { + bool: { + filter: [ + { + range: { + timestamp: { + gte: 'now-1d', }, }, - anomalyDetectorTypeFilter, - ], - }, - }; - - // If the jobIds arg is supplied, add a query filter - // to only include those jobIds in the aggregations. - if (Array.isArray(jobIds) && jobIds.length > 0) { - query.bool.filter.push({ - terms: { - job_id: jobIds, }, - }); - levelsPerJobAggSize = jobIds.length; - } + anomalyDetectorTypeFilter, + ], + }, + }; - const resp = await callAsInternalUser('search', { - index: ML_NOTIFICATION_INDEX_PATTERN, - ignore_unavailable: true, - rest_total_hits_as_int: true, - size: 0, - body: { - query, - aggs: { - levelsPerJob: { - terms: { - field: 'job_id', - size: levelsPerJobAggSize, - }, - aggs: { - levels: { - terms: { - field: 'level', - }, - aggs: { - latestMessage: { - terms: { - field: 'message.raw', - size: 1, - order: { - latestMessage: 'desc', - }, + // If the jobIds arg is supplied, add a query filter + // to only include those jobIds in the aggregations. + if (Array.isArray(jobIds) && jobIds.length > 0) { + query.bool.filter.push({ + terms: { + job_id: jobIds, + }, + }); + levelsPerJobAggSize = jobIds.length; + } + + const { body } = await asInternalUser.search({ + index: ML_NOTIFICATION_INDEX_PATTERN, + ignore_unavailable: true, + rest_total_hits_as_int: true, + size: 0, + body: { + query, + aggs: { + levelsPerJob: { + terms: { + field: 'job_id', + size: levelsPerJobAggSize, + }, + aggs: { + levels: { + terms: { + field: 'level', + }, + aggs: { + latestMessage: { + terms: { + field: 'message.raw', + size: 1, + order: { + latestMessage: 'desc', }, - aggs: { - latestMessage: { - max: { - field: 'timestamp', - }, + }, + aggs: { + latestMessage: { + max: { + field: 'timestamp', }, }, }, @@ -196,67 +190,65 @@ export function jobAuditMessagesProvider({ callAsInternalUser }) { }, }, }, - }); + }, + }); - let messagesPerJob = []; - const jobMessages = []; - if ( - resp.hits.total !== 0 && - resp.aggregations && - resp.aggregations.levelsPerJob && - resp.aggregations.levelsPerJob.buckets && - resp.aggregations.levelsPerJob.buckets.length - ) { - messagesPerJob = resp.aggregations.levelsPerJob.buckets; - } + let messagesPerJob = []; + const jobMessages = []; + if ( + body.hits.total !== 0 && + body.aggregations && + body.aggregations.levelsPerJob && + body.aggregations.levelsPerJob.buckets && + body.aggregations.levelsPerJob.buckets.length + ) { + messagesPerJob = body.aggregations.levelsPerJob.buckets; + } - messagesPerJob.forEach((job) => { - // ignore system messages (id==='') - if (job.key !== '' && job.levels && job.levels.buckets && job.levels.buckets.length) { - let highestLevel = 0; - let highestLevelText = ''; - let msgTime = 0; + messagesPerJob.forEach((job) => { + // ignore system messages (id==='') + if (job.key !== '' && job.levels && job.levels.buckets && job.levels.buckets.length) { + let highestLevel = 0; + let highestLevelText = ''; + let msgTime = 0; - job.levels.buckets.forEach((level) => { - const label = level.key; - // note the highest message level - if (LEVEL[label] > highestLevel) { - highestLevel = LEVEL[label]; - if ( - level.latestMessage && - level.latestMessage.buckets && - level.latestMessage.buckets.length - ) { - level.latestMessage.buckets.forEach((msg) => { - // there should only be one result here. - highestLevelText = msg.key; + job.levels.buckets.forEach((level) => { + const label = level.key; + // note the highest message level + if (LEVEL[label] > highestLevel) { + highestLevel = LEVEL[label]; + if ( + level.latestMessage && + level.latestMessage.buckets && + level.latestMessage.buckets.length + ) { + level.latestMessage.buckets.forEach((msg) => { + // there should only be one result here. + highestLevelText = msg.key; - // note the time in ms for the highest level - // so we can filter them out later if they're earlier than the - // job's create time. - if (msg.latestMessage && msg.latestMessage.value_as_string) { - const time = moment(msg.latestMessage.value_as_string); - msgTime = time.valueOf(); - } - }); - } + // note the time in ms for the highest level + // so we can filter them out later if they're earlier than the + // job's create time. + if (msg.latestMessage && msg.latestMessage.value_as_string) { + const time = moment(msg.latestMessage.value_as_string); + msgTime = time.valueOf(); + } + }); } - }); - - if (msgTime !== 0 && highestLevel !== 0) { - jobMessages.push({ - job_id: job.key, - highestLevelText, - highestLevel: levelToText(highestLevel), - msgTime, - }); } + }); + + if (msgTime !== 0 && highestLevel !== 0) { + jobMessages.push({ + job_id: job.key, + highestLevelText, + highestLevel: levelToText(highestLevel), + msgTime, + }); } - }); - return jobMessages; - } catch (e) { - throw e; - } + } + }); + return jobMessages; } function levelToText(level) { diff --git a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts index 98e1be48bb766..c0eb1b72825df 100644 --- a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts +++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { i18n } from '@kbn/i18n'; import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils'; @@ -26,7 +26,7 @@ interface Results { }; } -export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterClient) { +export function datafeedsProvider({ asInternalUser }: IScopedClusterClient) { async function forceStartDatafeeds(datafeedIds: string[], start?: number, end?: number) { const jobIds = await getJobIdsByDatafeedId(); const doStartsCalled = datafeedIds.reduce((acc, cur) => { @@ -42,8 +42,8 @@ export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterCl try { await startDatafeed(datafeedId, start, end); return { started: true }; - } catch (error) { - return { started: false, error }; + } catch ({ body }) { + return { started: false, error: body }; } } else { return { started: true }; @@ -66,7 +66,7 @@ export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterCl results[datafeedId] = await doStart(datafeedId); return fillResultsWithTimeouts(results, datafeedId, datafeedIds, JOB_STATE.OPENED); } - results[datafeedId] = { started: false, error }; + results[datafeedId] = { started: false, error: error.body }; } } else { results[datafeedId] = { @@ -84,8 +84,8 @@ export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterCl async function openJob(jobId: string) { let opened = false; try { - const resp = await callAsInternalUser('ml.openJob', { jobId }); - opened = resp.opened; + const { body } = await asInternalUser.ml.openJob({ job_id: jobId }); + opened = body.opened; } catch (error) { if (error.statusCode === 409) { opened = true; @@ -97,7 +97,11 @@ export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterCl } async function startDatafeed(datafeedId: string, start?: number, end?: number) { - return callAsInternalUser('ml.startDatafeed', { datafeedId, start, end }); + return asInternalUser.ml.startDatafeed({ + datafeed_id: datafeedId, + start: (start as unknown) as string, + end: (end as unknown) as string, + }); } async function stopDatafeeds(datafeedIds: string[]) { @@ -105,7 +109,12 @@ export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterCl for (const datafeedId of datafeedIds) { try { - results[datafeedId] = await callAsInternalUser('ml.stopDatafeed', { datafeedId }); + const { body } = await asInternalUser.ml.stopDatafeed<{ + started: boolean; + }>({ + datafeed_id: datafeedId, + }); + results[datafeedId] = body; } catch (error) { if (isRequestTimeout(error)) { return fillResultsWithTimeouts(results, datafeedId, datafeedIds, DATAFEED_STATE.STOPPED); @@ -117,11 +126,17 @@ export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterCl } async function forceDeleteDatafeed(datafeedId: string) { - return callAsInternalUser('ml.deleteDatafeed', { datafeedId, force: true }); + const { body } = await asInternalUser.ml.deleteDatafeed({ + datafeed_id: datafeedId, + force: true, + }); + return body; } async function getDatafeedIdsByJobId() { - const { datafeeds } = (await callAsInternalUser('ml.datafeeds')) as MlDatafeedsResponse; + const { + body: { datafeeds }, + } = await asInternalUser.ml.getDatafeeds(); return datafeeds.reduce((acc, cur) => { acc[cur.job_id] = cur.datafeed_id; return acc; @@ -129,7 +144,9 @@ export function datafeedsProvider({ callAsInternalUser }: ILegacyScopedClusterCl } async function getJobIdsByDatafeedId() { - const { datafeeds } = (await callAsInternalUser('ml.datafeeds')) as MlDatafeedsResponse; + const { + body: { datafeeds }, + } = await asInternalUser.ml.getDatafeeds(); return datafeeds.reduce((acc, cur) => { acc[cur.datafeed_id] = cur.job_id; return acc; diff --git a/x-pack/plugins/ml/server/models/job_service/error_utils.ts b/x-pack/plugins/ml/server/models/job_service/error_utils.ts index 8a47993546fb8..dc871a9dce805 100644 --- a/x-pack/plugins/ml/server/models/job_service/error_utils.ts +++ b/x-pack/plugins/ml/server/models/job_service/error_utils.ts @@ -7,11 +7,11 @@ import { i18n } from '@kbn/i18n'; import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; -const REQUEST_TIMEOUT = 'RequestTimeout'; +const REQUEST_TIMEOUT_NAME = 'RequestTimeout'; type ACTION_STATE = DATAFEED_STATE | JOB_STATE; -export function isRequestTimeout(error: { displayName: string }) { - return error.displayName === REQUEST_TIMEOUT; +export function isRequestTimeout(error: { name: string }) { + return error.name === REQUEST_TIMEOUT_NAME; } interface Results { diff --git a/x-pack/plugins/ml/server/models/job_service/groups.ts b/x-pack/plugins/ml/server/models/job_service/groups.ts index c4ea854c14f87..0f53d27f2eddf 100644 --- a/x-pack/plugins/ml/server/models/job_service/groups.ts +++ b/x-pack/plugins/ml/server/models/job_service/groups.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { CalendarManager } from '../calendar'; import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; import { Job } from '../../../common/types/anomaly_detection_jobs'; @@ -23,18 +23,19 @@ interface Results { }; } -export function groupsProvider(mlClusterClient: ILegacyScopedClusterClient) { - const calMngr = new CalendarManager(mlClusterClient); - const { callAsInternalUser } = mlClusterClient; +export function groupsProvider(client: IScopedClusterClient) { + const calMngr = new CalendarManager(client); + const { asInternalUser } = client; async function getAllGroups() { const groups: { [id: string]: Group } = {}; const jobIds: { [id: string]: undefined | null } = {}; - const [{ jobs }, calendars] = await Promise.all([ - callAsInternalUser('ml.jobs') as Promise, + const [{ body }, calendars] = await Promise.all([ + asInternalUser.ml.getJobs(), calMngr.getAllCalendars(), ]); + const { jobs } = body; if (jobs) { jobs.forEach((job) => { jobIds[job.job_id] = null; @@ -80,10 +81,10 @@ export function groupsProvider(mlClusterClient: ILegacyScopedClusterClient) { for (const job of jobs) { const { job_id: jobId, groups } = job; try { - await callAsInternalUser('ml.updateJob', { jobId, body: { groups } }); + await asInternalUser.ml.updateJob({ job_id: jobId, body: { groups } }); results[jobId] = { success: true }; - } catch (error) { - results[jobId] = { success: false, error }; + } catch ({ body }) { + results[jobId] = { success: false, error: body }; } } return results; diff --git a/x-pack/plugins/ml/server/models/job_service/index.ts b/x-pack/plugins/ml/server/models/job_service/index.ts index 1ff33a7b00f0b..6fea5d3b5a491 100644 --- a/x-pack/plugins/ml/server/models/job_service/index.ts +++ b/x-pack/plugins/ml/server/models/job_service/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { datafeedsProvider } from './datafeeds'; import { jobsProvider } from './jobs'; import { groupsProvider } from './groups'; @@ -12,14 +12,14 @@ import { newJobCapsProvider } from './new_job_caps'; import { newJobChartsProvider, topCategoriesProvider } from './new_job'; import { modelSnapshotProvider } from './model_snapshots'; -export function jobServiceProvider(mlClusterClient: ILegacyScopedClusterClient) { +export function jobServiceProvider(client: IScopedClusterClient) { return { - ...datafeedsProvider(mlClusterClient), - ...jobsProvider(mlClusterClient), - ...groupsProvider(mlClusterClient), - ...newJobCapsProvider(mlClusterClient), - ...newJobChartsProvider(mlClusterClient), - ...topCategoriesProvider(mlClusterClient), - ...modelSnapshotProvider(mlClusterClient), + ...datafeedsProvider(client), + ...jobsProvider(client), + ...groupsProvider(client), + ...newJobCapsProvider(client), + ...newJobChartsProvider(client), + ...topCategoriesProvider(client), + ...modelSnapshotProvider(client), }; } diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index 0aa1cfdae13c7..e047d31ba6eb7 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { uniq } from 'lodash'; import Boom from 'boom'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { parseTimeIntervalForJob } from '../../../common/util/job_utils'; import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; import { @@ -22,7 +22,7 @@ import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; import { datafeedsProvider, MlDatafeedsResponse, MlDatafeedsStatsResponse } from './datafeeds'; import { jobAuditMessagesProvider } from '../job_audit_messages'; import { resultsServiceProvider } from '../results_service'; -import { CalendarManager, Calendar } from '../calendar'; +import { CalendarManager } from '../calendar'; import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils'; import { getEarliestDatafeedStartTime, @@ -47,16 +47,16 @@ interface Results { }; } -export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { - const { callAsInternalUser } = mlClusterClient; +export function jobsProvider(client: IScopedClusterClient) { + const { asInternalUser } = client; - const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(mlClusterClient); - const { getAuditMessagesSummary } = jobAuditMessagesProvider(mlClusterClient); - const { getLatestBucketTimestampByJob } = resultsServiceProvider(mlClusterClient); - const calMngr = new CalendarManager(mlClusterClient); + const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(client); + const { getAuditMessagesSummary } = jobAuditMessagesProvider(client); + const { getLatestBucketTimestampByJob } = resultsServiceProvider(client); + const calMngr = new CalendarManager(client); async function forceDeleteJob(jobId: string) { - return callAsInternalUser('ml.deleteJob', { jobId, force: true }); + return asInternalUser.ml.deleteJob({ job_id: jobId, force: true, wait_for_completion: false }); } async function deleteJobs(jobIds: string[]) { @@ -78,7 +78,7 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { if (isRequestTimeout(error)) { return fillResultsWithTimeouts(results, jobId, jobIds, DATAFEED_STATE.DELETED); } - results[jobId] = { deleted: false, error }; + results[jobId] = { deleted: false, error: error.body }; } } } catch (error) { @@ -90,7 +90,7 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { DATAFEED_STATE.DELETED ); } - results[jobId] = { deleted: false, error }; + results[jobId] = { deleted: false, error: error.body }; } } return results; @@ -100,7 +100,7 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { const results: Results = {}; for (const jobId of jobIds) { try { - await callAsInternalUser('ml.closeJob', { jobId }); + await asInternalUser.ml.closeJob({ job_id: jobId }); results[jobId] = { closed: true }; } catch (error) { if (isRequestTimeout(error)) { @@ -109,23 +109,23 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { if ( error.statusCode === 409 && - error.response && - error.response.includes('datafeed') === false + error.body.error?.reason && + error.body.error.reason.includes('datafeed') === false ) { // the close job request may fail (409) if the job has failed or if the datafeed hasn't been stopped. // if the job has failed we want to attempt a force close. // however, if we received a 409 due to the datafeed being started we should not attempt a force close. try { - await callAsInternalUser('ml.closeJob', { jobId, force: true }); + await asInternalUser.ml.closeJob({ job_id: jobId, force: true }); results[jobId] = { closed: true }; } catch (error2) { - if (isRequestTimeout(error)) { + if (isRequestTimeout(error2)) { return fillResultsWithTimeouts(results, jobId, jobIds, JOB_STATE.CLOSED); } - results[jobId] = { closed: false, error: error2 }; + results[jobId] = { closed: false, error: error2.body }; } } else { - results[jobId] = { closed: false, error }; + results[jobId] = { closed: false, error: error.body }; } } } @@ -139,12 +139,12 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { throw Boom.notFound(`Cannot find datafeed for job ${jobId}`); } - const dfResult = await callAsInternalUser('ml.stopDatafeed', { datafeedId, force: true }); - if (!dfResult || dfResult.stopped !== true) { + const { body } = await asInternalUser.ml.stopDatafeed({ datafeed_id: datafeedId, force: true }); + if (body.stopped !== true) { return { success: false }; } - await callAsInternalUser('ml.closeJob', { jobId, force: true }); + await asInternalUser.ml.closeJob({ job_id: jobId, force: true }); return { success: true }; } @@ -256,41 +256,26 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { const calendarsByJobId: { [jobId: string]: string[] } = {}; const globalCalendars: string[] = []; - const requests: [ - Promise, - Promise, - Promise, - Promise, - Promise, - Promise<{ [id: string]: number | undefined }> - ] = [ - jobIds.length > 0 - ? (callAsInternalUser('ml.jobs', { jobId: jobIds }) as Promise) // move length check in side call - : (callAsInternalUser('ml.jobs') as Promise), - jobIds.length > 0 - ? (callAsInternalUser('ml.jobStats', { jobId: jobIds }) as Promise) - : (callAsInternalUser('ml.jobStats') as Promise), - callAsInternalUser('ml.datafeeds') as Promise, - callAsInternalUser('ml.datafeedStats') as Promise, - calMngr.getAllCalendars(), - getLatestBucketTimestampByJob(), - ]; - + const jobIdsString = jobIds.join(); const [ - jobResults, - jobStatsResults, - datafeedResults, - datafeedStatsResults, + { body: jobResults }, + { body: jobStatsResults }, + { body: datafeedResults }, + { body: datafeedStatsResults }, calendarResults, latestBucketTimestampByJob, - ] = await Promise.all< - MlJobsResponse, - MlJobsStatsResponse, - MlDatafeedsResponse, - MlDatafeedsStatsResponse, - Calendar[], - { [id: string]: number | undefined } - >(requests); + ] = await Promise.all([ + asInternalUser.ml.getJobs( + jobIds.length > 0 ? { job_id: jobIdsString } : undefined + ), + asInternalUser.ml.getJobStats( + jobIds.length > 0 ? { job_id: jobIdsString } : undefined + ), + asInternalUser.ml.getDatafeeds(), + asInternalUser.ml.getDatafeedStats(), + calMngr.getAllCalendars(), + getLatestBucketTimestampByJob(), + ]); if (datafeedResults && datafeedResults.datafeeds) { datafeedResults.datafeeds.forEach((datafeed) => { @@ -400,9 +385,9 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { const detailed = true; const jobIds = []; try { - const tasksList = await callAsInternalUser('tasks.list', { actions, detailed }); - Object.keys(tasksList.nodes).forEach((nodeId) => { - const tasks = tasksList.nodes[nodeId].tasks; + const { body } = await asInternalUser.tasks.list({ actions, detailed }); + Object.keys(body.nodes).forEach((nodeId) => { + const tasks = body.nodes[nodeId].tasks; Object.keys(tasks).forEach((taskId) => { jobIds.push(tasks[taskId].description.replace(/^delete-job-/, '')); }); @@ -410,7 +395,9 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { } catch (e) { // if the user doesn't have permission to load the task list, // use the jobs list to get the ids of deleting jobs - const { jobs } = (await callAsInternalUser('ml.jobs')) as MlJobsResponse; + const { + body: { jobs }, + } = await asInternalUser.ml.getJobs(); jobIds.push(...jobs.filter((j) => j.deleting === true).map((j) => j.job_id)); } return { jobIds }; @@ -421,13 +408,13 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { // e.g. *_low_request_rate_ecs async function jobsExist(jobIds: string[] = []) { // Get the list of job IDs. - const jobsInfo = (await callAsInternalUser('ml.jobs', { - jobId: jobIds, - })) as MlJobsResponse; + const { body } = await asInternalUser.ml.getJobs({ + job_id: jobIds.join(), + }); const results: { [id: string]: boolean } = {}; - if (jobsInfo.count > 0) { - const allJobIds = jobsInfo.jobs.map((job) => job.job_id); + if (body.count > 0) { + const allJobIds = body.jobs.map((job) => job.job_id); // Check if each of the supplied IDs match existing jobs. jobIds.forEach((jobId) => { @@ -446,9 +433,9 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { } async function getAllJobAndGroupIds() { - const { getAllGroups } = groupsProvider(mlClusterClient); - const jobs = (await callAsInternalUser('ml.jobs')) as MlJobsResponse; - const jobIds = jobs.jobs.map((job) => job.job_id); + const { getAllGroups } = groupsProvider(client); + const { body } = await asInternalUser.ml.getJobs(); + const jobIds = body.jobs.map((job) => job.job_id); const groups = await getAllGroups(); const groupIds = groups.map((group) => group.id); @@ -460,13 +447,13 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { async function getLookBackProgress(jobId: string, start: number, end: number) { const datafeedId = `datafeed-${jobId}`; - const [jobStats, isRunning] = await Promise.all([ - callAsInternalUser('ml.jobStats', { jobId: [jobId] }) as Promise, + const [{ body }, isRunning] = await Promise.all([ + asInternalUser.ml.getJobStats({ job_id: jobId }), isDatafeedRunning(datafeedId), ]); - if (jobStats.jobs.length) { - const statsForJob = jobStats.jobs[0]; + if (body.jobs.length) { + const statsForJob = body.jobs[0]; const time = statsForJob.data_counts.latest_record_timestamp; const progress = (time - start) / (end - start); const isJobClosed = statsForJob.state === JOB_STATE.CLOSED; @@ -480,11 +467,11 @@ export function jobsProvider(mlClusterClient: ILegacyScopedClusterClient) { } async function isDatafeedRunning(datafeedId: string) { - const stats = (await callAsInternalUser('ml.datafeedStats', { - datafeedId: [datafeedId], - })) as MlDatafeedsStatsResponse; - if (stats.datafeeds.length) { - const state = stats.datafeeds[0].state; + const { body } = await asInternalUser.ml.getDatafeedStats({ + datafeed_id: datafeedId, + }); + if (body.datafeeds.length) { + const state = body.datafeeds[0].state; return ( state === DATAFEED_STATE.STARTED || state === DATAFEED_STATE.STARTING || diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts index 576d6f8cbb160..34206a68ffeb9 100644 --- a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts +++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { ModelSnapshot } from '../../../common/types/anomaly_detection_jobs'; import { datafeedsProvider } from './datafeeds'; import { FormCalendar, CalendarManager } from '../calendar'; @@ -19,9 +19,9 @@ export interface RevertModelSnapshotResponse { model: ModelSnapshot; } -export function modelSnapshotProvider(mlClusterClient: ILegacyScopedClusterClient) { - const { callAsInternalUser } = mlClusterClient; - const { forceStartDatafeeds, getDatafeedIdsByJobId } = datafeedsProvider(mlClusterClient); +export function modelSnapshotProvider(client: IScopedClusterClient) { + const { asInternalUser } = client; + const { forceStartDatafeeds, getDatafeedIdsByJobId } = datafeedsProvider(client); async function revertModelSnapshot( jobId: string, @@ -33,13 +33,13 @@ export function modelSnapshotProvider(mlClusterClient: ILegacyScopedClusterClien ) { let datafeedId = `datafeed-${jobId}`; // ensure job exists - await callAsInternalUser('ml.jobs', { jobId: [jobId] }); + await asInternalUser.ml.getJobs({ job_id: jobId }); try { // ensure the datafeed exists // the datafeed is probably called datafeed- - await callAsInternalUser('ml.datafeeds', { - datafeedId: [datafeedId], + await asInternalUser.ml.getDatafeeds({ + datafeed_id: datafeedId, }); } catch (e) { // if the datafeed isn't called datafeed- @@ -52,19 +52,21 @@ export function modelSnapshotProvider(mlClusterClient: ILegacyScopedClusterClien } // ensure the snapshot exists - const snapshot = (await callAsInternalUser('ml.modelSnapshots', { - jobId, - snapshotId, - })) as ModelSnapshotsResponse; + const { body: snapshot } = await asInternalUser.ml.getModelSnapshots({ + job_id: jobId, + snapshot_id: snapshotId, + }); // apply the snapshot revert - const { model } = (await callAsInternalUser('ml.revertModelSnapshot', { - jobId, - snapshotId, + const { + body: { model }, + } = await asInternalUser.ml.revertModelSnapshot({ + job_id: jobId, + snapshot_id: snapshotId, body: { delete_intervening_results: deleteInterveningResults, }, - })) as RevertModelSnapshotResponse; + }); // create calendar (if specified) and replay datafeed if (replay && model.snapshot_id === snapshotId && snapshot.model_snapshots.length) { @@ -85,7 +87,7 @@ export function modelSnapshotProvider(mlClusterClient: ILegacyScopedClusterClien end_time: s.end, })), }; - const cm = new CalendarManager(mlClusterClient); + const cm = new CalendarManager(client); await cm.newCalendar(calendar); } diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index ca3e0cef21049..6b9f30b2ae00b 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { chunk } from 'lodash'; import { SearchResponse } from 'elasticsearch'; import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../common/constants/categorization_job'; @@ -18,9 +18,9 @@ import { ValidationResults } from './validation_results'; const CHUNK_SIZE = 100; export function categorizationExamplesProvider({ - callAsCurrentUser, - callAsInternalUser, -}: ILegacyScopedClusterClient) { + asCurrentUser, + asInternalUser, +}: IScopedClusterClient) { const validationResults = new ValidationResults(); async function categorizationExamples( @@ -57,7 +57,7 @@ export function categorizationExamplesProvider({ } } - const results: SearchResponse<{ [id: string]: string }> = await callAsCurrentUser('search', { + const { body } = await asCurrentUser.search>({ index: indexPatternTitle, size, body: { @@ -67,7 +67,7 @@ export function categorizationExamplesProvider({ }, }); - const tempExamples = results.hits.hits.map(({ _source }) => _source[categorizationFieldName]); + const tempExamples = body.hits.hits.map(({ _source }) => _source[categorizationFieldName]); validationResults.createNullValueResult(tempExamples); @@ -112,7 +112,9 @@ export function categorizationExamplesProvider({ } async function loadTokens(examples: string[], analyzer: CategorizationAnalyzer) { - const { tokens }: { tokens: Token[] } = await callAsInternalUser('indices.analyze', { + const { + body: { tokens }, + } = await asInternalUser.indices.analyze<{ tokens: Token[] }>({ body: { ...getAnalyzer(analyzer), text: examples, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts index 5ade86806f383..347afec8ef73c 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts @@ -5,13 +5,13 @@ */ import { SearchResponse } from 'elasticsearch'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns'; import { CategoryId, Category } from '../../../../../common/types/categories'; -export function topCategoriesProvider({ callAsInternalUser }: ILegacyScopedClusterClient) { - async function getTotalCategories(jobId: string): Promise<{ total: number }> { - const totalResp = await callAsInternalUser('search', { +export function topCategoriesProvider({ asInternalUser }: IScopedClusterClient) { + async function getTotalCategories(jobId: string): Promise { + const { body } = await asInternalUser.search>({ index: ML_RESULTS_INDEX_PATTERN, size: 0, body: { @@ -33,11 +33,12 @@ export function topCategoriesProvider({ callAsInternalUser }: ILegacyScopedClust }, }, }); - return totalResp?.hits?.total?.value ?? 0; + // @ts-ignore total is an object here + return body?.hits?.total?.value ?? 0; } async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { - const top: SearchResponse = await callAsInternalUser('search', { + const { body } = await asInternalUser.search>({ index: ML_RESULTS_INDEX_PATTERN, size: 0, body: { @@ -76,7 +77,7 @@ export function topCategoriesProvider({ callAsInternalUser }: ILegacyScopedClust const catCounts: Array<{ id: CategoryId; count: number; - }> = top.aggregations?.cat_count?.buckets.map((c: any) => ({ + }> = body.aggregations?.cat_count?.buckets.map((c: any) => ({ id: c.key, count: c.doc_count, })); @@ -99,7 +100,7 @@ export function topCategoriesProvider({ callAsInternalUser }: ILegacyScopedClust field: 'category_id', }, }; - const result: SearchResponse = await callAsInternalUser('search', { + const { body } = await asInternalUser.search>({ index: ML_RESULTS_INDEX_PATTERN, size, body: { @@ -118,7 +119,7 @@ export function topCategoriesProvider({ callAsInternalUser }: ILegacyScopedClust }, }); - return result.hits.hits?.map((c: { _source: Category }) => c._source) || []; + return body.hits.hits?.map((c: { _source: Category }) => c._source) || []; } async function topCategories(jobId: string, numberOfCategories: number) { diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts index 4b90283a3a966..60595ccedff45 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts @@ -144,7 +144,7 @@ export class ValidationResults { this.createPrivilegesErrorResult(error); return; } - const message: string = error.message; + const message: string = error.body.error?.reason; if (message) { const rxp = /exceeded the allowed maximum of \[(\d+?)\]/; const match = rxp.exec(message); @@ -170,7 +170,7 @@ export class ValidationResults { } public createPrivilegesErrorResult(error: any) { - const message: string = error.message; + const message: string = error.body.error?.reason; if (message) { this._results.push({ id: VALIDATION_RESULT.INSUFFICIENT_PRIVILEGES, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts b/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts index 63ae2c624ac38..da7d8d0577e4e 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { newJobLineChartProvider } from './line_chart'; import { newJobPopulationChartProvider } from './population_chart'; -export function newJobChartsProvider(mlClusterClient: ILegacyScopedClusterClient) { - const { newJobLineChart } = newJobLineChartProvider(mlClusterClient); - const { newJobPopulationChart } = newJobPopulationChartProvider(mlClusterClient); +export function newJobChartsProvider(client: IScopedClusterClient) { + const { newJobLineChart } = newJobLineChartProvider(client); + const { newJobPopulationChart } = newJobPopulationChartProvider(client); return { newJobLineChart, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts index 3080b37867de5..9eea1ea2a28ae 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; @@ -23,7 +23,7 @@ interface ProcessedResults { totalResults: number; } -export function newJobLineChartProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) { +export function newJobLineChartProvider({ asCurrentUser }: IScopedClusterClient) { async function newJobLineChart( indexPatternTitle: string, timeField: string, @@ -47,9 +47,9 @@ export function newJobLineChartProvider({ callAsCurrentUser }: ILegacyScopedClus splitFieldValue ); - const results = await callAsCurrentUser('search', json); + const { body } = await asCurrentUser.search(json); return processSearchResults( - results, + body, aggFieldNamePairs.map((af) => af.field) ); } diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts index ab75787a0069f..567afec809405 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; @@ -29,7 +29,7 @@ interface ProcessedResults { totalResults: number; } -export function newJobPopulationChartProvider({ callAsCurrentUser }: ILegacyScopedClusterClient) { +export function newJobPopulationChartProvider({ asCurrentUser }: IScopedClusterClient) { async function newJobPopulationChart( indexPatternTitle: string, timeField: string, @@ -51,15 +51,11 @@ export function newJobPopulationChartProvider({ callAsCurrentUser }: ILegacyScop splitFieldName ); - try { - const results = await callAsCurrentUser('search', json); - return processSearchResults( - results, - aggFieldNamePairs.map((af) => af.field) - ); - } catch (error) { - return { error }; - } + const { body } = await asCurrentUser.search(json); + return processSearchResults( + body, + aggFieldNamePairs.map((af) => af.field) + ); } return { diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index fd20610450cc1..c3b1de64c3eb5 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { cloneDeep } from 'lodash'; import { SavedObjectsClientContract } from 'kibana/server'; import { @@ -40,35 +40,36 @@ const supportedTypes: string[] = [ export function fieldServiceProvider( indexPattern: string, isRollup: boolean, - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract ) { - return new FieldsService(indexPattern, isRollup, mlClusterClient, savedObjectsClient); + return new FieldsService(indexPattern, isRollup, client, savedObjectsClient); } class FieldsService { private _indexPattern: string; private _isRollup: boolean; - private _mlClusterClient: ILegacyScopedClusterClient; + private _mlClusterClient: IScopedClusterClient; private _savedObjectsClient: SavedObjectsClientContract; constructor( indexPattern: string, isRollup: boolean, - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract ) { this._indexPattern = indexPattern; this._isRollup = isRollup; - this._mlClusterClient = mlClusterClient; + this._mlClusterClient = client; this._savedObjectsClient = savedObjectsClient; } private async loadFieldCaps(): Promise { - return this._mlClusterClient.callAsCurrentUser('fieldCaps', { + const { body } = await this._mlClusterClient.asCurrentUser.fieldCaps({ index: this._indexPattern, fields: '*', }); + return body; } // create field object from the results from _field_caps diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts index 38d6481e02a74..891cb2e0d1e64 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts @@ -21,28 +21,23 @@ describe('job_service - job_caps', () => { let savedObjectsClientMock: any; beforeEach(() => { - const callAsNonRollupMock = jest.fn((action: string) => { - switch (action) { - case 'fieldCaps': - return farequoteFieldCaps; - } - }); + const asNonRollupMock = { + fieldCaps: jest.fn(() => ({ body: farequoteFieldCaps })), + }; + mlClusterClientNonRollupMock = { - callAsCurrentUser: callAsNonRollupMock, - callAsInternalUser: callAsNonRollupMock, + asCurrentUser: asNonRollupMock, + asInternalUser: asNonRollupMock, + }; + + const callAsRollupMock = { + fieldCaps: jest.fn(() => ({ body: cloudwatchFieldCaps })), + rollup: { getRollupIndexCaps: jest.fn(() => Promise.resolve({ body: rollupCaps })) }, }; - const callAsRollupMock = jest.fn((action: string) => { - switch (action) { - case 'fieldCaps': - return cloudwatchFieldCaps; - case 'ml.rollupIndexCapabilities': - return Promise.resolve(rollupCaps); - } - }); mlClusterClientRollupMock = { - callAsCurrentUser: callAsRollupMock, - callAsInternalUser: callAsRollupMock, + asCurrentUser: callAsRollupMock, + asInternalUser: callAsRollupMock, }; savedObjectsClientMock = { diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts index 5616dade53a78..7559111d012d0 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; +import { IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; import { Aggregation, Field, NewJobCaps } from '../../../../common/types/fields'; import { fieldServiceProvider } from './field_service'; @@ -12,18 +12,13 @@ interface NewJobCapsResponse { [indexPattern: string]: NewJobCaps; } -export function newJobCapsProvider(mlClusterClient: ILegacyScopedClusterClient) { +export function newJobCapsProvider(client: IScopedClusterClient) { async function newJobCaps( indexPattern: string, isRollup: boolean = false, savedObjectsClient: SavedObjectsClientContract ): Promise { - const fieldService = fieldServiceProvider( - indexPattern, - isRollup, - mlClusterClient, - savedObjectsClient - ); + const fieldService = fieldServiceProvider(indexPattern, isRollup, client, savedObjectsClient); const { aggs, fields } = await fieldService.getData(); convertForStringify(aggs, fields); diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts index f3a9bd49c27d6..b7f4c8af62283 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { SavedObject } from 'kibana/server'; import { IndexPatternAttributes } from 'src/plugins/data/server'; import { SavedObjectsClientContract } from 'kibana/server'; @@ -22,7 +22,7 @@ export interface RollupJob { export async function rollupServiceProvider( indexPattern: string, - { callAsCurrentUser }: ILegacyScopedClusterClient, + { asCurrentUser }: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract ) { const rollupIndexPatternObject = await loadRollupIndexPattern(indexPattern, savedObjectsClient); @@ -32,8 +32,8 @@ export async function rollupServiceProvider( if (rollupIndexPatternObject !== null) { const parsedTypeMetaData = JSON.parse(rollupIndexPatternObject.attributes.typeMeta); const rollUpIndex: string = parsedTypeMetaData.params.rollup_index; - const rollupCaps = await callAsCurrentUser('ml.rollupIndexCapabilities', { - indexPattern: rollUpIndex, + const { body: rollupCaps } = await asCurrentUser.rollup.getRollupIndexCaps({ + index: rollUpIndex, }); const indexRollupCaps = rollupCaps[rollUpIndex]; diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts index 1c74953e4dda9..810d0ae9dcd87 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts @@ -4,48 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { validateJob, ValidateJobPayload } from './job_validation'; import { JobValidationMessage } from '../../../common/constants/messages'; -const mlClusterClient = ({ - // mock callAsCurrentUser - callAsCurrentUser: (method: string) => { - return new Promise((resolve) => { - if (method === 'fieldCaps') { - resolve({ fields: [] }); - return; - } else if (method === 'ml.info') { - resolve({ +const callAs = { + fieldCaps: () => Promise.resolve({ body: { fields: [] } }), + ml: { + info: () => + Promise.resolve({ + body: { limits: { effective_max_model_memory_limit: '100MB', max_model_memory_limit: '1GB', }, - }); - } - resolve({}); - }) as Promise; + }, + }), }, + search: () => Promise.resolve({ body: {} }), +}; - // mock callAsInternalUser - callAsInternalUser: (method: string) => { - return new Promise((resolve) => { - if (method === 'fieldCaps') { - resolve({ fields: [] }); - return; - } else if (method === 'ml.info') { - resolve({ - limits: { - effective_max_model_memory_limit: '100MB', - max_model_memory_limit: '1GB', - }, - }); - } - resolve({}); - }) as Promise; - }, -} as unknown) as ILegacyScopedClusterClient; +const mlClusterClient = ({ + asCurrentUser: callAs, + asInternalUser: callAs, +} as unknown) as IScopedClusterClient; // Note: The tests cast `payload` as any // so we can simulate possible runtime payloads diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts index 6692ecb22bd9e..9e272f1f770fc 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import Boom from 'boom'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { fieldsServiceProvider } from '../fields_service'; import { renderTemplate } from '../../../common/util/string_utils'; @@ -34,7 +34,7 @@ export type ValidateJobPayload = TypeOf; * @kbn/config-schema has checked the payload {@link validateJobSchema}. */ export async function validateJob( - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, payload: ValidateJobPayload, kbnVersion = 'current', isSecurityDisabled?: boolean @@ -63,8 +63,8 @@ export async function validateJob( // if no duration was part of the request, fall back to finding out // the time range of the time field of the index, but also check first // if the time field is a valid field of type 'date' using isValidTimeField() - if (typeof duration === 'undefined' && (await isValidTimeField(mlClusterClient, job))) { - const fs = fieldsServiceProvider(mlClusterClient); + if (typeof duration === 'undefined' && (await isValidTimeField(client, job))) { + const fs = fieldsServiceProvider(client); const index = job.datafeed_config.indices.join(','); const timeField = job.data_description.time_field; const timeRange = await fs.getTimeFieldRange(index, timeField, job.datafeed_config.query); @@ -79,24 +79,22 @@ export async function validateJob( // next run only the cardinality tests to find out if they trigger an error // so we can decide later whether certain additional tests should be run - const cardinalityMessages = await validateCardinality(mlClusterClient, job); + const cardinalityMessages = await validateCardinality(client, job); validationMessages.push(...cardinalityMessages); const cardinalityError = cardinalityMessages.some((m) => { return messages[m.id as MessageId].status === VALIDATION_STATUS.ERROR; }); validationMessages.push( - ...(await validateBucketSpan(mlClusterClient, job, duration, isSecurityDisabled)) + ...(await validateBucketSpan(client, job, duration, isSecurityDisabled)) ); - validationMessages.push(...(await validateTimeRange(mlClusterClient, job, duration))); + validationMessages.push(...(await validateTimeRange(client, job, duration))); // only run the influencer and model memory limit checks // if cardinality checks didn't return a message with an error level if (cardinalityError === false) { validationMessages.push(...(await validateInfluencers(job))); - validationMessages.push( - ...(await validateModelMemoryLimit(mlClusterClient, job, duration)) - ); + validationMessages.push(...(await validateModelMemoryLimit(client, job, duration))); } } else { validationMessages = basicValidation.messages; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js index 11f8d8967c4e0..315ad09176571 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js +++ b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js @@ -45,7 +45,7 @@ const pickBucketSpan = (bucketSpans) => { return bucketSpans[i]; }; -export async function validateBucketSpan(mlClusterClient, job, duration) { +export async function validateBucketSpan(client, job, duration) { validateJobObject(job); // if there is no duration, do not run the estimate test @@ -117,7 +117,7 @@ export async function validateBucketSpan(mlClusterClient, job, duration) { try { const estimations = estimatorConfigs.map((data) => { return new Promise((resolve) => { - estimateBucketSpanFactory(mlClusterClient)(data) + estimateBucketSpanFactory(client)(data) .then(resolve) // this catch gets triggered when the estimation code runs without error // but isn't able to come up with a bucket span estimation. diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts index f9145ab576d71..80418d590af76 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.test.ts @@ -24,12 +24,12 @@ import mockItSearchResponse from './__mocks__/mock_it_search_response.json'; const mlClusterClientFactory = (mockSearchResponse: any) => { const callAs = () => { return new Promise((resolve) => { - resolve(mockSearchResponse); + resolve({ body: mockSearchResponse }); }); }; return { - callAsCurrentUser: callAs, - callAsInternalUser: callAs, + asCurrentUser: callAs, + asInternalUser: callAs, }; }; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts index 16ee70ad9efde..1be0751e15f22 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts @@ -6,7 +6,7 @@ import cloneDeep from 'lodash/cloneDeep'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; @@ -24,21 +24,21 @@ const mockResponses = { const mlClusterClientFactory = ( responses: Record, fail = false -): ILegacyScopedClusterClient => { - const callAs = (requestName: string) => { - return new Promise((resolve, reject) => { - const response = responses[requestName]; - if (fail) { - reject(response); - } else { - resolve(response); - } - }) as Promise; +): IScopedClusterClient => { + const callAs = { + search: () => Promise.resolve({ body: responses.search }), + fieldCaps: () => Promise.resolve({ body: responses.fieldCaps }), }; - return { - callAsCurrentUser: callAs, - callAsInternalUser: callAs, + + const callAsFail = { + search: () => Promise.reject({ body: {} }), + fieldCaps: () => Promise.reject({ body: {} }), }; + + return ({ + asCurrentUser: fail === false ? callAs : callAsFail, + asInternalUser: fail === false ? callAs : callAsFail, + } as unknown) as IScopedClusterClient; }; describe('ML - validateCardinality', () => { diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts index 1545c4c0062ec..c5822b863c83d 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { DataVisualizer } from '../data_visualizer'; import { validateJobObject } from './validate_job_object'; @@ -43,12 +43,9 @@ type Validator = (obj: { messages: Messages; }>; -const validateFactory = ( - mlClusterClient: ILegacyScopedClusterClient, - job: CombinedJob -): Validator => { - const { callAsCurrentUser } = mlClusterClient; - const dv = new DataVisualizer(mlClusterClient); +const validateFactory = (client: IScopedClusterClient, job: CombinedJob): Validator => { + const { asCurrentUser } = client; + const dv = new DataVisualizer(client); const modelPlotConfigTerms = job?.model_plot_config?.terms ?? ''; const modelPlotConfigFieldCount = @@ -77,7 +74,7 @@ const validateFactory = ( ] as string[]; // use fieldCaps endpoint to get data about whether fields are aggregatable - const fieldCaps = await callAsCurrentUser('fieldCaps', { + const { body: fieldCaps } = await asCurrentUser.fieldCaps({ index: job.datafeed_config.indices.join(','), fields: uniqueFieldNames, }); @@ -154,7 +151,7 @@ const validateFactory = ( }; export async function validateCardinality( - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, job?: CombinedJob ): Promise | never { const messages: Messages = []; @@ -174,7 +171,7 @@ export async function validateCardinality( } // validate({ type, isInvalid }) asynchronously returns an array of validation messages - const validate = validateFactory(mlClusterClient, job); + const validate = validateFactory(client, job); const modelPlotEnabled = job.model_plot_config?.enabled ?? false; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts index 6ffb0e320982b..35792c66e66ec 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { CombinedJob, Detector } from '../../../common/types/anomaly_detection_jobs'; -import { ModelMemoryEstimate } from '../calculate_model_memory_limit/calculate_model_memory_limit'; +import { ModelMemoryEstimateResponse } from '../calculate_model_memory_limit/calculate_model_memory_limit'; import { validateModelMemoryLimit } from './validate_model_memory_limit'; describe('ML - validateModelMemoryLimit', () => { @@ -65,44 +65,36 @@ describe('ML - validateModelMemoryLimit', () => { }; // mock estimate model memory - const modelMemoryEstimateResponse: ModelMemoryEstimate = { + const modelMemoryEstimateResponse: ModelMemoryEstimateResponse = { model_memory_estimate: '40mb', }; interface MockAPICallResponse { - 'ml.estimateModelMemory'?: ModelMemoryEstimate; + 'ml.estimateModelMemory'?: ModelMemoryEstimateResponse; } - // mock callAsCurrentUser + // mock asCurrentUser // used in three places: // - to retrieve the info endpoint // - to search for cardinality of split field // - to retrieve field capabilities used in search for split field cardinality const getMockMlClusterClient = ({ 'ml.estimateModelMemory': estimateModelMemory, - }: MockAPICallResponse = {}): ILegacyScopedClusterClient => { - const callAs = (call: string) => { - if (typeof call === undefined) { - return Promise.reject(); - } - - let response = {}; - if (call === 'ml.info') { - response = mlInfoResponse; - } else if (call === 'search') { - response = cardinalitySearchResponse; - } else if (call === 'fieldCaps') { - response = fieldCapsResponse; - } else if (call === 'ml.estimateModelMemory') { - response = estimateModelMemory || modelMemoryEstimateResponse; - } - return Promise.resolve(response); + }: MockAPICallResponse = {}): IScopedClusterClient => { + const callAs = { + ml: { + info: () => Promise.resolve({ body: mlInfoResponse }), + estimateModelMemory: () => + Promise.resolve({ body: estimateModelMemory || modelMemoryEstimateResponse }), + }, + search: () => Promise.resolve({ body: cardinalitySearchResponse }), + fieldCaps: () => Promise.resolve({ body: fieldCapsResponse }), }; - return { - callAsCurrentUser: callAs, - callAsInternalUser: callAs, - }; + return ({ + asCurrentUser: callAs, + asInternalUser: callAs, + } as unknown) as IScopedClusterClient; }; function getJobConfig(influencers: string[] = [], detectors: Detector[] = []) { diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.ts b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.ts index 728342294c424..9733e17e0f379 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.ts @@ -5,7 +5,7 @@ */ import numeral from '@elastic/numeral'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; import { validateJobObject } from './validate_job_object'; import { calculateModelMemoryLimitProvider } from '../calculate_model_memory_limit'; @@ -16,11 +16,11 @@ import { MlInfoResponse } from '../../../common/types/ml_server_info'; const MODEL_MEMORY_LIMIT_MINIMUM_BYTES = 1048576; export async function validateModelMemoryLimit( - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, job: CombinedJob, duration?: { start?: number; end?: number } ) { - const { callAsInternalUser } = mlClusterClient; + const { asInternalUser } = client; validateJobObject(job); // retrieve the model memory limit specified by the user in the job config. @@ -52,12 +52,12 @@ export async function validateModelMemoryLimit( // retrieve the max_model_memory_limit value from the server // this will be unset unless the user has set this on their cluster - const info = (await callAsInternalUser('ml.info')) as MlInfoResponse; - const maxModelMemoryLimit = info.limits.max_model_memory_limit?.toUpperCase(); - const effectiveMaxModelMemoryLimit = info.limits.effective_max_model_memory_limit?.toUpperCase(); + const { body } = await asInternalUser.ml.info(); + const maxModelMemoryLimit = body.limits.max_model_memory_limit?.toUpperCase(); + const effectiveMaxModelMemoryLimit = body.limits.effective_max_model_memory_limit?.toUpperCase(); if (runCalcModelMemoryTest) { - const { modelMemoryLimit } = await calculateModelMemoryLimitProvider(mlClusterClient)( + const { modelMemoryLimit } = await calculateModelMemoryLimitProvider(client)( job.analysis_config, job.datafeed_config.indices.join(','), job.datafeed_config.query, diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts index a45be189ba3d8..12458af0521a9 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts @@ -6,7 +6,7 @@ import cloneDeep from 'lodash/cloneDeep'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; @@ -21,16 +21,15 @@ const mockSearchResponse = { search: mockTimeRange, }; -const mlClusterClientFactory = (resp: any): ILegacyScopedClusterClient => { - const callAs = (path: string) => { - return new Promise((resolve) => { - resolve(resp[path]); - }) as Promise; - }; - return { - callAsCurrentUser: callAs, - callAsInternalUser: callAs, +const mlClusterClientFactory = (response: any): IScopedClusterClient => { + const callAs = { + fieldCaps: () => Promise.resolve({ body: response.fieldCaps }), + search: () => Promise.resolve({ body: response.search }), }; + return ({ + asCurrentUser: callAs, + asInternalUser: callAs, + } as unknown) as IScopedClusterClient; }; function getMinimalValidJob() { diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts index a94ceffa90273..83d9621898f96 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/server'; import { parseInterval } from '../../../common/util/parse_interval'; import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; @@ -26,15 +26,12 @@ const BUCKET_SPAN_COMPARE_FACTOR = 25; const MIN_TIME_SPAN_MS = 7200000; const MIN_TIME_SPAN_READABLE = '2 hours'; -export async function isValidTimeField( - { callAsCurrentUser }: ILegacyScopedClusterClient, - job: CombinedJob -) { +export async function isValidTimeField({ asCurrentUser }: IScopedClusterClient, job: CombinedJob) { const index = job.datafeed_config.indices.join(','); const timeField = job.data_description.time_field; // check if time_field is of type 'date' or 'date_nanos' - const fieldCaps = await callAsCurrentUser('fieldCaps', { + const { body: fieldCaps } = await asCurrentUser.fieldCaps({ index, fields: [timeField], }); @@ -47,7 +44,7 @@ export async function isValidTimeField( } export async function validateTimeRange( - mlClientCluster: ILegacyScopedClusterClient, + mlClientCluster: IScopedClusterClient, job: CombinedJob, timeRange?: Partial ) { diff --git a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts index 9c0efe259844c..76dc68d2b59e3 100644 --- a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts +++ b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { PARTITION_FIELDS } from '../../../common/constants/anomalies'; import { PartitionFieldsType } from '../../../common/types/anomalies'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; @@ -74,9 +74,7 @@ function getFieldObject(fieldType: PartitionFieldsType, aggs: any) { : {}; } -export const getPartitionFieldsValuesFactory = ({ - callAsInternalUser, -}: ILegacyScopedClusterClient) => +export const getPartitionFieldsValuesFactory = ({ asInternalUser }: IScopedClusterClient) => /** * Gets the record of partition fields with possible values that fit the provided queries. * @param jobId - Job ID @@ -92,7 +90,7 @@ export const getPartitionFieldsValuesFactory = ({ earliestMs: number, latestMs: number ) { - const jobsResponse = await callAsInternalUser('ml.jobs', { jobId: [jobId] }); + const { body: jobsResponse } = await asInternalUser.ml.getJobs({ job_id: jobId }); if (jobsResponse.count === 0 || jobsResponse.jobs === undefined) { throw Boom.notFound(`Job with the id "${jobId}" not found`); } @@ -101,7 +99,7 @@ export const getPartitionFieldsValuesFactory = ({ const isModelPlotEnabled = job?.model_plot_config?.enabled; - const resp = await callAsInternalUser('search', { + const { body } = await asInternalUser.search({ index: ML_RESULTS_INDEX_PATTERN, size: 0, body: { @@ -151,7 +149,7 @@ export const getPartitionFieldsValuesFactory = ({ return PARTITION_FIELDS.reduce((acc, key) => { return { ...acc, - ...getFieldObject(key, resp.aggregations), + ...getFieldObject(key, body.aggregations), }; }, {}); }; diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts index 7be8bac61e69d..190b5d99309d7 100644 --- a/x-pack/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -9,7 +9,7 @@ import slice from 'lodash/slice'; import get from 'lodash/get'; import moment from 'moment'; import { SearchResponse } from 'elasticsearch'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import Boom from 'boom'; import { buildAnomalyTableItems } from './build_anomaly_table_items'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; @@ -40,8 +40,8 @@ interface Influencer { fieldValue: any; } -export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClient) { - const { callAsInternalUser } = mlClusterClient; +export function resultsServiceProvider(client: IScopedClusterClient) { + const { asInternalUser } = client; // Obtains data for the anomalies table, aggregating anomalies by day or hour as requested. // Return an Object with properties 'anomalies' and 'interval' (interval used to aggregate anomalies, // one of day, hour or second. Note 'auto' can be provided as the aggregationInterval in the request, @@ -144,7 +144,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }); } - const resp: SearchResponse = await callAsInternalUser('search', { + const { body } = await asInternalUser.search>({ index: ML_RESULTS_INDEX_PATTERN, rest_total_hits_as_int: true, size: maxRecords, @@ -178,9 +178,9 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie anomalies: [], interval: 'second', }; - if (resp.hits.total !== 0) { + if (body.hits.total !== 0) { let records: AnomalyRecordDoc[] = []; - resp.hits.hits.forEach((hit) => { + body.hits.hits.forEach((hit) => { records.push(hit._source); }); @@ -298,8 +298,8 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }, }; - const resp = await callAsInternalUser('search', query); - const maxScore = get(resp, ['aggregations', 'max_score', 'value'], null); + const { body } = await asInternalUser.search(query); + const maxScore = get(body, ['aggregations', 'max_score', 'value'], null); return { maxScore }; } @@ -336,7 +336,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie // Size of job terms agg, consistent with maximum number of jobs supported by Java endpoints. const maxJobs = 10000; - const resp = await callAsInternalUser('search', { + const { body } = await asInternalUser.search({ index: ML_RESULTS_INDEX_PATTERN, size: 0, body: { @@ -364,7 +364,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }); const bucketsByJobId: Array<{ key: string; maxTimestamp: { value?: number } }> = get( - resp, + body, ['aggregations', 'byJobId', 'buckets'], [] ); @@ -380,7 +380,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie // from the given index and job ID. // Returned response consists of a list of examples against category ID. async function getCategoryExamples(jobId: string, categoryIds: any, maxExamples: number) { - const resp = await callAsInternalUser('search', { + const { body } = await asInternalUser.search({ index: ML_RESULTS_INDEX_PATTERN, rest_total_hits_as_int: true, size: ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, // Matches size of records in anomaly summary table. @@ -394,8 +394,8 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }); const examplesByCategoryId: { [key: string]: any } = {}; - if (resp.hits.total !== 0) { - resp.hits.hits.forEach((hit: any) => { + if (body.hits.total !== 0) { + body.hits.hits.forEach((hit: any) => { if (maxExamples) { examplesByCategoryId[hit._source.category_id] = slice( hit._source.examples, @@ -415,7 +415,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie // Returned response contains four properties - categoryId, regex, examples // and terms (space delimited String of the common tokens matched in values of the category). async function getCategoryDefinition(jobId: string, categoryId: string) { - const resp = await callAsInternalUser('search', { + const { body } = await asInternalUser.search({ index: ML_RESULTS_INDEX_PATTERN, rest_total_hits_as_int: true, size: 1, @@ -429,8 +429,8 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }); const definition = { categoryId, terms: null, regex: null, examples: [] }; - if (resp.hits.total !== 0) { - const source = resp.hits.hits[0]._source; + if (body.hits.total !== 0) { + const source = body.hits.hits[0]._source; definition.categoryId = source.category_id; definition.regex = source.regex; definition.terms = source.terms; @@ -456,7 +456,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }, }); } - const results: SearchResponse = await callAsInternalUser('search', { + const { body } = await asInternalUser.search>({ index: ML_RESULTS_INDEX_PATTERN, body: { query: { @@ -473,7 +473,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }, }, }); - return results ? results.hits.hits.map((r) => r._source) : []; + return body ? body.hits.hits.map((r) => r._source) : []; } async function getCategoryStoppedPartitions( @@ -485,15 +485,15 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }; // first determine from job config if stop_on_warn is true // if false return [] - const jobConfigResponse: MlJobsResponse = await callAsInternalUser('ml.jobs', { - jobId: jobIds, + const { body } = await asInternalUser.ml.getJobs({ + job_id: jobIds.join(), }); - if (!jobConfigResponse || jobConfigResponse.jobs.length < 1) { + if (!body || body.jobs.length < 1) { throw Boom.notFound(`Unable to find anomaly detector jobs ${jobIds.join(', ')}`); } - const jobIdsWithStopOnWarnSet = jobConfigResponse.jobs + const jobIdsWithStopOnWarnSet = body.jobs .filter( (jobConfig) => jobConfig.analysis_config?.per_partition_categorization?.stop_on_warn === true @@ -543,7 +543,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie }, }, ]; - const results: SearchResponse = await callAsInternalUser('search', { + const { body: results } = await asInternalUser.search>({ index: ML_RESULTS_INDEX_PATTERN, size: 0, body: { @@ -594,7 +594,7 @@ export function resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClie getCategoryExamples, getLatestBucketTimestampByJob, getMaxAnomalyScore, - getPartitionFieldsValues: getPartitionFieldsValuesFactory(mlClusterClient), + getPartitionFieldsValues: getPartitionFieldsValuesFactory(client), getCategorizerStats, getCategoryStoppedPartitions, }; diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 76128341e6ddc..39672f5b188bc 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -9,18 +9,16 @@ import { CoreSetup, CoreStart, Plugin, - ILegacyScopedClusterClient, KibanaRequest, Logger, PluginInitializerContext, - ILegacyCustomClusterClient, CapabilitiesStart, + IClusterClient, } from 'kibana/server'; import { PluginsSetup, RouteInitialization } from './types'; import { PLUGIN_ID, PLUGIN_ICON } from '../common/constants/app'; import { MlCapabilities } from '../common/types/capabilities'; -import { elasticsearchJsPlugin } from './client/elasticsearch_ml'; import { initMlTelemetry } from './lib/telemetry'; import { initMlServerLog } from './client/log'; import { initSampleDataSets } from './lib/sample_data_sets'; @@ -50,17 +48,7 @@ import { setupCapabilitiesSwitcher } from './lib/capabilities'; import { registerKibanaSettings } from './lib/register_settings'; import { inferenceRoutes } from './routes/inference'; -declare module 'kibana/server' { - interface RequestHandlerContext { - [PLUGIN_ID]?: { - mlClient: ILegacyScopedClusterClient; - }; - } -} - -export interface MlPluginSetup extends SharedServices { - mlClient: ILegacyCustomClusterClient; -} +export type MlPluginSetup = SharedServices; export type MlPluginStart = void; export class MlServerPlugin implements Plugin { @@ -68,6 +56,7 @@ export class MlServerPlugin implements Plugin { - return { - mlClient: mlClient.asScoped(request), - }; - }); - const routeInit: RouteInitialization = { router: coreSetup.http.createRouter(), mlLicense: this.mlLicense, @@ -176,13 +154,19 @@ export class MlServerPlugin implements Plugin this.clusterClient + ), }; } public start(coreStart: CoreStart): MlPluginStart { this.capabilities = coreStart.capabilities; + this.clusterClient = coreStart.elasticsearch.client; } public stop() { diff --git a/x-pack/plugins/ml/server/routes/annotations.ts b/x-pack/plugins/ml/server/routes/annotations.ts index a6de80bb7e5e2..5c4b36164fbb0 100644 --- a/x-pack/plugins/ml/server/routes/annotations.ts +++ b/x-pack/plugins/ml/server/routes/annotations.ts @@ -58,9 +58,9 @@ export function annotationRoutes( tags: ['access:ml:canGetAnnotations'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { getAnnotations } = annotationServiceProvider(legacyClient); + const { getAnnotations } = annotationServiceProvider(client); const resp = await getAnnotations(request.body); return response.ok({ @@ -91,14 +91,14 @@ export function annotationRoutes( tags: ['access:ml:canCreateAnnotation'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable(legacyClient); + const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable(client); if (annotationsFeatureAvailable === false) { throw getAnnotationsFeatureUnavailableErrorMessage(); } - const { indexAnnotation } = annotationServiceProvider(legacyClient); + const { indexAnnotation } = annotationServiceProvider(client); const currentUser = securityPlugin !== undefined ? securityPlugin.authc.getCurrentUser(request) : {}; @@ -134,15 +134,15 @@ export function annotationRoutes( tags: ['access:ml:canDeleteAnnotation'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable(legacyClient); + const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable(client); if (annotationsFeatureAvailable === false) { throw getAnnotationsFeatureUnavailableErrorMessage(); } const annotationId = request.params.annotationId; - const { deleteAnnotation } = annotationServiceProvider(legacyClient); + const { deleteAnnotation } = annotationServiceProvider(client); const resp = await deleteAnnotation(annotationId); return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index 0027bec910134..251e465eafccc 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -5,6 +5,7 @@ */ import { schema } from '@kbn/config-schema'; +import { RequestParams } from '@elastic/elasticsearch'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { @@ -20,6 +21,7 @@ import { getModelSnapshotsSchema, updateModelSnapshotSchema, } from './schemas/anomaly_detectors_schema'; + /** * Routes for the anomaly detectors */ @@ -42,11 +44,11 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ response, client }) => { try { - const results = await legacyClient.callAsInternalUser('ml.jobs'); + const { body } = await client.asInternalUser.ml.getJobs(); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -73,12 +75,12 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { jobId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.jobs', { jobId }); + const { body } = await client.asInternalUser.ml.getJobs({ job_id: jobId }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -104,11 +106,11 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.jobStats'); + const { body } = await client.asInternalUser.ml.getJobStats(); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -135,12 +137,12 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { jobId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.jobStats', { jobId }); + const { body } = await client.asInternalUser.ml.getJobStats({ job_id: jobId }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -171,15 +173,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { jobId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.addJob', { - jobId, + const { body } = await client.asInternalUser.ml.putJob({ + job_id: jobId, body: request.body, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -208,15 +210,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canUpdateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { jobId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.updateJob', { - jobId, + const { body } = await client.asInternalUser.ml.updateJob({ + job_id: jobId, body: request.body, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -243,14 +245,12 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canOpenJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { jobId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.openJob', { - jobId, - }); + const { body } = await client.asInternalUser.ml.openJob({ job_id: jobId }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -277,18 +277,18 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCloseJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const options: { jobId: string; force?: boolean } = { - jobId: request.params.jobId, + const options: RequestParams.MlCloseJob = { + job_id: request.params.jobId, }; const force = request.query.force; if (force !== undefined) { options.force = force; } - const results = await legacyClient.callAsInternalUser('ml.closeJob', options); + const { body } = await client.asInternalUser.ml.closeJob(options); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -315,18 +315,19 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canDeleteJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const options: { jobId: string; force?: boolean } = { - jobId: request.params.jobId, + const options: RequestParams.MlDeleteJob = { + job_id: request.params.jobId, + wait_for_completion: false, }; const force = request.query.force; if (force !== undefined) { options.force = force; } - const results = await legacyClient.callAsInternalUser('ml.deleteJob', options); + const { body } = await client.asInternalUser.ml.deleteJob(options); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -351,13 +352,11 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.validateDetector', { - body: request.body, - }); + const { body } = await client.asInternalUser.ml.validateDetector({ body: request.body }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -386,16 +385,16 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canForecastJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const jobId = request.params.jobId; const duration = request.body.duration; - const results = await legacyClient.callAsInternalUser('ml.forecast', { - jobId, + const { body } = await client.asInternalUser.ml.forecast({ + job_id: jobId, duration, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -427,14 +426,14 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.records', { - jobId: request.params.jobId, + const { body } = await client.asInternalUser.ml.getRecords({ + job_id: request.params.jobId, body: request.body, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -466,15 +465,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.buckets', { - jobId: request.params.jobId, + const { body } = await client.asInternalUser.ml.getBuckets({ + job_id: request.params.jobId, timestamp: request.params.timestamp, body: request.body, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -506,17 +505,17 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.overallBuckets', { - jobId: request.params.jobId, + const { body } = await client.asInternalUser.ml.getOverallBuckets({ + job_id: request.params.jobId, top_n: request.body.topN, bucket_span: request.body.bucketSpan, start: request.body.start, end: request.body.end, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -543,14 +542,14 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.categories', { - jobId: request.params.jobId, - categoryId: request.params.categoryId, + const { body } = await client.asInternalUser.ml.getCategories({ + job_id: request.params.jobId, + category_id: request.params.categoryId, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -577,13 +576,13 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.modelSnapshots', { - jobId: request.params.jobId, + const { body } = await client.asInternalUser.ml.getModelSnapshots({ + job_id: request.params.jobId, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -610,14 +609,14 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.modelSnapshots', { - jobId: request.params.jobId, - snapshotId: request.params.snapshotId, + const { body } = await client.asInternalUser.ml.getModelSnapshots({ + job_id: request.params.jobId, + snapshot_id: request.params.snapshotId, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -646,15 +645,15 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.updateModelSnapshot', { - jobId: request.params.jobId, - snapshotId: request.params.snapshotId, + const { body } = await client.asInternalUser.ml.updateModelSnapshot({ + job_id: request.params.jobId, + snapshot_id: request.params.snapshotId, body: request.body, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -681,14 +680,14 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.deleteModelSnapshot', { - jobId: request.params.jobId, - snapshotId: request.params.snapshotId, + const { body } = await client.asInternalUser.ml.deleteModelSnapshot({ + job_id: request.params.jobId, + snapshot_id: request.params.snapshotId, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/ml/server/routes/calendars.ts b/x-pack/plugins/ml/server/routes/calendars.ts index 3beb6e437b2ee..2c95ce6fb59ec 100644 --- a/x-pack/plugins/ml/server/routes/calendars.ts +++ b/x-pack/plugins/ml/server/routes/calendars.ts @@ -4,43 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { calendarSchema, calendarIdSchema, calendarIdsSchema } from './schemas/calendars_schema'; import { CalendarManager, Calendar, FormCalendar } from '../models/calendar'; -function getAllCalendars(legacyClient: ILegacyScopedClusterClient) { - const cal = new CalendarManager(legacyClient); +function getAllCalendars(client: IScopedClusterClient) { + const cal = new CalendarManager(client); return cal.getAllCalendars(); } -function getCalendar(legacyClient: ILegacyScopedClusterClient, calendarId: string) { - const cal = new CalendarManager(legacyClient); +function getCalendar(client: IScopedClusterClient, calendarId: string) { + const cal = new CalendarManager(client); return cal.getCalendar(calendarId); } -function newCalendar(legacyClient: ILegacyScopedClusterClient, calendar: FormCalendar) { - const cal = new CalendarManager(legacyClient); +function newCalendar(client: IScopedClusterClient, calendar: FormCalendar) { + const cal = new CalendarManager(client); return cal.newCalendar(calendar); } -function updateCalendar( - legacyClient: ILegacyScopedClusterClient, - calendarId: string, - calendar: Calendar -) { - const cal = new CalendarManager(legacyClient); +function updateCalendar(client: IScopedClusterClient, calendarId: string, calendar: Calendar) { + const cal = new CalendarManager(client); return cal.updateCalendar(calendarId, calendar); } -function deleteCalendar(legacyClient: ILegacyScopedClusterClient, calendarId: string) { - const cal = new CalendarManager(legacyClient); +function deleteCalendar(client: IScopedClusterClient, calendarId: string) { + const cal = new CalendarManager(client); return cal.deleteCalendar(calendarId); } -function getCalendarsByIds(legacyClient: ILegacyScopedClusterClient, calendarIds: string) { - const cal = new CalendarManager(legacyClient); +function getCalendarsByIds(client: IScopedClusterClient, calendarIds: string) { + const cal = new CalendarManager(client); return cal.getCalendarsByIds(calendarIds); } @@ -60,9 +56,9 @@ export function calendars({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetCalendars'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const resp = await getAllCalendars(legacyClient); + const resp = await getAllCalendars(client); return response.ok({ body: resp, @@ -92,15 +88,15 @@ export function calendars({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetCalendars'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { let returnValue; try { const calendarIds = request.params.calendarIds.split(','); if (calendarIds.length === 1) { - returnValue = await getCalendar(legacyClient, calendarIds[0]); + returnValue = await getCalendar(client, calendarIds[0]); } else { - returnValue = await getCalendarsByIds(legacyClient, calendarIds); + returnValue = await getCalendarsByIds(client, calendarIds); } return response.ok({ @@ -131,10 +127,10 @@ export function calendars({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateCalendar'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const body = request.body; - const resp = await newCalendar(legacyClient, body); + const resp = await newCalendar(client, body); return response.ok({ body: resp, @@ -166,11 +162,11 @@ export function calendars({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateCalendar'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { calendarId } = request.params; const body = request.body; - const resp = await updateCalendar(legacyClient, calendarId, body); + const resp = await updateCalendar(client, calendarId, body); return response.ok({ body: resp, @@ -200,10 +196,10 @@ export function calendars({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canDeleteCalendar'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { calendarId } = request.params; - const resp = await deleteCalendar(legacyClient, calendarId); + const resp = await deleteCalendar(client, calendarId); return response.ok({ body: resp, diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 75d48056cf458..dea4803e8275e 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext, ILegacyScopedClusterClient } from 'kibana/server'; +import { RequestHandlerContext, IScopedClusterClient } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; import { RouteInitialization } from '../types'; @@ -36,13 +36,14 @@ function deleteDestIndexPatternById(context: RequestHandlerContext, indexPattern */ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitialization) { async function userCanDeleteIndex( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, destinationIndex: string ): Promise { if (!mlLicense.isSecurityEnabled()) { return true; } - const privilege = await legacyClient.callAsCurrentUser('ml.privilegeCheck', { + + const { body } = await client.asCurrentUser.security.hasPrivileges({ body: { index: [ { @@ -52,10 +53,8 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat ], }, }); - if (!privilege) { - return false; - } - return privilege.has_all_requested === true; + + return body?.has_all_requested === true; } /** @@ -76,11 +75,11 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canGetDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.getDataFrameAnalytics'); + const { body } = await client.asInternalUser.ml.getDataFrameAnalytics({ size: 1000 }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -107,14 +106,14 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canGetDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { analyticsId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.getDataFrameAnalytics', { - analyticsId, + const { body } = await client.asInternalUser.ml.getDataFrameAnalytics({ + id: analyticsId, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -137,11 +136,11 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canGetDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.getDataFrameAnalyticsStats'); + const { body } = await client.asInternalUser.ml.getDataFrameAnalyticsStats({ size: 1000 }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -168,14 +167,14 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canGetDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { analyticsId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.getDataFrameAnalyticsStats', { - analyticsId, + const { body } = await client.asInternalUser.ml.getDataFrameAnalyticsStats({ + id: analyticsId, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -205,16 +204,18 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canCreateDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { analyticsId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.createDataFrameAnalytics', { - body: request.body, - analyticsId, - ...getAuthorizationHeader(request), - }); + const { body } = await client.asInternalUser.ml.putDataFrameAnalytics( + { + id: analyticsId, + body: request.body, + }, + getAuthorizationHeader(request) + ); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -241,14 +242,16 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canGetDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.evaluateDataFrameAnalytics', { - body: request.body, - ...getAuthorizationHeader(request), - }); + const { body } = await client.asInternalUser.ml.evaluateDataFrame( + { + body: request.body, + }, + getAuthorizationHeader(request) + ); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -276,13 +279,13 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canCreateDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const results = await legacyClient.callAsInternalUser('ml.explainDataFrameAnalytics', { + const { body } = await client.asInternalUser.ml.explainDataFrameAnalytics({ body: request.body, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -310,7 +313,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canDeleteDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response, context }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response, context }) => { try { const { analyticsId } = request.params; const { deleteDestIndex, deleteDestIndexPattern } = request.query; @@ -324,11 +327,11 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat // Check if analyticsId is valid and get destination index if (deleteDestIndex || deleteDestIndexPattern) { try { - const dfa = await legacyClient.callAsInternalUser('ml.getDataFrameAnalytics', { - analyticsId, + const { body } = await client.asInternalUser.ml.getDataFrameAnalytics({ + id: analyticsId, }); - if (Array.isArray(dfa.data_frame_analytics) && dfa.data_frame_analytics.length > 0) { - destinationIndex = dfa.data_frame_analytics[0].dest.index; + if (Array.isArray(body.data_frame_analytics) && body.data_frame_analytics.length > 0) { + destinationIndex = body.data_frame_analytics[0].dest.index; } } catch (e) { return response.customError(wrapError(e)); @@ -337,11 +340,11 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat // If user checks box to delete the destinationIndex associated with the job if (destinationIndex && deleteDestIndex) { // Verify if user has privilege to delete the destination index - const userCanDeleteDestIndex = await userCanDeleteIndex(legacyClient, destinationIndex); + const userCanDeleteDestIndex = await userCanDeleteIndex(client, destinationIndex); // If user does have privilege to delete the index, then delete the index if (userCanDeleteDestIndex) { try { - await legacyClient.callAsCurrentUser('indices.delete', { + await client.asCurrentUser.indices.delete({ index: destinationIndex, }); destIndexDeleted.success = true; @@ -370,8 +373,8 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat // Delete the data frame analytics try { - await legacyClient.callAsInternalUser('ml.deleteDataFrameAnalytics', { - analyticsId, + await client.asInternalUser.ml.deleteDataFrameAnalytics({ + id: analyticsId, }); analyticsJobDeleted.success = true; } catch (deleteDFAError) { @@ -413,14 +416,14 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canStartStopDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { analyticsId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.startDataFrameAnalytics', { - analyticsId, + const { body } = await client.asInternalUser.ml.startDataFrameAnalytics({ + id: analyticsId, }); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -449,10 +452,10 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canStartStopDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const options: { analyticsId: string; force?: boolean | undefined } = { - analyticsId: request.params.analyticsId, + const options: { id: string; force?: boolean | undefined } = { + id: request.params.analyticsId, }; // @ts-expect-error TODO: update types if (request.url?.query?.force !== undefined) { @@ -460,9 +463,9 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat options.force = request.url.query.force; } - const results = await legacyClient.callAsInternalUser('ml.stopDataFrameAnalytics', options); + const { body } = await client.asInternalUser.ml.stopDataFrameAnalytics(options); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -490,16 +493,18 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canCreateDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { analyticsId } = request.params; - const results = await legacyClient.callAsInternalUser('ml.updateDataFrameAnalytics', { - body: request.body, - analyticsId, - ...getAuthorizationHeader(request), - }); + const { body } = await client.asInternalUser.ml.updateDataFrameAnalytics( + { + id: analyticsId, + body: request.body, + }, + getAuthorizationHeader(request) + ); return response.ok({ - body: results, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -526,10 +531,10 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canGetDataFrameAnalytics'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { analyticsId } = request.params; - const { getAnalyticsAuditMessages } = analyticsAuditMessagesProvider(legacyClient); + const { getAnalyticsAuditMessages } = analyticsAuditMessagesProvider(client); const results = await getAnalyticsAuditMessages(analyticsId); return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/data_visualizer.ts b/x-pack/plugins/ml/server/routes/data_visualizer.ts index 6355285127f06..a697fe017f192 100644 --- a/x-pack/plugins/ml/server/routes/data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/data_visualizer.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { DataVisualizer } from '../models/data_visualizer'; import { Field, HistogramField } from '../models/data_visualizer/data_visualizer'; @@ -17,7 +17,7 @@ import { import { RouteInitialization } from '../types'; function getOverallStats( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, indexPatternTitle: string, query: object, aggregatableFields: string[], @@ -27,7 +27,7 @@ function getOverallStats( earliestMs: number, latestMs: number ) { - const dv = new DataVisualizer(legacyClient); + const dv = new DataVisualizer(client); return dv.getOverallStats( indexPatternTitle, query, @@ -41,7 +41,7 @@ function getOverallStats( } function getStatsForFields( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, indexPatternTitle: string, query: any, fields: Field[], @@ -52,7 +52,7 @@ function getStatsForFields( interval: number, maxExamples: number ) { - const dv = new DataVisualizer(legacyClient); + const dv = new DataVisualizer(client); return dv.getStatsForFields( indexPatternTitle, query, @@ -67,13 +67,13 @@ function getStatsForFields( } function getHistogramsForFields( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, indexPatternTitle: string, query: any, fields: HistogramField[], samplerShardSize: number ) { - const dv = new DataVisualizer(legacyClient); + const dv = new DataVisualizer(client); return dv.getHistogramsForFields(indexPatternTitle, query, fields, samplerShardSize); } @@ -104,7 +104,7 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canAccessML'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { const { params: { indexPatternTitle }, @@ -112,7 +112,7 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) } = request; const results = await getHistogramsForFields( - legacyClient, + client, indexPatternTitle, query, fields, @@ -151,7 +151,7 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canAccessML'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { const { params: { indexPatternTitle }, @@ -168,7 +168,7 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) } = request; const results = await getStatsForFields( - legacyClient, + client, indexPatternTitle, query, fields, @@ -216,7 +216,7 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canAccessML'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { const { params: { indexPatternTitle }, @@ -232,7 +232,7 @@ export function dataVisualizerRoutes({ router, mlLicense }: RouteInitialization) } = request; const results = await getOverallStats( - legacyClient, + client, indexPatternTitle, query, aggregatableFields, diff --git a/x-pack/plugins/ml/server/routes/datafeeds.ts b/x-pack/plugins/ml/server/routes/datafeeds.ts index 47a9afc2244d9..df2aa9e79d71b 100644 --- a/x-pack/plugins/ml/server/routes/datafeeds.ts +++ b/x-pack/plugins/ml/server/routes/datafeeds.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RequestParams } from '@elastic/elasticsearch'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { @@ -33,12 +34,12 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetDatafeeds'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const resp = await legacyClient.callAsInternalUser('ml.datafeeds'); + const { body } = await client.asInternalUser.ml.getDatafeeds(); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -65,13 +66,13 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetDatafeeds'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const datafeedId = request.params.datafeedId; - const resp = await legacyClient.callAsInternalUser('ml.datafeeds', { datafeedId }); + const { body } = await client.asInternalUser.ml.getDatafeeds({ datafeed_id: datafeedId }); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -94,12 +95,12 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetDatafeeds'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await legacyClient.callAsInternalUser('ml.datafeedStats'); + const { body } = await client.asInternalUser.ml.getDatafeedStats(); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -126,15 +127,15 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetDatafeeds'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const datafeedId = request.params.datafeedId; - const resp = await legacyClient.callAsInternalUser('ml.datafeedStats', { - datafeedId, + const { body } = await client.asInternalUser.ml.getDatafeedStats({ + datafeed_id: datafeedId, }); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -163,17 +164,19 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const datafeedId = request.params.datafeedId; - const resp = await legacyClient.callAsInternalUser('ml.addDatafeed', { - datafeedId, - body: request.body, - ...getAuthorizationHeader(request), - }); + const { body } = await client.asInternalUser.ml.putDatafeed( + { + datafeed_id: datafeedId, + body: request.body, + }, + getAuthorizationHeader(request) + ); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -202,17 +205,19 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canUpdateDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const datafeedId = request.params.datafeedId; - const resp = await legacyClient.callAsInternalUser('ml.updateDatafeed', { - datafeedId, - body: request.body, - ...getAuthorizationHeader(request), - }); + const { body } = await client.asInternalUser.ml.updateDatafeed( + { + datafeed_id: datafeedId, + body: request.body, + }, + getAuthorizationHeader(request) + ); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -241,20 +246,20 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canDeleteDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const options: { datafeedId: string; force?: boolean } = { - datafeedId: request.params.jobId, + const options: RequestParams.MlDeleteDatafeed = { + datafeed_id: request.params.jobId, }; const force = request.query.force; if (force !== undefined) { options.force = force; } - const resp = await legacyClient.callAsInternalUser('ml.deleteDatafeed', options); + const { body } = await client.asInternalUser.ml.deleteDatafeed(options); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -283,19 +288,19 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canStartStopDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const datafeedId = request.params.datafeedId; const { start, end } = request.body; - const resp = await legacyClient.callAsInternalUser('ml.startDatafeed', { - datafeedId, + const { body } = await client.asInternalUser.ml.startDatafeed({ + datafeed_id: datafeedId, start, end, }); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -322,16 +327,16 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canStartStopDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const datafeedId = request.params.datafeedId; - const resp = await legacyClient.callAsInternalUser('ml.stopDatafeed', { - datafeedId, + const { body } = await client.asInternalUser.ml.stopDatafeed({ + datafeed_id: datafeedId, }); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); @@ -358,16 +363,18 @@ export function dataFeedRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canPreviewDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const datafeedId = request.params.datafeedId; - const resp = await legacyClient.callAsInternalUser('ml.datafeedPreview', { - datafeedId, - ...getAuthorizationHeader(request), - }); + const { body } = await client.asInternalUser.ml.previewDatafeed( + { + datafeed_id: datafeedId, + }, + getAuthorizationHeader(request) + ); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/ml/server/routes/fields_service.ts b/x-pack/plugins/ml/server/routes/fields_service.ts index 0595b31d5bbbc..e1bd8a5736d82 100644 --- a/x-pack/plugins/ml/server/routes/fields_service.ts +++ b/x-pack/plugins/ml/server/routes/fields_service.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { @@ -13,14 +13,14 @@ import { } from './schemas/fields_service_schema'; import { fieldsServiceProvider } from '../models/fields_service'; -function getCardinalityOfFields(legacyClient: ILegacyScopedClusterClient, payload: any) { - const fs = fieldsServiceProvider(legacyClient); +function getCardinalityOfFields(client: IScopedClusterClient, payload: any) { + const fs = fieldsServiceProvider(client); const { index, fieldNames, query, timeFieldName, earliestMs, latestMs } = payload; return fs.getCardinalityOfFields(index, fieldNames, query, timeFieldName, earliestMs, latestMs); } -function getTimeFieldRange(legacyClient: ILegacyScopedClusterClient, payload: any) { - const fs = fieldsServiceProvider(legacyClient); +function getTimeFieldRange(client: IScopedClusterClient, payload: any) { + const fs = fieldsServiceProvider(client); const { index, timeFieldName, query } = payload; return fs.getTimeFieldRange(index, timeFieldName, query); } @@ -50,9 +50,9 @@ export function fieldsService({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canAccessML'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getCardinalityOfFields(legacyClient, request.body); + const resp = await getCardinalityOfFields(client, request.body); return response.ok({ body: resp, @@ -85,9 +85,9 @@ export function fieldsService({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canAccessML'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getTimeFieldRange(legacyClient, request.body); + const resp = await getTimeFieldRange(client, request.body); return response.ok({ body: resp, diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index 88949fecbc7df..4c1ee87e96fc5 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { MAX_FILE_SIZE_BYTES } from '../../common/constants/file_datavisualizer'; import { InputOverrides, @@ -28,17 +28,13 @@ import { importFileQuerySchema, } from './schemas/file_data_visualizer_schema'; -function analyzeFiles( - legacyClient: ILegacyScopedClusterClient, - data: InputData, - overrides: InputOverrides -) { - const { analyzeFile } = fileDataVisualizerProvider(legacyClient); +function analyzeFiles(client: IScopedClusterClient, data: InputData, overrides: InputOverrides) { + const { analyzeFile } = fileDataVisualizerProvider(client); return analyzeFile(data, overrides); } function importData( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, id: string, index: string, settings: Settings, @@ -46,7 +42,7 @@ function importData( ingestPipeline: IngestPipelineWrapper, data: InputData ) { - const { importData: importDataFunc } = importDataProvider(legacyClient); + const { importData: importDataFunc } = importDataProvider(client); return importDataFunc(id, index, settings, mappings, ingestPipeline, data); } @@ -78,9 +74,9 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canFindFileStructure'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { - const result = await analyzeFiles(legacyClient, request.body, request.query); + const result = await analyzeFiles(client, request.body, request.query); return response.ok({ body: result }); } catch (e) { return response.customError(wrapError(e)); @@ -113,7 +109,7 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat tags: ['access:ml:canFindFileStructure'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { const { id } = request.query; const { index, data, settings, mappings, ingestPipeline } = request.body; @@ -126,7 +122,7 @@ export function fileDataVisualizerRoutes({ router, mlLicense }: RouteInitializat } const result = await importData( - legacyClient, + client, id, index, settings, diff --git a/x-pack/plugins/ml/server/routes/filters.ts b/x-pack/plugins/ml/server/routes/filters.ts index bb4f8a2bebaa9..efb4acfa432f9 100644 --- a/x-pack/plugins/ml/server/routes/filters.ts +++ b/x-pack/plugins/ml/server/routes/filters.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; import { createFilterSchema, filterIdSchema, updateFilterSchema } from './schemas/filters_schema'; @@ -12,37 +12,33 @@ import { FilterManager, FormFilter } from '../models/filter'; // TODO - add function for returning a list of just the filter IDs. // TODO - add function for returning a list of filter IDs plus item count. -function getAllFilters(legacyClient: ILegacyScopedClusterClient) { - const mgr = new FilterManager(legacyClient); +function getAllFilters(client: IScopedClusterClient) { + const mgr = new FilterManager(client); return mgr.getAllFilters(); } -function getAllFilterStats(legacyClient: ILegacyScopedClusterClient) { - const mgr = new FilterManager(legacyClient); +function getAllFilterStats(client: IScopedClusterClient) { + const mgr = new FilterManager(client); return mgr.getAllFilterStats(); } -function getFilter(legacyClient: ILegacyScopedClusterClient, filterId: string) { - const mgr = new FilterManager(legacyClient); +function getFilter(client: IScopedClusterClient, filterId: string) { + const mgr = new FilterManager(client); return mgr.getFilter(filterId); } -function newFilter(legacyClient: ILegacyScopedClusterClient, filter: FormFilter) { - const mgr = new FilterManager(legacyClient); +function newFilter(client: IScopedClusterClient, filter: FormFilter) { + const mgr = new FilterManager(client); return mgr.newFilter(filter); } -function updateFilter( - legacyClient: ILegacyScopedClusterClient, - filterId: string, - filter: FormFilter -) { - const mgr = new FilterManager(legacyClient); +function updateFilter(client: IScopedClusterClient, filterId: string, filter: FormFilter) { + const mgr = new FilterManager(client); return mgr.updateFilter(filterId, filter); } -function deleteFilter(legacyClient: ILegacyScopedClusterClient, filterId: string) { - const mgr = new FilterManager(legacyClient); +function deleteFilter(client: IScopedClusterClient, filterId: string) { + const mgr = new FilterManager(client); return mgr.deleteFilter(filterId); } @@ -65,9 +61,9 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetFilters'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const resp = await getAllFilters(legacyClient); + const resp = await getAllFilters(client); return response.ok({ body: resp, @@ -100,9 +96,9 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetFilters'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getFilter(legacyClient, request.params.filterId); + const resp = await getFilter(client, request.params.filterId); return response.ok({ body: resp, }); @@ -134,10 +130,10 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateFilter'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const body = request.body; - const resp = await newFilter(legacyClient, body); + const resp = await newFilter(client, body); return response.ok({ body: resp, @@ -172,11 +168,11 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateFilter'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { filterId } = request.params; const body = request.body; - const resp = await updateFilter(legacyClient, filterId, body); + const resp = await updateFilter(client, filterId, body); return response.ok({ body: resp, @@ -206,10 +202,10 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canDeleteFilter'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { filterId } = request.params; - const resp = await deleteFilter(legacyClient, filterId); + const resp = await deleteFilter(client, filterId); return response.ok({ body: resp, @@ -239,9 +235,9 @@ export function filtersRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetFilters'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const resp = await getAllFilterStats(legacyClient); + const resp = await getAllFilterStats(client); return response.ok({ body: resp, diff --git a/x-pack/plugins/ml/server/routes/indices.ts b/x-pack/plugins/ml/server/routes/indices.ts index 6a759cb97f308..ee817c492dbd4 100644 --- a/x-pack/plugins/ml/server/routes/indices.ts +++ b/x-pack/plugins/ml/server/routes/indices.ts @@ -31,7 +31,7 @@ export function indicesRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canAccessML'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { body: { index, fields: requestFields }, @@ -40,8 +40,8 @@ export function indicesRoutes({ router, mlLicense }: RouteInitialization) { requestFields !== undefined && Array.isArray(requestFields) ? requestFields.join(',') : '*'; - const result = await legacyClient.callAsCurrentUser('fieldCaps', { index, fields }); - return response.ok({ body: result }); + const { body } = await client.asInternalUser.fieldCaps({ index, fields }); + return response.ok({ body }); } catch (e) { return response.customError(wrapError(e)); } diff --git a/x-pack/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts index 2313decfabd5b..0c90081f8e755 100644 --- a/x-pack/plugins/ml/server/routes/job_audit_messages.ts +++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts @@ -37,9 +37,9 @@ export function jobAuditMessagesRoutes({ router, mlLicense }: RouteInitializatio tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { getJobAuditMessages } = jobAuditMessagesProvider(legacyClient); + const { getJobAuditMessages } = jobAuditMessagesProvider(client); const { jobId } = request.params; const { from } = request.query; const resp = await getJobAuditMessages(jobId, from); @@ -72,9 +72,9 @@ export function jobAuditMessagesRoutes({ router, mlLicense }: RouteInitializatio tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { getJobAuditMessages } = jobAuditMessagesProvider(legacyClient); + const { getJobAuditMessages } = jobAuditMessagesProvider(client); const { from } = request.query; const resp = await getJobAuditMessages(undefined, from); diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 3d560fc857e95..3c7f35b871b10 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -48,9 +48,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canStartStopDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { forceStartDatafeeds } = jobServiceProvider(legacyClient); + const { forceStartDatafeeds } = jobServiceProvider(client); const { datafeedIds, start, end } = request.body; const resp = await forceStartDatafeeds(datafeedIds, start, end); @@ -82,9 +82,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canStartStopDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { stopDatafeeds } = jobServiceProvider(legacyClient); + const { stopDatafeeds } = jobServiceProvider(client); const { datafeedIds } = request.body; const resp = await stopDatafeeds(datafeedIds); @@ -116,9 +116,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canDeleteJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { deleteJobs } = jobServiceProvider(legacyClient); + const { deleteJobs } = jobServiceProvider(client); const { jobIds } = request.body; const resp = await deleteJobs(jobIds); @@ -150,9 +150,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCloseJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { closeJobs } = jobServiceProvider(legacyClient); + const { closeJobs } = jobServiceProvider(client); const { jobIds } = request.body; const resp = await closeJobs(jobIds); @@ -184,9 +184,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCloseJob', 'access:ml:canStartStopDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { forceStopAndCloseJob } = jobServiceProvider(legacyClient); + const { forceStopAndCloseJob } = jobServiceProvider(client); const { jobId } = request.body; const resp = await forceStopAndCloseJob(jobId); @@ -223,9 +223,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { jobsSummary } = jobServiceProvider(legacyClient); + const { jobsSummary } = jobServiceProvider(client); const { jobIds } = request.body; const resp = await jobsSummary(jobIds); @@ -257,9 +257,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const { jobsWithTimerange } = jobServiceProvider(legacyClient); + const { jobsWithTimerange } = jobServiceProvider(client); const resp = await jobsWithTimerange(); return response.ok({ @@ -290,9 +290,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { createFullJobsList } = jobServiceProvider(legacyClient); + const { createFullJobsList } = jobServiceProvider(client); const { jobIds } = request.body; const resp = await createFullJobsList(jobIds); @@ -320,9 +320,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const { getAllGroups } = jobServiceProvider(legacyClient); + const { getAllGroups } = jobServiceProvider(client); const resp = await getAllGroups(); return response.ok({ @@ -353,9 +353,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canUpdateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { updateGroups } = jobServiceProvider(legacyClient); + const { updateGroups } = jobServiceProvider(client); const { jobs } = request.body; const resp = await updateGroups(jobs); @@ -383,9 +383,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const { deletingJobTasks } = jobServiceProvider(legacyClient); + const { deletingJobTasks } = jobServiceProvider(client); const resp = await deletingJobTasks(); return response.ok({ @@ -416,9 +416,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { jobsExist } = jobServiceProvider(legacyClient); + const { jobsExist } = jobServiceProvider(client); const { jobIds } = request.body; const resp = await jobsExist(jobIds); @@ -449,12 +449,12 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response, context }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response, context }) => { try { const { indexPattern } = request.params; const isRollup = request.query.rollup === 'true'; const savedObjectsClient = context.core.savedObjects.client; - const { newJobCaps } = jobServiceProvider(legacyClient); + const { newJobCaps } = jobServiceProvider(client); const resp = await newJobCaps(indexPattern, isRollup, savedObjectsClient); return response.ok({ @@ -485,7 +485,7 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { indexPatternTitle, @@ -499,7 +499,7 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { splitFieldValue, } = request.body; - const { newJobLineChart } = jobServiceProvider(legacyClient); + const { newJobLineChart } = jobServiceProvider(client); const resp = await newJobLineChart( indexPatternTitle, timeField, @@ -540,7 +540,7 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { const { indexPatternTitle, @@ -553,7 +553,7 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { splitFieldName, } = request.body; - const { newJobPopulationChart } = jobServiceProvider(legacyClient); + const { newJobPopulationChart } = jobServiceProvider(client); const resp = await newJobPopulationChart( indexPatternTitle, timeField, @@ -589,9 +589,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const { getAllJobAndGroupIds } = jobServiceProvider(legacyClient); + const { getAllJobAndGroupIds } = jobServiceProvider(client); const resp = await getAllJobAndGroupIds(); return response.ok({ @@ -622,9 +622,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { getLookBackProgress } = jobServiceProvider(legacyClient); + const { getLookBackProgress } = jobServiceProvider(client); const { jobId, start, end } = request.body; const resp = await getLookBackProgress(jobId, start, end); @@ -656,9 +656,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { validateCategoryExamples } = categorizationExamplesProvider(legacyClient); + const { validateCategoryExamples } = categorizationExamplesProvider(client); const { indexPatternTitle, timeField, @@ -709,9 +709,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { topCategories } = jobServiceProvider(legacyClient); + const { topCategories } = jobServiceProvider(client); const { jobId, count } = request.body; const resp = await topCategories(jobId, count); @@ -743,9 +743,9 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob', 'access:ml:canStartStopDatafeed'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const { revertModelSnapshot } = jobServiceProvider(legacyClient); + const { revertModelSnapshot } = jobServiceProvider(client); const { jobId, snapshotId, diff --git a/x-pack/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts index 6da052663a002..b52043595327b 100644 --- a/x-pack/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { AnalysisConfig } from '../../common/types/anomaly_detection_jobs'; import { wrapError } from '../client/error_wrapper'; @@ -27,12 +27,12 @@ type CalculateModelMemoryLimitPayload = TypeOf; */ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, version: string) { function calculateModelMemoryLimit( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, payload: CalculateModelMemoryLimitPayload ) { const { analysisConfig, indexPattern, query, timeFieldName, earliestMs, latestMs } = payload; - return calculateModelMemoryLimitProvider(legacyClient)( + return calculateModelMemoryLimitProvider(client)( analysisConfig as AnalysisConfig, indexPattern, query, @@ -61,10 +61,10 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { let errorResp; - const resp = await estimateBucketSpanFactory(legacyClient)(request.body) + const resp = await estimateBucketSpanFactory(client)(request.body) // this catch gets triggered when the estimation code runs without error // but isn't able to come up with a bucket span estimation. // this doesn't return a HTTP error but an object with an error message @@ -109,9 +109,9 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await calculateModelMemoryLimit(legacyClient, request.body); + const resp = await calculateModelMemoryLimit(client, request.body); return response.ok({ body: resp, @@ -141,9 +141,9 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await validateCardinality(legacyClient, request.body); + const resp = await validateCardinality(client, request.body); return response.ok({ body: resp, @@ -173,11 +173,11 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { // version corresponds to the version used in documentation links. const resp = await validateJob( - legacyClient, + client, request.body, version, mlLicense.isSecurityEnabled() === false diff --git a/x-pack/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts index 23e37d2213029..72a4c5e428c2b 100644 --- a/x-pack/plugins/ml/server/routes/modules.ts +++ b/x-pack/plugins/ml/server/routes/modules.ts @@ -6,11 +6,7 @@ import { TypeOf } from '@kbn/config-schema'; -import { - ILegacyScopedClusterClient, - KibanaRequest, - SavedObjectsClientContract, -} from 'kibana/server'; +import { IScopedClusterClient, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; import { DatafeedOverride, JobOverride } from '../../common/types/modules'; import { wrapError } from '../client/error_wrapper'; import { DataRecognizer } from '../models/data_recognizer'; @@ -23,22 +19,22 @@ import { import { RouteInitialization } from '../types'; function recognize( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract, request: KibanaRequest, indexPatternTitle: string ) { - const dr = new DataRecognizer(legacyClient, savedObjectsClient, request); + const dr = new DataRecognizer(client, savedObjectsClient, request); return dr.findMatches(indexPatternTitle); } function getModule( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract, request: KibanaRequest, moduleId: string ) { - const dr = new DataRecognizer(legacyClient, savedObjectsClient, request); + const dr = new DataRecognizer(client, savedObjectsClient, request); if (moduleId === undefined) { return dr.listModules(); } else { @@ -47,7 +43,7 @@ function getModule( } function setup( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract, request: KibanaRequest, moduleId: string, @@ -63,7 +59,7 @@ function setup( datafeedOverrides?: DatafeedOverride | DatafeedOverride[], estimateModelMemory?: boolean ) { - const dr = new DataRecognizer(legacyClient, savedObjectsClient, request); + const dr = new DataRecognizer(client, savedObjectsClient, request); return dr.setup( moduleId, prefix, @@ -81,12 +77,12 @@ function setup( } function dataRecognizerJobsExist( - legacyClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract, request: KibanaRequest, moduleId: string ) { - const dr = new DataRecognizer(legacyClient, savedObjectsClient, request); + const dr = new DataRecognizer(client, savedObjectsClient, request); return dr.dataRecognizerJobsExist(moduleId); } @@ -131,11 +127,11 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response, context }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response, context }) => { try { const { indexPatternTitle } = request.params; const results = await recognize( - legacyClient, + client, context.core.savedObjects.client, request, indexPatternTitle @@ -266,7 +262,7 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response, context }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response, context }) => { try { let { moduleId } = request.params; if (moduleId === '') { @@ -275,7 +271,7 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { moduleId = undefined; } const results = await getModule( - legacyClient, + client, context.core.savedObjects.client, request, moduleId @@ -439,7 +435,7 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canCreateJob'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response, context }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response, context }) => { try { const { moduleId } = request.params; @@ -458,7 +454,7 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { } = request.body as TypeOf; const result = await setup( - legacyClient, + client, context.core.savedObjects.client, request, moduleId, @@ -544,11 +540,11 @@ export function dataRecognizer({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response, context }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response, context }) => { try { const { moduleId } = request.params; const result = await dataRecognizerJobsExist( - legacyClient, + client, context.core.savedObjects.client, request, moduleId diff --git a/x-pack/plugins/ml/server/routes/notification_settings.ts b/x-pack/plugins/ml/server/routes/notification_settings.ts index 09c145d6257a8..5bb51bf9e596c 100644 --- a/x-pack/plugins/ml/server/routes/notification_settings.ts +++ b/x-pack/plugins/ml/server/routes/notification_settings.ts @@ -26,16 +26,15 @@ export function notificationRoutes({ router, mlLicense }: RouteInitialization) { tags: ['access:ml:canAccessML'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, response }) => { try { - const params = { - includeDefaults: true, - filterPath: '**.xpack.notification', - }; - const resp = await legacyClient.callAsCurrentUser('cluster.getSettings', params); + const { body } = await client.asCurrentUser.cluster.getSettings({ + include_defaults: true, + filter_path: '**.xpack.notification', + }); return response.ok({ - body: resp, + body, }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts index 2af37c17f714a..4e34320d51333 100644 --- a/x-pack/plugins/ml/server/routes/results_service.ts +++ b/x-pack/plugins/ml/server/routes/results_service.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { RouteInitialization } from '../types'; @@ -23,8 +23,8 @@ import { getCategorizerStoppedPartitionsSchema, } from './schemas/results_service_schema'; -function getAnomaliesTableData(legacyClient: ILegacyScopedClusterClient, payload: any) { - const rs = resultsServiceProvider(legacyClient); +function getAnomaliesTableData(client: IScopedClusterClient, payload: any) { + const rs = resultsServiceProvider(client); const { jobIds, criteriaFields, @@ -53,39 +53,39 @@ function getAnomaliesTableData(legacyClient: ILegacyScopedClusterClient, payload ); } -function getCategoryDefinition(legacyClient: ILegacyScopedClusterClient, payload: any) { - const rs = resultsServiceProvider(legacyClient); +function getCategoryDefinition(client: IScopedClusterClient, payload: any) { + const rs = resultsServiceProvider(client); return rs.getCategoryDefinition(payload.jobId, payload.categoryId); } -function getCategoryExamples(legacyClient: ILegacyScopedClusterClient, payload: any) { - const rs = resultsServiceProvider(legacyClient); +function getCategoryExamples(client: IScopedClusterClient, payload: any) { + const rs = resultsServiceProvider(client); const { jobId, categoryIds, maxExamples } = payload; return rs.getCategoryExamples(jobId, categoryIds, maxExamples); } -function getMaxAnomalyScore(legacyClient: ILegacyScopedClusterClient, payload: any) { - const rs = resultsServiceProvider(legacyClient); +function getMaxAnomalyScore(client: IScopedClusterClient, payload: any) { + const rs = resultsServiceProvider(client); const { jobIds, earliestMs, latestMs } = payload; return rs.getMaxAnomalyScore(jobIds, earliestMs, latestMs); } -function getPartitionFieldsValues(legacyClient: ILegacyScopedClusterClient, payload: any) { - const rs = resultsServiceProvider(legacyClient); +function getPartitionFieldsValues(client: IScopedClusterClient, payload: any) { + const rs = resultsServiceProvider(client); const { jobId, searchTerm, criteriaFields, earliestMs, latestMs } = payload; return rs.getPartitionFieldsValues(jobId, searchTerm, criteriaFields, earliestMs, latestMs); } -function getCategorizerStats(legacyClient: ILegacyScopedClusterClient, params: any, query: any) { +function getCategorizerStats(client: IScopedClusterClient, params: any, query: any) { const { jobId } = params; const { partitionByValue } = query; - const rs = resultsServiceProvider(legacyClient); + const rs = resultsServiceProvider(client); return rs.getCategorizerStats(jobId, partitionByValue); } -function getCategoryStoppedPartitions(legacyClient: ILegacyScopedClusterClient, payload: any) { +function getCategoryStoppedPartitions(client: IScopedClusterClient, payload: any) { const { jobIds, fieldToBucket } = payload; - const rs = resultsServiceProvider(legacyClient); + const rs = resultsServiceProvider(client); return rs.getCategoryStoppedPartitions(jobIds, fieldToBucket); } @@ -112,9 +112,9 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getAnomaliesTableData(legacyClient, request.body); + const resp = await getAnomaliesTableData(client, request.body); return response.ok({ body: resp, @@ -144,9 +144,9 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getCategoryDefinition(legacyClient, request.body); + const resp = await getCategoryDefinition(client, request.body); return response.ok({ body: resp, @@ -176,9 +176,9 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getMaxAnomalyScore(legacyClient, request.body); + const resp = await getMaxAnomalyScore(client, request.body); return response.ok({ body: resp, @@ -208,9 +208,9 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getCategoryExamples(legacyClient, request.body); + const resp = await getCategoryExamples(client, request.body); return response.ok({ body: resp, @@ -240,9 +240,9 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getPartitionFieldsValues(legacyClient, request.body); + const resp = await getPartitionFieldsValues(client, request.body); return response.ok({ body: resp, @@ -269,14 +269,14 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { - const body = { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { + const { body } = await client.asInternalUser.search({ ...request.body, index: ML_RESULTS_INDEX_PATTERN, - }; + }); try { return response.ok({ - body: await legacyClient.callAsInternalUser('search', body), + body, }); } catch (error) { return response.customError(wrapError(error)); @@ -304,9 +304,9 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getCategorizerStats(legacyClient, request.params, request.query); + const resp = await getCategorizerStats(client, request.params, request.query); return response.ok({ body: resp, }); @@ -334,9 +334,9 @@ export function resultsServiceRoutes({ router, mlLicense }: RouteInitialization) tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { - const resp = await getCategoryStoppedPartitions(legacyClient, request.body); + const resp = await getCategoryStoppedPartitions(client, request.body); return response.ok({ body: resp, }); diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts index 273b86163245f..3a66f60943bb3 100644 --- a/x-pack/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { Request } from 'hapi'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { mlLog } from '../client/log'; import { capabilitiesProvider } from '../lib/capabilities'; @@ -21,17 +21,16 @@ export function systemRoutes( { router, mlLicense }: RouteInitialization, { spaces, cloud, resolveMlCapabilities }: SystemRouteDeps ) { - async function getNodeCount(legacyClient: ILegacyScopedClusterClient) { - const filterPath = 'nodes.*.attributes'; - const resp = await legacyClient.callAsInternalUser('nodes.info', { - filterPath, + async function getNodeCount(client: IScopedClusterClient) { + const { body } = await client.asInternalUser.nodes.info({ + filter_path: 'nodes.*.attributes', }); let count = 0; - if (typeof resp.nodes === 'object') { - Object.keys(resp.nodes).forEach((k) => { - if (resp.nodes[k].attributes !== undefined) { - const maxOpenJobs = resp.nodes[k].attributes['ml.max_open_jobs']; + if (typeof body.nodes === 'object') { + Object.keys(body.nodes).forEach((k) => { + if (body.nodes[k].attributes !== undefined) { + const maxOpenJobs = body.nodes[k].attributes['ml.max_open_jobs']; if (maxOpenJobs !== null && maxOpenJobs > 0) { count++; } @@ -58,15 +57,15 @@ export function systemRoutes( tags: ['access:ml:canAccessML'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { - const { callAsCurrentUser, callAsInternalUser } = legacyClient; + const { asCurrentUser, asInternalUser } = client; let upgradeInProgress = false; try { - const info = await callAsInternalUser('ml.info'); + const { body } = await asInternalUser.ml.info(); // if ml indices are currently being migrated, upgrade_mode will be set to true // pass this back with the privileges to allow for the disabling of UI controls. - upgradeInProgress = info.upgrade_mode === true; + upgradeInProgress = body.upgrade_mode === true; } catch (error) { // if the ml.info check fails, it could be due to the user having insufficient privileges // most likely they do not have the ml_user role and therefore will be blocked from using @@ -90,11 +89,12 @@ export function systemRoutes( }, }); } else { - const body = request.body; - const resp = await callAsCurrentUser('ml.privilegeCheck', { body }); - resp.upgradeInProgress = upgradeInProgress; + const { body } = await asCurrentUser.security.hasPrivileges({ body: request.body }); return response.ok({ - body: resp, + body: { + ...body, + upgradeInProgress, + }, }); } } catch (error) { @@ -115,7 +115,7 @@ export function systemRoutes( path: '/api/ml/ml_capabilities', validate: false, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { // if spaces is disabled force isMlEnabledInSpace to be true const { isMlEnabledInSpace } = @@ -129,7 +129,7 @@ export function systemRoutes( } const { getCapabilities } = capabilitiesProvider( - legacyClient, + client, mlCapabilities, mlLicense, isMlEnabledInSpace @@ -159,10 +159,10 @@ export function systemRoutes( }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { return response.ok({ - body: await getNodeCount(legacyClient), + body: await getNodeCount(client), }); } catch (e) { return response.customError(wrapError(e)); @@ -185,12 +185,12 @@ export function systemRoutes( tags: ['access:ml:canAccessML'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { - const info = await legacyClient.callAsInternalUser('ml.info'); + const { body } = await client.asInternalUser.ml.info(); const cloudId = cloud && cloud.cloudId; return response.ok({ - body: { ...info, cloudId }, + body: { ...body, cloudId }, }); } catch (error) { return response.customError(wrapError(error)); @@ -216,10 +216,11 @@ export function systemRoutes( tags: ['access:ml:canGetJobs'], }, }, - mlLicense.fullLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.fullLicenseAPIGuard(async ({ client, request, response }) => { try { + const { body } = await client.asCurrentUser.search(request.body); return response.ok({ - body: await legacyClient.callAsCurrentUser('search', request.body), + body, }); } catch (error) { return response.customError(wrapError(error)); @@ -243,22 +244,21 @@ export function systemRoutes( tags: ['access:ml:canAccessML'], }, }, - mlLicense.basicLicenseAPIGuard(async ({ legacyClient, request, response }) => { + mlLicense.basicLicenseAPIGuard(async ({ client, request, response }) => { try { const { index } = request.body; const options = { index: [index], fields: ['*'], - ignoreUnavailable: true, - allowNoIndices: true, - ignore: 404, + ignore_unavailable: true, + allow_no_indices: true, }; - const fieldsResult = await legacyClient.callAsCurrentUser('fieldCaps', options); + const { body } = await client.asCurrentUser.fieldCaps(options); const result = { exists: false }; - if (Array.isArray(fieldsResult.indices) && fieldsResult.indices.length !== 0) { + if (Array.isArray(body.indices) && body.indices.length !== 0) { result.exists = true; } diff --git a/x-pack/plugins/ml/server/shared_services/errors.ts b/x-pack/plugins/ml/server/shared_services/errors.ts new file mode 100644 index 0000000000000..f15a85a490a46 --- /dev/null +++ b/x-pack/plugins/ml/server/shared_services/errors.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export class MLClusterClientUninitialized extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/x-pack/plugins/ml/server/shared_services/license_checks/errors.ts b/x-pack/plugins/ml/server/shared_services/license_checks/errors.ts new file mode 100644 index 0000000000000..18e7dab43fda7 --- /dev/null +++ b/x-pack/plugins/ml/server/shared_services/license_checks/errors.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable max-classes-per-file */ + +export class InsufficientFullLicenseError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class InsufficientBasicLicenseError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/x-pack/plugins/ml/server/shared_services/license_checks/index.ts b/x-pack/plugins/ml/server/shared_services/license_checks/index.ts new file mode 100644 index 0000000000000..6b837dadf5c0d --- /dev/null +++ b/x-pack/plugins/ml/server/shared_services/license_checks/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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { LicenseCheck, licenseChecks } from './license_checks'; +export { InsufficientBasicLicenseError, InsufficientFullLicenseError } from './errors'; diff --git a/x-pack/plugins/ml/server/shared_services/license_checks.ts b/x-pack/plugins/ml/server/shared_services/license_checks/license_checks.ts similarity index 66% rename from x-pack/plugins/ml/server/shared_services/license_checks.ts rename to x-pack/plugins/ml/server/shared_services/license_checks/license_checks.ts index 191124ffa5f3a..3d9de1ef70f2d 100644 --- a/x-pack/plugins/ml/server/shared_services/license_checks.ts +++ b/x-pack/plugins/ml/server/shared_services/license_checks/license_checks.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MlServerLicense } from '../lib/license'; +import { MlServerLicense } from '../../lib/license'; +import { InsufficientFullLicenseError, InsufficientBasicLicenseError } from './errors'; export type LicenseCheck = () => void; @@ -14,12 +15,12 @@ export function licenseChecks( return { isFullLicense() { if (mlLicense.isFullLicense() === false) { - throw Error('Platinum, Enterprise or trial license needed'); + throw new InsufficientFullLicenseError('Platinum, Enterprise or trial license needed'); } }, isMinimumLicense() { if (mlLicense.isMinimumLicense() === false) { - throw Error('Basic license needed'); + throw new InsufficientBasicLicenseError('Basic license needed'); } }, }; diff --git a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts index 603b4fba17adb..53898cb64d07f 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts @@ -4,40 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, KibanaRequest } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { Job } from '../../../common/types/anomaly_detection_jobs'; -import { SharedServicesChecks } from '../shared_services'; +import { GetGuards } from '../shared_services'; export interface AnomalyDetectorsProvider { anomalyDetectorsProvider( - mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest ): { jobs(jobId?: string): Promise<{ count: number; jobs: Job[] }>; }; } -export function getAnomalyDetectorsProvider({ - isFullLicense, - getHasMlCapabilities, -}: SharedServicesChecks): AnomalyDetectorsProvider { +export function getAnomalyDetectorsProvider(getGuards: GetGuards): AnomalyDetectorsProvider { return { - anomalyDetectorsProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) { - // APM is using this service in anomaly alert, kibana alerting doesn't provide request object - // So we are adding a dummy request for now - // TODO: Remove this once kibana alerting provides request object - const hasMlCapabilities = - request.params !== 'DummyKibanaRequest' - ? getHasMlCapabilities(request) - : (_caps: string[]) => Promise.resolve(); + anomalyDetectorsProvider(request: KibanaRequest) { return { async jobs(jobId?: string) { - isFullLicense(); - await hasMlCapabilities(['canGetJobs']); - return mlClusterClient.callAsInternalUser( - 'ml.jobs', - jobId !== undefined ? { jobId } : {} - ); + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canGetJobs']) + .ok(async ({ scopedClient }) => { + const { body } = await scopedClient.asInternalUser.ml.getJobs<{ + count: number; + jobs: Job[]; + }>(jobId !== undefined ? { job_id: jobId } : undefined); + return body; + }); }, }; }, diff --git a/x-pack/plugins/ml/server/shared_services/providers/job_service.ts b/x-pack/plugins/ml/server/shared_services/providers/job_service.ts index c734dcc1583a1..2897bf08717f8 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/job_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/job_service.ts @@ -4,38 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, KibanaRequest } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { jobServiceProvider } from '../../models/job_service'; -import { SharedServicesChecks } from '../shared_services'; +import { GetGuards } from '../shared_services'; type OrigJobServiceProvider = ReturnType; export interface JobServiceProvider { jobServiceProvider( - mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest ): { jobsSummary: OrigJobServiceProvider['jobsSummary']; }; } -export function getJobServiceProvider({ - isFullLicense, - getHasMlCapabilities, -}: SharedServicesChecks): JobServiceProvider { +export function getJobServiceProvider(getGuards: GetGuards): JobServiceProvider { return { - jobServiceProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) { - // const hasMlCapabilities = getHasMlCapabilities(request); - const { jobsSummary } = jobServiceProvider(mlClusterClient); + jobServiceProvider(request: KibanaRequest) { return { - async jobsSummary(...args) { - isFullLicense(); - // Removed while https://github.com/elastic/kibana/issues/64588 exists. - // SIEM are calling this endpoint with a dummy request object from their alerting - // integration and currently alerting does not supply a request object. - // await hasMlCapabilities(['canGetJobs']); - - return jobsSummary(...args); + jobsSummary: async (...args) => { + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canGetJobs']) + .ok(async ({ scopedClient }) => { + const { jobsSummary } = jobServiceProvider(scopedClient); + return jobsSummary(...args); + }); }, }; }, diff --git a/x-pack/plugins/ml/server/shared_services/providers/modules.ts b/x-pack/plugins/ml/server/shared_services/providers/modules.ts index fb7d59f9c8218..a727d96433f1d 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/modules.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/modules.ts @@ -4,23 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ILegacyScopedClusterClient, - KibanaRequest, - SavedObjectsClientContract, -} from 'kibana/server'; +import { IScopedClusterClient, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { DataRecognizer } from '../../models/data_recognizer'; -import { SharedServicesChecks } from '../shared_services'; +import { GetGuards } from '../shared_services'; import { moduleIdParamSchema, setupModuleBodySchema } from '../../routes/schemas/modules'; -import { HasMlCapabilities } from '../../lib/capabilities'; export type ModuleSetupPayload = TypeOf & TypeOf; export interface ModulesProvider { modulesProvider( - mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract ): { @@ -31,61 +25,58 @@ export interface ModulesProvider { }; } -export function getModulesProvider({ - isFullLicense, - getHasMlCapabilities, -}: SharedServicesChecks): ModulesProvider { +export function getModulesProvider(getGuards: GetGuards): ModulesProvider { return { - modulesProvider( - mlClusterClient: ILegacyScopedClusterClient, - request: KibanaRequest, - savedObjectsClient: SavedObjectsClientContract - ) { - let hasMlCapabilities: HasMlCapabilities; - if (request.params === 'DummyKibanaRequest') { - hasMlCapabilities = () => Promise.resolve(); - } else { - hasMlCapabilities = getHasMlCapabilities(request); - } - const dr = dataRecognizerFactory(mlClusterClient, savedObjectsClient, request); - + modulesProvider(request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract) { return { async recognize(...args) { - isFullLicense(); - await hasMlCapabilities(['canCreateJob']); - - return dr.findMatches(...args); + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canGetJobs']) + .ok(async ({ scopedClient }) => { + const dr = dataRecognizerFactory(scopedClient, savedObjectsClient, request); + return dr.findMatches(...args); + }); }, async getModule(moduleId: string) { - isFullLicense(); - await hasMlCapabilities(['canGetJobs']); - - return dr.getModule(moduleId); + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canGetJobs']) + .ok(async ({ scopedClient }) => { + const dr = dataRecognizerFactory(scopedClient, savedObjectsClient, request); + return dr.getModule(moduleId); + }); }, async listModules() { - isFullLicense(); - await hasMlCapabilities(['canGetJobs']); - - return dr.listModules(); + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canGetJobs']) + .ok(async ({ scopedClient }) => { + const dr = dataRecognizerFactory(scopedClient, savedObjectsClient, request); + return dr.listModules(); + }); }, async setup(payload: ModuleSetupPayload) { - isFullLicense(); - await hasMlCapabilities(['canCreateJob']); - - return dr.setup( - payload.moduleId, - payload.prefix, - payload.groups, - payload.indexPatternName, - payload.query, - payload.useDedicatedIndex, - payload.startDatafeed, - payload.start, - payload.end, - payload.jobOverrides, - payload.datafeedOverrides, - payload.estimateModelMemory - ); + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canCreateJob']) + .ok(async ({ scopedClient }) => { + const dr = dataRecognizerFactory(scopedClient, savedObjectsClient, request); + return dr.setup( + payload.moduleId, + payload.prefix, + payload.groups, + payload.indexPatternName, + payload.query, + payload.useDedicatedIndex, + payload.startDatafeed, + payload.start, + payload.end, + payload.jobOverrides, + payload.datafeedOverrides, + payload.estimateModelMemory + ); + }); }, }; }, @@ -93,9 +84,9 @@ export function getModulesProvider({ } function dataRecognizerFactory( - mlClusterClient: ILegacyScopedClusterClient, + client: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract, request: KibanaRequest ) { - return new DataRecognizer(mlClusterClient, savedObjectsClient, request); + return new DataRecognizer(client, savedObjectsClient, request); } diff --git a/x-pack/plugins/ml/server/shared_services/providers/results_service.ts b/x-pack/plugins/ml/server/shared_services/providers/results_service.ts index 6af4eb008567a..5536765cc376a 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/results_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/results_service.ts @@ -4,41 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, KibanaRequest } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { resultsServiceProvider } from '../../models/results_service'; -import { SharedServicesChecks } from '../shared_services'; +import { GetGuards } from '../shared_services'; type OrigResultsServiceProvider = ReturnType; export interface ResultsServiceProvider { resultsServiceProvider( - mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest ): { getAnomaliesTableData: OrigResultsServiceProvider['getAnomaliesTableData']; }; } -export function getResultsServiceProvider({ - isFullLicense, - getHasMlCapabilities, -}: SharedServicesChecks): ResultsServiceProvider { +export function getResultsServiceProvider(getGuards: GetGuards): ResultsServiceProvider { return { - resultsServiceProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) { - // Uptime is using this service in anomaly alert, kibana alerting doesn't provide request object - // So we are adding a dummy request for now - // TODO: Remove this once kibana alerting provides request object - const hasMlCapabilities = - request.params !== 'DummyKibanaRequest' - ? getHasMlCapabilities(request) - : (_caps: string[]) => Promise.resolve(); - - const { getAnomaliesTableData } = resultsServiceProvider(mlClusterClient); + resultsServiceProvider(request: KibanaRequest) { return { async getAnomaliesTableData(...args) { - isFullLicense(); - await hasMlCapabilities(['canGetJobs']); - return getAnomaliesTableData(...args); + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canGetJobs']) + .ok(async ({ scopedClient }) => { + const { getAnomaliesTableData } = resultsServiceProvider(scopedClient); + return getAnomaliesTableData(...args); + }); }, }; }, diff --git a/x-pack/plugins/ml/server/shared_services/providers/system.ts b/x-pack/plugins/ml/server/shared_services/providers/system.ts index d292abc438a2f..3217ff13787b0 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/system.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/system.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, KibanaRequest } from 'kibana/server'; -import { SearchResponse, SearchParams } from 'elasticsearch'; +import { KibanaRequest } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; +import { RequestParams } from '@elastic/elasticsearch'; import { MlServerLicense } from '../../lib/license'; import { CloudSetup } from '../../../../cloud/server'; import { spacesUtilsProvider } from '../../lib/spaces_utils'; @@ -14,73 +15,79 @@ import { capabilitiesProvider } from '../../lib/capabilities'; import { MlInfoResponse } from '../../../common/types/ml_server_info'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; import { MlCapabilitiesResponse, ResolveMlCapabilities } from '../../../common/types/capabilities'; -import { SharedServicesChecks } from '../shared_services'; +import { GetGuards } from '../shared_services'; export interface MlSystemProvider { mlSystemProvider( - mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest ): { mlCapabilities(): Promise; mlInfo(): Promise; - mlAnomalySearch(searchParams: SearchParams): Promise>; + mlAnomalySearch(searchParams: RequestParams.Search): Promise>; }; } export function getMlSystemProvider( - { isMinimumLicense, isFullLicense, getHasMlCapabilities }: SharedServicesChecks, + getGuards: GetGuards, mlLicense: MlServerLicense, spaces: SpacesPluginSetup | undefined, cloud: CloudSetup | undefined, resolveMlCapabilities: ResolveMlCapabilities ): MlSystemProvider { return { - mlSystemProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) { - // const hasMlCapabilities = getHasMlCapabilities(request); - const { callAsInternalUser } = mlClusterClient; + mlSystemProvider(request: KibanaRequest) { return { async mlCapabilities() { - isMinimumLicense(); + return await getGuards(request) + .isMinimumLicense() + .ok(async ({ scopedClient }) => { + const { isMlEnabledInSpace } = + spaces !== undefined + ? spacesUtilsProvider(spaces, request) + : { isMlEnabledInSpace: async () => true }; - const { isMlEnabledInSpace } = - spaces !== undefined - ? spacesUtilsProvider(spaces, request) - : { isMlEnabledInSpace: async () => true }; + const mlCapabilities = await resolveMlCapabilities(request); + if (mlCapabilities === null) { + throw new Error('mlCapabilities is not defined'); + } - const mlCapabilities = await resolveMlCapabilities(request); - if (mlCapabilities === null) { - throw new Error('mlCapabilities is not defined'); - } - - const { getCapabilities } = capabilitiesProvider( - mlClusterClient, - mlCapabilities, - mlLicense, - isMlEnabledInSpace - ); - return getCapabilities(); + const { getCapabilities } = capabilitiesProvider( + scopedClient, + mlCapabilities, + mlLicense, + isMlEnabledInSpace + ); + return getCapabilities(); + }); }, async mlInfo(): Promise { - isMinimumLicense(); + return await getGuards(request) + .isMinimumLicense() + .ok(async ({ scopedClient }) => { + const { asInternalUser } = scopedClient; - const info = await callAsInternalUser('ml.info'); - const cloudId = cloud && cloud.cloudId; - return { - ...info, - cloudId, - }; + const { body: info } = await asInternalUser.ml.info(); + const cloudId = cloud && cloud.cloudId; + return { + ...info, + cloudId, + }; + }); }, - async mlAnomalySearch(searchParams: SearchParams): Promise> { - isFullLicense(); - // Removed while https://github.com/elastic/kibana/issues/64588 exists. - // SIEM are calling this endpoint with a dummy request object from their alerting - // integration and currently alerting does not supply a request object. - // await hasMlCapabilities(['canAccessML']); - - return callAsInternalUser('search', { - ...searchParams, - index: ML_RESULTS_INDEX_PATTERN, - }); + async mlAnomalySearch( + searchParams: RequestParams.Search + ): Promise> { + return await getGuards(request) + .isFullLicense() + .hasMlCapabilities(['canAccessML']) + .ok(async ({ scopedClient }) => { + const { asInternalUser } = scopedClient; + const { body } = await asInternalUser.search>({ + ...searchParams, + index: ML_RESULTS_INDEX_PATTERN, + }); + return body; + }); }, }; }, diff --git a/x-pack/plugins/ml/server/shared_services/shared_services.ts b/x-pack/plugins/ml/server/shared_services/shared_services.ts index 3345111fad4ae..4c568e4515a27 100644 --- a/x-pack/plugins/ml/server/shared_services/shared_services.ts +++ b/x-pack/plugins/ml/server/shared_services/shared_services.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest } from 'kibana/server'; +import { IClusterClient, IScopedClusterClient } from 'kibana/server'; +// including KibanaRequest from 'kibana/server' causes an error +// when being used with instanceof +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaRequest } from '../../.././../../src/core/server/http'; import { MlServerLicense } from '../lib/license'; import { SpacesPluginSetup } from '../../../spaces/server'; @@ -18,8 +22,9 @@ import { AnomalyDetectorsProvider, getAnomalyDetectorsProvider, } from './providers/anomaly_detectors'; -import { ResolveMlCapabilities } from '../../common/types/capabilities'; +import { ResolveMlCapabilities, MlCapabilitiesKey } from '../../common/types/capabilities'; import { hasMlCapabilitiesProvider, HasMlCapabilities } from '../lib/capabilities'; +import { MLClusterClientUninitialized } from './errors'; export type SharedServices = JobServiceProvider & AnomalyDetectorsProvider & @@ -27,31 +32,97 @@ export type SharedServices = JobServiceProvider & ModulesProvider & ResultsServiceProvider; +interface Guards { + isMinimumLicense(): Guards; + isFullLicense(): Guards; + hasMlCapabilities: (caps: MlCapabilitiesKey[]) => Guards; + ok(callback: OkCallback): any; +} + +export type GetGuards = (request: KibanaRequest) => Guards; + export interface SharedServicesChecks { - isFullLicense(): void; - isMinimumLicense(): void; - getHasMlCapabilities(request: KibanaRequest): HasMlCapabilities; + getGuards(request: KibanaRequest): Guards; } +interface OkParams { + scopedClient: IScopedClusterClient; +} + +type OkCallback = (okParams: OkParams) => any; + export function createSharedServices( mlLicense: MlServerLicense, spaces: SpacesPluginSetup | undefined, cloud: CloudSetup, - resolveMlCapabilities: ResolveMlCapabilities + resolveMlCapabilities: ResolveMlCapabilities, + getClusterClient: () => IClusterClient | null ): SharedServices { + const getRequestItems = getRequestItemsProvider(resolveMlCapabilities, getClusterClient); const { isFullLicense, isMinimumLicense } = licenseChecks(mlLicense); - const getHasMlCapabilities = hasMlCapabilitiesProvider(resolveMlCapabilities); - const checks: SharedServicesChecks = { - isFullLicense, - isMinimumLicense, - getHasMlCapabilities, - }; + + function getGuards(request: KibanaRequest): Guards { + const { hasMlCapabilities, scopedClient } = getRequestItems(request); + const asyncGuards: Array> = []; + + const guards: Guards = { + isMinimumLicense: () => { + isMinimumLicense(); + return guards; + }, + isFullLicense: () => { + isFullLicense(); + return guards; + }, + hasMlCapabilities: (caps: MlCapabilitiesKey[]) => { + asyncGuards.push(hasMlCapabilities(caps)); + return guards; + }, + async ok(callback: OkCallback) { + await Promise.all(asyncGuards); + return callback({ scopedClient }); + }, + }; + return guards; + } return { - ...getJobServiceProvider(checks), - ...getAnomalyDetectorsProvider(checks), - ...getModulesProvider(checks), - ...getResultsServiceProvider(checks), - ...getMlSystemProvider(checks, mlLicense, spaces, cloud, resolveMlCapabilities), + ...getJobServiceProvider(getGuards), + ...getAnomalyDetectorsProvider(getGuards), + ...getModulesProvider(getGuards), + ...getResultsServiceProvider(getGuards), + ...getMlSystemProvider(getGuards, mlLicense, spaces, cloud, resolveMlCapabilities), + }; +} + +function getRequestItemsProvider( + resolveMlCapabilities: ResolveMlCapabilities, + getClusterClient: () => IClusterClient | null +) { + return (request: KibanaRequest) => { + const getHasMlCapabilities = hasMlCapabilitiesProvider(resolveMlCapabilities); + let hasMlCapabilities: HasMlCapabilities; + let scopedClient: IScopedClusterClient; + // While https://github.com/elastic/kibana/issues/64588 exists we + // will not receive a real request object when being called from an alert. + // instead a dummy request object will be supplied + const clusterClient = getClusterClient(); + + if (clusterClient === null) { + throw new MLClusterClientUninitialized(`ML's cluster client has not been initialized`); + } + + if (request instanceof KibanaRequest) { + hasMlCapabilities = getHasMlCapabilities(request); + scopedClient = clusterClient.asScoped(request); + } else { + hasMlCapabilities = () => Promise.resolve(); + const { asInternalUser } = clusterClient; + scopedClient = { + asInternalUser, + asCurrentUser: asInternalUser, + }; + } + return { hasMlCapabilities, scopedClient }; }; } diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap index c2ee498d7189d..e35d2ba6108f5 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap @@ -142,6 +142,8 @@ exports[`CcrShard that it renders normally 1`] = ` } id="ccrLatestStat" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="l" > { + this.kibanaStatus = nextStatus.level; + }); } filterCollectorSet(usageCollection) { @@ -128,7 +128,7 @@ export class BulkUploader { async _fetchAndUpload(usageCollection) { const collectorsReady = await usageCollection.areAllCollectorsReady(); const hasUsageCollectors = usageCollection.some(usageCollection.isUsageCollector); - if (!collectorsReady || typeof this.kibanaStatusGetter !== 'function') { + if (!collectorsReady) { this._log.debug('Skipping bulk uploading because not all collectors are ready'); if (hasUsageCollectors) { this._lastFetchUsageTime = null; @@ -172,10 +172,23 @@ export class BulkUploader { return await sendBulkPayload(this._cluster, this._interval, payload, this._log); } + getConvertedKibanaStatuss() { + if (this.kibanaStatus === ServiceStatusLevels.available) { + return 'green'; + } + if (this.kibanaStatus === ServiceStatusLevels.critical) { + return 'red'; + } + if (this.kibanaStatus === ServiceStatusLevels.degraded) { + return 'yellow'; + } + return 'unknown'; + } + getKibanaStats(type) { const stats = { ...this.kibanaStats, - status: this.kibanaStatusGetter(), + status: this.getConvertedKibanaStatuss(), }; if (type === KIBANA_STATS_TYPE_MONITORING) { diff --git a/x-pack/plugins/monitoring/server/plugin.test.ts b/x-pack/plugins/monitoring/server/plugin.test.ts index 8bd43b6be0a8d..a2520593c436d 100644 --- a/x-pack/plugins/monitoring/server/plugin.test.ts +++ b/x-pack/plugins/monitoring/server/plugin.test.ts @@ -5,8 +5,6 @@ */ import { Plugin } from './plugin'; import { combineLatest } from 'rxjs'; -// @ts-ignore -import { initBulkUploader } from './kibana_monitoring'; import { AlertsFactory } from './alerts'; jest.mock('rxjs', () => ({ @@ -27,10 +25,6 @@ jest.mock('./license_service', () => ({ })), })); -jest.mock('./kibana_monitoring', () => ({ - initBulkUploader: jest.fn(), -})); - describe('Monitoring plugin', () => { const initializerContext = { logger: { @@ -71,6 +65,11 @@ describe('Monitoring plugin', () => { createClient: jest.fn(), }, }, + status: { + overall$: { + subscribe: jest.fn(), + }, + }, }; const setupPlugins = { @@ -113,19 +112,13 @@ describe('Monitoring plugin', () => { afterEach(() => { (setupPlugins.alerts.registerType as jest.Mock).mockReset(); + (coreSetup.status.overall$.subscribe as jest.Mock).mockReset(); }); it('always create the bulk uploader', async () => { - const setKibanaStatusGetter = jest.fn(); - (initBulkUploader as jest.Mock).mockImplementation(() => { - return { - setKibanaStatusGetter, - }; - }); const plugin = new Plugin(initializerContext as any); - const contract = await plugin.setup(coreSetup as any, setupPlugins as any); - contract.registerLegacyAPI(null as any); - expect(setKibanaStatusGetter).toHaveBeenCalled(); + await plugin.setup(coreSetup as any, setupPlugins as any); + expect(coreSetup.status.overall$.subscribe).toHaveBeenCalled(); }); it('should register all alerts', async () => { diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 501c96b12fde8..f5cbadb523a81 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -46,7 +46,6 @@ import { IBulkUploader, PluginsSetup, PluginsStart, - LegacyAPI, LegacyRequest, } from './types'; @@ -158,6 +157,7 @@ export class Plugin { elasticsearch: core.elasticsearch, config, log: kibanaMonitoringLog, + statusGetter$: core.status.overall$, kibanaStats: { uuid: this.initializerContext.env.instanceUuid, name: serverInfo.name, @@ -221,11 +221,6 @@ export class Plugin { } return { - // The legacy plugin calls this to register certain legacy dependencies - // that are necessary for the plugin to properly run - registerLegacyAPI: (legacyAPI: LegacyAPI) => { - this.setupLegacy(legacyAPI); - }, // OSS stats api needs to call this in order to centralize how // we fetch kibana specific stats getKibanaStats: () => this.bulkUploader.getKibanaStats(), @@ -280,11 +275,6 @@ export class Plugin { }); } - async setupLegacy(legacyAPI: LegacyAPI) { - // Set the stats getter - this.bulkUploader.setKibanaStatusGetter(() => legacyAPI.getServerStatus()); - } - getLegacyShim( config: MonitoringConfig, legacyConfig: any, diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index a0ef6d3e2d984..e6a4b174df55d 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -33,10 +33,6 @@ export interface MonitoringElasticsearchConfig { hosts: string[]; } -export interface LegacyAPI { - getServerStatus: () => string; -} - export interface PluginsSetup { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; telemetryCollectionManager?: TelemetryCollectionManagerPluginSetup; @@ -77,7 +73,6 @@ export interface LegacyShimDependencies { } export interface IBulkUploader { - setKibanaStatusGetter: (getter: () => string | undefined) => void; getKibanaStats: () => any; } diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx index db7fca140be89..19995ed233e8d 100644 --- a/x-pack/plugins/observability/public/application/application.test.tsx +++ b/x-pack/plugins/observability/public/application/application.test.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { createMemoryHistory } from 'history'; import React from 'react'; -import { renderApp } from './'; import { Observable } from 'rxjs'; -import { CoreStart, AppMountParameters } from 'src/core/public'; +import { AppMountParameters, CoreStart } from 'src/core/public'; +import { renderApp } from './'; describe('renderApp', () => { it('renders', () => { @@ -19,6 +20,7 @@ describe('renderApp', () => { } as unknown) as CoreStart; const params = ({ element: window.document.createElement('div'), + history: createMemoryHistory(), } as unknown) as AppMountParameters; expect(() => { diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 4c0147dc3cd51..fa691a7f41ddb 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { createHashHistory } from 'history'; import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; @@ -52,10 +51,10 @@ function App() { ); } -export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { +export const renderApp = (core: CoreStart, { element, history }: AppMountParameters) => { const i18nCore = core.i18n; const isDarkMode = core.uiSettings.get('theme:darkMode'); - const history = createHashHistory(); + ReactDOM.render( diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index a1d51ffda6afd..b635c2c68b926 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -112,7 +112,7 @@ export function APMSection({ absoluteTime, relativeTime, bucketSize }: Props) { `${formatTpm(value)} tpm`} /> diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index aa1dc1640125e..343611294bc45 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -150,7 +150,7 @@ export function LogsSection({ absoluteTime, relativeTime, bucketSize }: Props) { /> numeral(d).format('0a')} /> diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index cfb06af3224c7..5c23c7a065b5e 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -182,7 +182,7 @@ function UptimeBarSeries({ /> numeral(x).format('0a')} /> diff --git a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.test.ts b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.test.ts index 831bf45cf72ea..b7147fe0a9ebd 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.test.ts @@ -7,17 +7,13 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; import { CancellationToken } from '../../../../common'; -import { LevelLogger } from '../../../lib'; +import { createMockLevelLogger } from '../../../test_helpers/create_mock_levellogger'; import { ScrollConfig } from '../../../types'; import { createHitIterator } from './hit_iterator'; -const mockLogger = { - error: new Function(), - debug: new Function(), - warning: new Function(), -} as LevelLogger; +const mockLogger = createMockLevelLogger(); const debugLogStub = sinon.stub(mockLogger, 'debug'); -const warnLogStub = sinon.stub(mockLogger, 'warning'); +const warnLogStub = sinon.stub(mockLogger, 'warn'); const errorLogStub = sinon.stub(mockLogger, 'error'); const mockCallEndpoint = sinon.stub(); const mockSearchRequest = {}; @@ -134,4 +130,30 @@ describe('hitIterator', function () { expect(errorLogStub.callCount).to.be(1); expect(errorThrown).to.be(true); }); + + it('handles scroll id could not be cleared', async () => { + // Setup + mockCallEndpoint.withArgs('clearScroll').rejects({ status: 404 }); + + // Begin + const hitIterator = createHitIterator(mockLogger); + const iterator = hitIterator( + mockConfig, + mockCallEndpoint, + mockSearchRequest, + realCancellationToken + ); + + while (true) { + const { done: iterationDone, value: hit } = await iterator.next(); + if (iterationDone) { + break; + } + expect(hit).to.be('you found me'); + } + + expect(mockCallEndpoint.callCount).to.be(13); + expect(warnLogStub.callCount).to.be(1); + expect(errorLogStub.callCount).to.be(1); + }); }); diff --git a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.ts b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.ts index dee653cf30007..b95a311200266 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/hit_iterator.ts @@ -68,11 +68,17 @@ export function createHitIterator(logger: LevelLogger) { ); } - function clearScroll(scrollId: string | undefined) { + async function clearScroll(scrollId: string | undefined) { logger.debug('executing clearScroll request'); - return callEndpoint('clearScroll', { - scrollId: [scrollId], - }); + try { + await callEndpoint('clearScroll', { + scrollId: [scrollId], + }); + } catch (err) { + // Do not throw the error, as the job can still be completed successfully + logger.warn('Scroll context can not be cleared!'); + logger.error(err); + } } try { @@ -86,7 +92,7 @@ export function createHitIterator(logger: LevelLogger) { ({ scrollId, hits } = await scroll(scrollId)); if (cancellationToken.isCancelled()) { - logger.warning( + logger.warn( 'Any remaining scrolling searches have been cancelled by the cancellation token.' ); } diff --git a/x-pack/plugins/security/public/account_management/account_management_app.test.ts b/x-pack/plugins/security/public/account_management/account_management_app.test.ts index 37b97a8472310..c41bd43872bee 100644 --- a/x-pack/plugins/security/public/account_management/account_management_app.test.ts +++ b/x-pack/plugins/security/public/account_management/account_management_app.test.ts @@ -54,6 +54,7 @@ describe('accountManagementApp', () => { element: containerMock, appBasePath: '', onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), history: scopedHistoryMock.create(), }); diff --git a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts index 0e262e9089842..eafad74d2f0d8 100644 --- a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts +++ b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts @@ -48,6 +48,7 @@ describe('accessAgreementApp', () => { element: containerMock, appBasePath: '', onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), history: scopedHistoryMock.create(), }); diff --git a/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts b/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts index c5b9245414630..e6723085460f8 100644 --- a/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts +++ b/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts @@ -54,6 +54,7 @@ describe('captureURLApp', () => { element: document.createElement('div'), appBasePath: '', onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), history: (scopedHistoryMock.create() as unknown) as ScopedHistory, }); diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts b/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts index 15d55136b405d..86a5d21f1b233 100644 --- a/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts +++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts @@ -46,6 +46,7 @@ describe('loggedOutApp', () => { element: containerMock, appBasePath: '', onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), history: scopedHistoryMock.create(), }); diff --git a/x-pack/plugins/security/public/authentication/login/login_app.test.ts b/x-pack/plugins/security/public/authentication/login/login_app.test.ts index a6e5a321ef6ec..5ae8afab9de23 100644 --- a/x-pack/plugins/security/public/authentication/login/login_app.test.ts +++ b/x-pack/plugins/security/public/authentication/login/login_app.test.ts @@ -51,6 +51,7 @@ describe('loginApp', () => { element: containerMock, appBasePath: '', onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), history: scopedHistoryMock.create(), }); diff --git a/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts b/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts index 46b1083a2ed14..b7bfdf492305e 100644 --- a/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts +++ b/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts @@ -52,6 +52,7 @@ describe('logoutApp', () => { element: containerMock, appBasePath: '', onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), history: scopedHistoryMock.create(), }); diff --git a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts index 0eed1382c270b..6e0e06dd3dc44 100644 --- a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts +++ b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts @@ -53,6 +53,7 @@ describe('overwrittenSessionApp', () => { element: containerMock, appBasePath: '', onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), history: scopedHistoryMock.create(), }); diff --git a/x-pack/plugins/security/server/authorization/authorization_service.ts b/x-pack/plugins/security/server/authorization/authorization_service.ts index 4190499cbd5f4..2dead301b298a 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.ts +++ b/x-pack/plugins/security/server/authorization/authorization_service.ts @@ -5,7 +5,7 @@ */ import { Subscription, Observable } from 'rxjs'; -import { UICapabilities } from 'ui/capabilities'; +import type { Capabilities as UICapabilities } from '../../../../../src/core/types'; import { LoggerFactory, KibanaRequest, diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index c126be1b07f6e..41d596d570fb9 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -5,7 +5,7 @@ */ import { flatten, isObject, mapValues } from 'lodash'; -import { UICapabilities } from 'ui/capabilities'; +import type { Capabilities as UICapabilities } from '../../../../../src/core/types'; import { KibanaRequest, Logger } from '../../../../../src/core/server'; import { Feature } from '../../../features/server'; diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index 507ce63c7b815..b72a52f0a0eb7 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -13,3 +13,4 @@ export const LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG = 'endpoint:limited-concurre export const LIMITED_CONCURRENCY_ENDPOINT_COUNT = 100; export const TRUSTED_APPS_LIST_API = '/api/endpoint/trusted_apps'; +export const TRUSTED_APPS_CREATE_API = '/api/endpoint/trusted_apps'; diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts index 7aec8e15c317c..b0c769216732d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GetTrustedAppsRequestSchema } from './trusted_apps'; +import { GetTrustedAppsRequestSchema, PostTrustedAppCreateRequestSchema } from './trusted_apps'; describe('When invoking Trusted Apps Schema', () => { describe('for GET List', () => { @@ -68,4 +68,180 @@ describe('When invoking Trusted Apps Schema', () => { }); }); }); + + describe('for POST Create', () => { + const getCreateTrustedAppItem = () => ({ + name: 'Some Anti-Virus App', + description: 'this one is ok', + os: 'windows', + entries: [ + { + field: 'path', + type: 'match', + operator: 'included', + value: 'c:/programs files/Anti-Virus', + }, + ], + }); + const body = PostTrustedAppCreateRequestSchema.body; + + it('should not error on a valid message', () => { + const bodyMsg = getCreateTrustedAppItem(); + expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg); + }); + + it('should validate `name` is required', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + name: undefined, + }; + expect(() => body.validate(bodyMsg)).toThrow(); + }); + + it('should validate `name` value to be non-empty', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + name: '', + }; + expect(() => body.validate(bodyMsg)).toThrow(); + }); + + it('should validate `description` as optional', () => { + const { description, ...bodyMsg } = getCreateTrustedAppItem(); + expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg); + }); + + it('should validate `description` to be non-empty if defined', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + description: '', + }; + expect(() => body.validate(bodyMsg)).toThrow(); + }); + + it('should validate `os` to to only accept known values', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + os: undefined, + }; + expect(() => body.validate(bodyMsg)).toThrow(); + + const bodyMsg2 = { + ...bodyMsg, + os: '', + }; + expect(() => body.validate(bodyMsg2)).toThrow(); + + const bodyMsg3 = { + ...bodyMsg, + os: 'winz', + }; + expect(() => body.validate(bodyMsg3)).toThrow(); + + ['linux', 'macos', 'windows'].forEach((os) => { + expect(() => { + body.validate({ + ...bodyMsg, + os, + }); + }).not.toThrow(); + }); + }); + + it('should validate `entries` as required', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + entries: undefined, + }; + expect(() => body.validate(bodyMsg)).toThrow(); + + const { entries, ...bodyMsg2 } = getCreateTrustedAppItem(); + expect(() => body.validate(bodyMsg2)).toThrow(); + }); + + it('should validate `entries` to have at least 1 item', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + entries: [], + }; + expect(() => body.validate(bodyMsg)).toThrow(); + }); + + describe('when `entries` are defined', () => { + const getTrustedAppItemEntryItem = () => getCreateTrustedAppItem().entries[0]; + + it('should validate `entry.field` is required', () => { + const { field, ...entry } = getTrustedAppItemEntryItem(); + const bodyMsg = { + ...getCreateTrustedAppItem(), + entries: [entry], + }; + expect(() => body.validate(bodyMsg)).toThrow(); + }); + + it('should validate `entry.field` is limited to known values', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + entries: [ + { + ...getTrustedAppItemEntryItem(), + field: '', + }, + ], + }; + expect(() => body.validate(bodyMsg)).toThrow(); + + const bodyMsg2 = { + ...getCreateTrustedAppItem(), + entries: [ + { + ...getTrustedAppItemEntryItem(), + field: 'invalid value', + }, + ], + }; + expect(() => body.validate(bodyMsg2)).toThrow(); + + ['hash', 'path'].forEach((field) => { + const bodyMsg3 = { + ...getCreateTrustedAppItem(), + entries: [ + { + ...getTrustedAppItemEntryItem(), + field, + }, + ], + }; + + expect(() => body.validate(bodyMsg3)).not.toThrow(); + }); + }); + + it.todo('should validate `entry.type` is limited to known values'); + + it.todo('should validate `entry.operator` is limited to known values'); + + it('should validate `entry.value` required', () => { + const { value, ...entry } = getTrustedAppItemEntryItem(); + const bodyMsg = { + ...getCreateTrustedAppItem(), + entries: [entry], + }; + expect(() => body.validate(bodyMsg)).toThrow(); + }); + + it('should validate `entry.value` is non-empty', () => { + const bodyMsg = { + ...getCreateTrustedAppItem(), + entries: [ + { + ...getTrustedAppItemEntryItem(), + value: '', + }, + ], + }; + expect(() => body.validate(bodyMsg)).toThrow(); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts index 20fab93aaf304..7535b23a10e8a 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts @@ -12,3 +12,20 @@ export const GetTrustedAppsRequestSchema = { per_page: schema.maybe(schema.number({ defaultValue: 20, min: 1 })), }), }; + +export const PostTrustedAppCreateRequestSchema = { + body: schema.object({ + name: schema.string({ minLength: 1 }), + description: schema.maybe(schema.string({ minLength: 1 })), + os: schema.oneOf([schema.literal('linux'), schema.literal('macos'), schema.literal('windows')]), + entries: schema.arrayOf( + schema.object({ + field: schema.oneOf([schema.literal('hash'), schema.literal('path')]), + type: schema.literal('match'), + operator: schema.literal('included'), + value: schema.string({ minLength: 1 }), + }), + { minSize: 1 } + ), + }), +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts index 2905274bef1cb..7aeb6c6024b99 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts @@ -5,7 +5,10 @@ */ import { TypeOf } from '@kbn/config-schema'; -import { GetTrustedAppsRequestSchema } from '../schema/trusted_apps'; +import { + GetTrustedAppsRequestSchema, + PostTrustedAppCreateRequestSchema, +} from '../schema/trusted_apps'; /** API request params for retrieving a list of Trusted Apps */ export type GetTrustedAppsListRequest = TypeOf; @@ -16,6 +19,12 @@ export interface GetTrustedListAppsResponse { data: TrustedApp[]; } +/** API Request body for creating a new Trusted App entry */ +export type PostTrustedAppCreateRequest = TypeOf; +export interface PostTrustedAppCreateResponse { + data: TrustedApp; +} + interface MacosLinuxConditionEntry { field: 'hash' | 'path'; type: 'match'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts new file mode 100644 index 0000000000000..91a53066b4f4b --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/all/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; + +import { HostItem, HostsFields } from '../common'; +import { CursorType, Inspect, Maybe, PageInfoPaginated, RequestOptionsPaginated } from '../..'; + +export interface HostsEdges { + node: HostItem; + + cursor: CursorType; +} + +export interface HostsStrategyResponse extends IEsSearchResponse { + edges: HostsEdges[]; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} + +export interface HostsRequestOptions extends RequestOptionsPaginated { + defaultIndex: string[]; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts new file mode 100644 index 0000000000000..d15da4bf07ae7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CloudEcs } from '../../../../ecs/cloud'; +import { HostEcs, OsEcs } from '../../../../ecs/host'; +import { Maybe, SearchHit, TotalValue } from '../..'; + +export enum HostPolicyResponseActionStatus { + success = 'success', + failure = 'failure', + warning = 'warning', +} + +export enum HostsFields { + lastSeen = 'lastSeen', + hostName = 'hostName', +} + +export interface EndpointFields { + endpointPolicy?: Maybe; + sensorVersion?: Maybe; + policyStatus?: Maybe; +} + +export interface HostItem { + _id?: Maybe; + cloud?: Maybe; + endpoint?: Maybe; + host?: Maybe; + lastSeen?: Maybe; +} + +export interface HostValue { + value: number; + value_as_string: string; +} + +export interface HostBucketItem { + key: string; + doc_count: number; + timestamp: HostValue; +} + +export interface HostBuckets { + buckets: HostBucketItem[]; +} + +export interface HostOsHitsItem { + hits: { + total: TotalValue | number; + max_score: number | null; + hits: Array<{ + _source: { host: { os: Maybe } }; + sort?: [number]; + _index?: string; + _type?: string; + _id?: string; + _score?: number | null; + }>; + }; +} + +export interface HostAggEsItem { + cloud_instance_id?: HostBuckets; + cloud_machine_type?: HostBuckets; + cloud_provider?: HostBuckets; + cloud_region?: HostBuckets; + firstSeen?: HostValue; + host_architecture?: HostBuckets; + host_id?: HostBuckets; + host_ip?: HostBuckets; + host_mac?: HostBuckets; + host_name?: HostBuckets; + host_os_name?: HostBuckets; + host_os_version?: HostBuckets; + host_type?: HostBuckets; + key?: string; + lastSeen?: HostValue; + os?: HostOsHitsItem; +} + +export interface HostEsData extends SearchHit { + sort: string[]; + aggregations: { + host_count: { + value: number; + }; + host_data: { + buckets: HostAggEsItem[]; + }; + }; +} + +export interface HostAggEsData extends SearchHit { + sort: string[]; + aggregations: HostAggEsItem; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts new file mode 100644 index 0000000000000..cbabe9dd11115 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe, RequestOptionsPaginated } from '../..'; +import { HostsFields } from '../common'; + +export interface HostFirstLastSeenRequestOptions + extends Partial> { + hostName: string; +} +export interface HostFirstLastSeenStrategyResponse extends IEsSearchResponse { + inspect?: Maybe; + firstSeen?: Maybe; + lastSeen?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index 3a0942d2decb8..a27899e454074 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -4,81 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; -import { CloudEcs } from '../../../ecs/cloud'; -import { HostEcs } from '../../../ecs/host'; - -import { - CursorType, - Inspect, - Maybe, - PageInfoPaginated, - RequestOptionsPaginated, - SortField, - TimerangeInput, -} from '..'; +export * from './all'; +export * from './common'; +export * from './overview'; +export * from './first_last_seen'; export enum HostsQueries { hosts = 'hosts', hostOverview = 'hostOverview', -} - -export enum HostPolicyResponseActionStatus { - success = 'success', - failure = 'failure', - warning = 'warning', -} - -export interface EndpointFields { - endpointPolicy?: Maybe; - - sensorVersion?: Maybe; - - policyStatus?: Maybe; -} - -export interface HostItem { - _id?: Maybe; - - cloud?: Maybe; - - endpoint?: Maybe; - - host?: Maybe; - - lastSeen?: Maybe; -} - -export interface HostsEdges { - node: HostItem; - - cursor: CursorType; -} - -export interface HostsStrategyResponse extends IEsSearchResponse { - edges: HostsEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; - - inspect?: Maybe; -} - -export interface HostOverviewStrategyResponse extends IEsSearchResponse, HostItem { - inspect?: Maybe; -} - -export interface HostsRequestOptions extends RequestOptionsPaginated { - sort: SortField; - defaultIndex: string[]; -} - -export interface HostLastFirstSeenRequestOptions extends Partial { - hostName: string; -} - -export interface HostOverviewRequestOptions extends HostLastFirstSeenRequestOptions { - fields: string[]; - timerange: TimerangeInput; + firstLastSeen = 'firstLastSeen', } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts new file mode 100644 index 0000000000000..8d54481f56dbd --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/overview/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; + +import { HostItem, HostsFields } from '../common'; +import { Inspect, Maybe, RequestOptionsPaginated, TimerangeInput } from '../..'; + +export interface HostOverviewStrategyResponse extends IEsSearchResponse { + hostOverview: HostItem; + inspect?: Maybe; +} + +export interface HostOverviewRequestOptions extends Partial> { + hostName: string; + skip?: boolean; + timerange: TimerangeInput; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index a188eb7619e6b..6905f2be38966 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -4,19 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; +import { IEsSearchRequest, IEsSearchResponse } from '../../../../../../src/plugins/data/common'; import { ESQuery } from '../../typed_json'; import { HostOverviewStrategyResponse, HostOverviewRequestOptions, + HostFirstLastSeenStrategyResponse, + HostFirstLastSeenRequestOptions, HostsQueries, HostsRequestOptions, HostsStrategyResponse, } from './hosts'; +import { + NetworkQueries, + NetworkTlsStrategyResponse, + NetworkTlsRequestOptions, + NetworkHttpStrategyResponse, + NetworkHttpRequestOptions, +} from './network'; + export * from './hosts'; +export * from './network'; export type Maybe = T | null; -export type FactoryQueryTypes = HostsQueries; +export type FactoryQueryTypes = HostsQueries | NetworkQueries; + +export type SearchHit = IEsSearchResponse['rawResponse']['hits']['hits'][0]; + +export interface TotalValue { + value: number; + relation: string; +} export interface Inspect { dsl: string[]; @@ -39,8 +57,8 @@ export enum Direction { desc = 'desc', } -export interface SortField { - field: 'lastSeen' | 'hostName'; +export interface SortField { + field: Field; direction: Direction; } @@ -86,24 +104,41 @@ export interface RequestBasicOptions extends IEsSearchRequest { factoryQueryType?: FactoryQueryTypes; } -export interface RequestOptions extends RequestBasicOptions { +export interface RequestOptions extends RequestBasicOptions { pagination: PaginationInput; - sortField?: SortField; + sort: SortField; } -export interface RequestOptionsPaginated extends RequestBasicOptions { +export interface RequestOptionsPaginated extends RequestBasicOptions { pagination: PaginationInputPaginated; - sortField?: SortField; + sort: SortField; } export type StrategyResponseType = T extends HostsQueries.hosts ? HostsStrategyResponse : T extends HostsQueries.hostOverview ? HostOverviewStrategyResponse + : T extends HostsQueries.firstLastSeen + ? HostFirstLastSeenStrategyResponse + : T extends NetworkQueries.tls + ? NetworkTlsStrategyResponse + : T extends NetworkQueries.http + ? NetworkHttpStrategyResponse : never; export type StrategyRequestType = T extends HostsQueries.hosts ? HostsRequestOptions : T extends HostsQueries.hostOverview ? HostOverviewRequestOptions + : T extends HostsQueries.firstLastSeen + ? HostFirstLastSeenRequestOptions + : T extends NetworkQueries.tls + ? NetworkTlsRequestOptions + : T extends NetworkQueries.http + ? NetworkHttpRequestOptions : never; + +export interface GenericBuckets { + key: string; + doc_count: number; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/http/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/http/index.ts new file mode 100644 index 0000000000000..c42b3d2ab8db3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/http/index.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; +import { + Maybe, + CursorType, + Inspect, + RequestOptionsPaginated, + PageInfoPaginated, + GenericBuckets, +} from '../..'; + +export interface NetworkHttpRequestOptions extends RequestOptionsPaginated { + ip?: string; + defaultIndex: string[]; +} + +export interface NetworkHttpStrategyResponse extends IEsSearchResponse { + edges: NetworkHttpEdges[]; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} + +export interface NetworkHttpEdges { + node: NetworkHttpItem; + cursor: CursorType; +} + +export interface NetworkHttpItem { + _id?: Maybe; + domains: string[]; + lastHost?: Maybe; + lastSourceIp?: Maybe; + methods: string[]; + path?: Maybe; + requestCount?: Maybe; + statuses: string[]; +} + +export interface NetworkHttpBuckets { + key: string; + doc_count: number; + domains: { + buckets: GenericBuckets[]; + }; + methods: { + buckets: GenericBuckets[]; + }; + source: object; + status: { + buckets: GenericBuckets[]; + }; +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/no_match/components/no_match/index.js b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts similarity index 68% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/no_match/components/no_match/index.js rename to x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts index 2cdbc4a7094a8..194bb5d057e3f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/no_match/components/no_match/index.js +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/index.ts @@ -4,4 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export { NoMatch } from './no_match'; +export * from './tls'; +export * from './http'; + +export enum NetworkQueries { + http = 'http', + tls = 'tls', +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts new file mode 100644 index 0000000000000..c9e593bb7a7d2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/network/tls/index.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; +import { CursorType, Inspect, Maybe, PageInfoPaginated, RequestOptionsPaginated } from '../..'; + +export interface TlsBuckets { + key: string; + timestamp?: { + value: number; + value_as_string: string; + }; + subjects: { + buckets: Readonly>; + }; + ja3: { + buckets: Readonly>; + }; + issuers: { + buckets: Readonly>; + }; + not_after: { + buckets: Readonly>; + }; +} + +export interface TlsNode { + _id?: Maybe; + timestamp?: Maybe; + notAfter?: Maybe; + subjects?: Maybe; + ja3?: Maybe; + issuers?: Maybe; +} + +export enum FlowTargetSourceDest { + destination = 'destination', + source = 'source', +} + +export enum TlsFields { + _id = '_id', +} + +export interface TlsEdges { + node: TlsNode; + cursor: CursorType; +} + +export interface NetworkTlsRequestOptions extends RequestOptionsPaginated { + ip: string; + flowTarget: FlowTargetSourceDest; + defaultIndex: string[]; +} + +export interface NetworkTlsStrategyResponse extends IEsSearchResponse { + edges: TlsEdges[]; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts new file mode 100644 index 0000000000000..d8f96aaf5e563 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { exportTimeline, waitForTimelinesPanelToBeLoaded } from '../tasks/timeline'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; + +import { TIMELINES_URL } from '../urls/navigation'; + +const EXPECTED_EXPORTED_TIMELINE_PATH = 'cypress/test_files/expected_timelines_export.ndjson'; + +describe('Export timelines', () => { + before(() => { + esArchiverLoad('timeline'); + cy.server(); + cy.route('POST', '**api/timeline/_export?file_name=timelines_export.ndjson*').as('export'); + }); + + after(() => { + esArchiverUnload('timeline'); + }); + + it('Exports a custom timeline', () => { + loginAndWaitForPageWithoutDateRange(TIMELINES_URL); + waitForTimelinesPanelToBeLoaded(); + + cy.readFile(EXPECTED_EXPORTED_TIMELINE_PATH).then(($expectedExportedJson) => { + const parsedJson = JSON.parse($expectedExportedJson); + const timelineId = parsedJson.savedObjectId; + exportTimeline(timelineId); + + cy.wait('@export').then((response) => { + cy.wrap(response.status).should('eql', 200); + cy.wrap(response.xhr.responseText).should('eql', $expectedExportedJson); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/search_bar.ts b/x-pack/plugins/security_solution/cypress/screens/search_bar.ts index 07e9de137826c..d2838292b04f0 100644 --- a/x-pack/plugins/security_solution/cypress/screens/search_bar.ts +++ b/x-pack/plugins/security_solution/cypress/screens/search_bar.ts @@ -14,7 +14,7 @@ export const ADD_FILTER_FORM_FIELD_INPUT = '[data-test-subj="filterFieldSuggestionList"] input[data-test-subj="comboBoxSearchInput"]'; export const ADD_FILTER_FORM_FIELD_OPTION = (value: string) => - `[data-test-subj="comboBoxOptionsList filterFieldSuggestionList-optionsList"] button[title="${value}"] strong`; + `[data-test-subj="comboBoxOptionsList filterFieldSuggestionList-optionsList"] button[title="${value}"] mark`; export const ADD_FILTER_FORM_OPERATOR_FIELD = '[data-test-subj="filterOperatorList"] input[data-test-subj="comboBoxSearchInput"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 26203a8ca3b83..fd41cd63fc090 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +export const BULK_ACTIONS = '[data-test-subj="utility-bar-action-button"]'; + export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; @@ -11,6 +13,8 @@ export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; export const DRAGGABLE_HEADER = '[data-test-subj="events-viewer-panel"] [data-test-subj="headers-group"] [data-test-subj="draggable-header"]'; +export const EXPORT_TIMELINE_ACTION = '[data-test-subj="export-timeline-action"]'; + export const HEADER = '[data-test-subj="header"]'; export const HEADERS_GROUP = '[data-test-subj="headers-group"]'; @@ -41,6 +45,10 @@ export const TIMELINE = (id: string) => { export const TIMELINE_CHANGES_IN_PROGRESS = '[data-test-subj="timeline"] .euiProgress'; +export const TIMELINE_CHECKBOX = (id: string) => { + return `[data-test-subj="checkboxSelectRow-${id}"]`; +}; + export const TIMELINE_COLUMN_SPINNER = '[data-test-subj="timeline-loading-spinner"]'; export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]'; @@ -70,6 +78,8 @@ export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-gear"]'; export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; +export const TIMELINES_TABLE = '[data-test-subj="timelines-table"]'; + export const TIMESTAMP_HEADER_FIELD = '[data-test-subj="header-text-@timestamp"]'; export const TIMESTAMP_TOGGLE_FIELD = '[data-test-subj="toggle-field-@timestamp"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 08624df06e096..6fb8bb5e29ae5 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -5,8 +5,11 @@ */ import { + BULK_ACTIONS, CLOSE_TIMELINE_BTN, CREATE_NEW_TIMELINE, + EXPORT_TIMELINE_ACTION, + TIMELINE_CHECKBOX, HEADER, ID_FIELD, ID_HEADER_FIELD, @@ -20,6 +23,7 @@ import { TIMELINE_INSPECT_BUTTON, TIMELINE_SETTINGS_ICON, TIMELINE_TITLE, + TIMELINES_TABLE, TIMESTAMP_TOGGLE_FIELD, TOGGLE_TIMELINE_EXPAND_EVENT, REMOVE_COLUMN, @@ -66,6 +70,12 @@ export const expandFirstTimelineEventDetails = () => { cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click({ force: true }); }; +export const exportTimeline = (timelineId: string) => { + cy.get(TIMELINE_CHECKBOX(timelineId)).click({ force: true }); + cy.get(BULK_ACTIONS).click({ force: true }); + cy.get(EXPORT_TIMELINE_ACTION).click(); +}; + export const openTimelineFieldsBrowser = () => { cy.get(TIMELINE_FIELDS_BUTTON).click({ force: true }); }; @@ -122,6 +132,10 @@ export const resetFields = () => { cy.get(RESET_FIELDS).click({ force: true }); }; +export const waitForTimelinesPanelToBeLoaded = () => { + cy.get(TIMELINES_TABLE).should('exist'); +}; + export const waitForTimelineChanges = () => { cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('exist'); cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('not.exist'); diff --git a/x-pack/plugins/security_solution/cypress/test_files/expected_timelines_export.ndjson b/x-pack/plugins/security_solution/cypress/test_files/expected_timelines_export.ndjson new file mode 100644 index 0000000000000..9cca356a8b052 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/test_files/expected_timelines_export.ndjson @@ -0,0 +1 @@ +{"savedObjectId":"0162c130-78be-11ea-9718-118a926974a4","version":"WzcsMV0=","columns":[{"columnHeaderType":"not-filtered","id":"@timestamp"},{"columnHeaderType":"not-filtered","id":"message"},{"columnHeaderType":"not-filtered","id":"event.category"},{"columnHeaderType":"not-filtered","id":"event.action"},{"columnHeaderType":"not-filtered","id":"host.name"},{"columnHeaderType":"not-filtered","id":"source.ip"},{"columnHeaderType":"not-filtered","id":"destination.ip"},{"columnHeaderType":"not-filtered","id":"user.name"}],"created":1586256805054,"createdBy":"elastic","dataProviders":[],"dateRange":{"end":1586256837669,"start":1546343624710},"description":"description","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"host.name:*","kind":"kuery"},"serializedQuery":"{\"bool\":{\"should\":[{\"exists\":{\"field\":\"host.name\"}}],\"minimum_should_match\":1}}"}},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"title":"SIEM test","updated":1586256839298,"updatedBy":"elastic","timelineType":"default","eventNotes":[],"globalNotes":[],"pinnedEventIds":[]} diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index 687099541b3d2..4d2602d1498ee 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -10,7 +10,7 @@ "cypress:open": "cypress open --config-file ./cypress/cypress.json", "cypress:run": "cypress run --browser chrome --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../node_modules/.bin/mochawesome-merge --reportDir ../../../target/kibana-security-solution/cypress/results > ../../../target/kibana-security-solution/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/ && exit $status;", "cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/security_solution_cypress/config.ts", - "test:generate": "ts-node --project scripts/endpoint/cli_tsconfig.json scripts/endpoint/resolver_generator.ts" + "test:generate": "node scripts/endpoint/resolver_generator" }, "devDependencies": { "@types/md5": "^2.2.0", diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index b5e952b0ffa8e..b4e9ba3dd7a71 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -15,6 +15,7 @@ import { EuiErrorBoundary } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { ManageUserInfo } from '../detections/components/user_info'; import { DEFAULT_DARK_MODE, APP_NAME } from '../../common/constants'; import { ErrorToastDispatcher } from '../common/components/error_toast_dispatcher'; import { MlCapabilitiesProvider } from '../common/components/ml/permissions/ml_capabilities_provider'; @@ -28,6 +29,7 @@ import { ManageGlobalTimeline } from '../timelines/components/manage_timeline'; import { StartServices } from '../types'; import { PageRouter } from './routes'; import { ManageSource } from '../common/containers/sourcerer'; + interface StartAppComponent extends AppFrontendLibs { children: React.ReactNode; history: History; @@ -57,7 +59,9 @@ const StartAppComponent: FC = ({ children, apolloClient, hist - {children} + + {children} + diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 7c287646ba7ac..b48ae4e6e2d75 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import styled from 'styled-components'; +import { TimelineId } from '../../../common/types/timeline'; import { DragDropContextWrapper } from '../../common/components/drag_and_drop/drag_drop_context_wrapper'; import { Flyout } from '../../timelines/components/flyout'; import { HeaderGlobal } from '../../common/components/header_global'; @@ -17,6 +18,7 @@ import { useWithSource } from '../../common/containers/source'; import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline'; import { navTabs } from './home_navigations'; import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useUserInfo } from '../../detections/components/user_info'; const SecuritySolutionAppWrapper = styled.div` display: flex; @@ -52,6 +54,9 @@ const HomePageComponent: React.FC = ({ children }) => { const [showTimeline] = useShowTimeline(); const { browserFields, indexPattern, indicesExist } = useWithSource('default', indexToAdd); + // side effect: this will attempt to create the signals index if it doesn't exist + useUserInfo(); + return ( @@ -62,7 +67,7 @@ const HomePageComponent: React.FC = ({ children }) => { {indicesExist && showTimeline && ( <> - + )} diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx index 841a1ef09ede6..00879ace040b9 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx @@ -5,14 +5,12 @@ */ import React, { useEffect, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; import { Filter } from '../../../../../../../src/plugins/data/public'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { StatefulEventsViewer } from '../events_viewer'; import { alertsDefaultModel } from './default_headers'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; -import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers'; import * as i18n from './translations'; import { useKibana } from '../../lib/kibana'; @@ -68,7 +66,6 @@ const AlertsTableComponent: React.FC = ({ startDate, pageFilters = [], }) => { - const dispatch = useDispatch(); const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); const { filterManager } = useKibana().services.data.query; const { initializeTimeline } = useManageTimeline(); @@ -80,12 +77,12 @@ const AlertsTableComponent: React.FC = ({ filterManager, defaultModel: alertsDefaultModel, footerText: i18n.TOTAL_COUNT_OF_ALERTS, - timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId })], title: i18n.ALERTS_TABLE_TITLE, unit: i18n.UNIT, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + return ( o.text === DEFAULT_STACK_BY) ?? alertsStackByOptions[1], errorMessage: i18n.ERROR_FETCHING_ALERTS_DATA, diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx index 633135d63ac33..de9a8b32f1f90 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx @@ -15,7 +15,7 @@ import * as i18n from './translations'; import { useUiSetting$ } from '../../lib/kibana'; import { MatrixHistogramContainer } from '../matrix_histogram'; import { histogramConfigs } from './histogram_configs'; -import { MatrixHisrogramConfigs } from '../matrix_histogram/types'; +import { MatrixHistogramConfigs } from '../matrix_histogram/types'; const ID = 'alertsOverTimeQuery'; export const AlertsView = ({ @@ -38,7 +38,7 @@ export const AlertsView = ({ [] ); const { globalFullScreen } = useFullScreen(); - const alertsHistogramConfigs: MatrixHisrogramConfigs = useMemo( + const alertsHistogramConfigs: MatrixHistogramConfigs = useMemo( () => ({ ...histogramConfigs, subtitle: getSubtitle, 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 b503d63553835..45c20b8903281 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 @@ -110,10 +110,23 @@ export const AreaChartBaseComponent = ({ position={Position.Bottom} showOverlappingTicks={false} tickFormat={xTickFormatter} - tickSize={0} + style={{ + tickLine: { + visible: false, + }, + }} /> - + ) : null; 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 cafb0095431f1..80fc1b4597081 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 @@ -98,11 +98,24 @@ export const BarChartBaseComponent = ({ id={xAxisId} position={Position.Bottom} showOverlappingTicks={false} - tickSize={tickSize} + style={{ + tickLine: { + size: tickSize, + }, + }} tickFormat={xTickFormatter} /> - + ) : null; }; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 6f77d15913d07..833688ae57993 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -50,10 +50,6 @@ const utilityBar = (refetch: inputsModel.Refetch, totalCount: number) => (
); -const exceptionsModal = (refetch: inputsModel.Refetch) => ( -
-); - const eventsViewerDefaultProps = { browserFields: {}, columns: [], @@ -464,42 +460,4 @@ describe('EventsViewer', () => { }); }); }); - - describe('exceptions modal', () => { - test('it renders exception modal if "exceptionsModal" callback exists', async () => { - const wrapper = mount( - - - - - - ); - - await waitFor(() => { - wrapper.update(); - - expect(wrapper.find(`[data-test-subj="mock-exceptions-modal"]`).exists()).toBeTruthy(); - }); - }); - - test('it does not render exception modal if "exceptionModal" callback does not exist', async () => { - const wrapper = mount( - - - - - - ); - - await waitFor(() => { - wrapper.update(); - - expect(wrapper.find(`[data-test-subj="mock-exceptions-modal"]`).exists()).toBeFalsy(); - }); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index ebda64efabf65..3d193856a8ae4 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -109,7 +109,6 @@ interface Props { utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; // If truthy, the graph viewer (Resolver) is showing graphEventId: string | undefined; - exceptionsModal?: (refetch: inputsModel.Refetch) => React.ReactNode; } const EventsViewerComponent: React.FC = ({ @@ -135,7 +134,6 @@ const EventsViewerComponent: React.FC = ({ toggleColumn, utilityBar, graphEventId, - exceptionsModal, }) => { const { globalFullScreen } = useFullScreen(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; @@ -261,7 +259,6 @@ const EventsViewerComponent: React.FC = ({ )} - {exceptionsModal && exceptionsModal(refetch)} {utilityBar && !resolverIsShowing(graphEventId) && ( {utilityBar?.(refetch, totalCountMinusDeleted)} )} @@ -280,6 +277,7 @@ const EventsViewerComponent: React.FC = ({ docValueFields={docValueFields} id={id} isEventViewer={true} + refetch={refetch} sort={sort} toggleColumn={toggleColumn} /> @@ -338,6 +336,5 @@ export const EventsViewer = React.memo( prevProps.start === nextProps.start && prevProps.sort === nextProps.sort && prevProps.utilityBar === nextProps.utilityBar && - prevProps.graphEventId === nextProps.graphEventId && - prevProps.exceptionsModal === nextProps.exceptionsModal + prevProps.graphEventId === nextProps.graphEventId ); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index ec56a3a1bd8d3..e4520dab4626a 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -43,7 +43,6 @@ export interface OwnProps { headerFilterGroup?: React.ReactNode; pageFilters?: Filter[]; utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; - exceptionsModal?: (refetch: inputsModel.Refetch) => React.ReactNode; } type Props = OwnProps & PropsFromRedux; @@ -75,7 +74,6 @@ const StatefulEventsViewerComponent: React.FC = ({ utilityBar, // If truthy, the graph viewer (Resolver) is showing graphEventId, - exceptionsModal, }) => { const [ { docValueFields, browserFields, indexPatterns, isLoading: isLoadingIndexPattern }, @@ -158,7 +156,6 @@ const StatefulEventsViewerComponent: React.FC = ({ toggleColumn={toggleColumn} utilityBar={utilityBar} graphEventId={graphEventId} - exceptionsModal={exceptionsModal} /> @@ -223,7 +220,6 @@ type PropsFromRedux = ConnectedProps; export const StatefulEventsViewer = connector( React.memo( StatefulEventsViewerComponent, - // eslint-disable-next-line complexity (prevProps, nextProps) => prevProps.id === nextProps.id && deepEqual(prevProps.columns, nextProps.columns) && @@ -244,7 +240,6 @@ export const StatefulEventsViewer = connector( prevProps.showCheckboxes === nextProps.showCheckboxes && prevProps.start === nextProps.start && prevProps.utilityBar === nextProps.utilityBar && - prevProps.graphEventId === nextProps.graphEventId && - prevProps.exceptionsModal === nextProps.exceptionsModal + prevProps.graphEventId === nextProps.graphEventId ) ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 21f82c6ab4c98..c46eb1b6b59cc 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -63,7 +63,7 @@ export interface AddExceptionModalBaseProps { export interface AddExceptionModalProps extends AddExceptionModalBaseProps { onCancel: () => void; - onConfirm: (didCloseAlert: boolean) => void; + onConfirm: (didCloseAlert: boolean, didBulkCloseAlert: boolean) => void; onRuleChange?: () => void; alertStatus?: Status; } @@ -137,8 +137,8 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ); const onSuccess = useCallback(() => { addSuccess(i18n.ADD_EXCEPTION_SUCCESS); - onConfirm(shouldCloseAlert); - }, [addSuccess, onConfirm, shouldCloseAlert]); + onConfirm(shouldCloseAlert, shouldBulkCloseAlert); + }, [addSuccess, onConfirm, shouldBulkCloseAlert, shouldCloseAlert]); const [{ isLoading: addExceptionIsLoading }, addOrUpdateExceptionItems] = useAddOrUpdateException( { diff --git a/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx b/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx index 75d396fe384f8..19f1d70e6e230 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_icon/index.tsx @@ -6,7 +6,7 @@ import { EuiIcon, EuiLink, IconSize, IconType } from '@elastic/eui'; import { LinkAnchorProps } from '@elastic/eui/src/components/link/link'; -import React from 'react'; +import React, { ReactNode } from 'react'; import styled, { css } from 'styled-components'; interface LinkProps { @@ -47,7 +47,7 @@ export const Link = styled(({ iconSide, children, ...rest }) => ( Link.displayName = 'Link'; export interface LinkIconProps extends LinkProps { - children: string; + children: string | ReactNode; iconSize?: IconSize; iconType: IconType; dataTestSubj?: string; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index a859b0dd39231..d471b5ae9bed1 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -25,7 +25,7 @@ export interface MatrixHistogramOption { export type GetSubTitle = (count: number) => string; export type GetTitle = (matrixHistogramOption: MatrixHistogramOption) => string; -export interface MatrixHisrogramConfigs { +export interface MatrixHistogramConfigs { defaultStackByOption: MatrixHistogramOption; errorMessage: string; hideHistogramIfEmpty?: boolean; diff --git a/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx b/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx index d626433de1b63..c2ef6b9b192c7 100644 --- a/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/news_feed/no_news/index.tsx @@ -4,24 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLink, EuiText } from '@elastic/eui'; -import React from 'react'; +import { EuiText } from '@elastic/eui'; +import React, { useCallback } from 'react'; import * as i18n from '../translations'; -import { useBasePath } from '../../../lib/kibana'; +import { useKibana } from '../../../lib/kibana'; +import { LinkAnchor } from '../../links'; export const NoNews = React.memo(() => { - const basePath = useBasePath(); + const { getUrlForApp, navigateToApp, capabilities } = useKibana().services.application; + const canSeeAdvancedSettings = capabilities.management.kibana.settings ?? false; + const goToKibanaSettings = useCallback( + () => navigateToApp('management', { path: '/kibana/settings' }), + [navigateToApp] + ); + return ( - <> - - {i18n.NO_NEWS_MESSAGE}{' '} - - {i18n.ADVANCED_SETTINGS_LINK_TITLE} - - {'.'} - - + + {canSeeAdvancedSettings ? i18n.NO_NEWS_MESSAGE_ADMIN : i18n.NO_NEWS_MESSAGE} + {canSeeAdvancedSettings && ( + <> + {' '} + + {i18n.ADVANCED_SETTINGS_LINK_TITLE} + + {'.'} + + )} + ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/news_feed/translations.ts b/x-pack/plugins/security_solution/public/common/components/news_feed/translations.ts index b0f9507266e52..dabaa38178884 100644 --- a/x-pack/plugins/security_solution/public/common/components/news_feed/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/news_feed/translations.ts @@ -7,10 +7,17 @@ import { i18n } from '@kbn/i18n'; export const NO_NEWS_MESSAGE = i18n.translate('xpack.securitySolution.newsFeed.noNewsMessage', { - defaultMessage: - 'Your current news feed URL returned no recent news. You may update the URL or disable security news via', + defaultMessage: 'Your current news feed URL returned no recent news.', }); +export const NO_NEWS_MESSAGE_ADMIN = i18n.translate( + 'xpack.securitySolution.newsFeed.noNewsMessageForAdmin', + { + defaultMessage: + 'Your current news feed URL returned no recent news. You may update the URL or disable security news via', + } +); + export const ADVANCED_SETTINGS_LINK_TITLE = i18n.translate( 'xpack.securitySolution.newsFeed.advancedSettingsLinkTitle', { diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap index 3b8ef69892cc0..9724e36f045e4 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -24,6 +24,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "size": "64px", }, }, + "browserDefaultFontSize": "16px", "euiAnimSlightBounce": "cubic-bezier(0.34, 1.61, 0.7, 1)", "euiAnimSlightResistance": "cubic-bezier(0.694, 0.0482, 0.335, 1)", "euiAnimSpeedExtraFast": "90ms", @@ -434,6 +435,28 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiScrollBarCorner": "6px", "euiSelectableListItemBorder": "1px solid #202128", "euiSelectableListItemPadding": "4px 12px", + "euiSelectableTemplateSitewideTypes": Object { + "application": Object { + "color": "#6092c0", + "font-weight": 500, + }, + "article": Object { + "color": "#9777bc", + "font-weight": 500, + }, + "case": Object { + "color": "#e7664c", + "font-weight": 500, + }, + "deployment": Object { + "color": "#54b399", + "font-weight": 500, + }, + "platform": Object { + "color": "#d6bf57", + "font-weight": 500, + }, + }, "euiShadowColor": "#000000", "euiShadowColorLarge": "#000000", "euiSize": "16px", diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap index b9cb5f3c83d03..f7924f37d2c17 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap @@ -26,6 +26,8 @@ exports[`Modal all errors rendering it renders the default all errors modal when data-test-subj="modal-all-errors-accordion" id="accordion1" initialIsOpen={true} + isLoading={false} + isLoadingMessage={false} key="id-super-id-0" paddingSize="none" > diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts index 5e40cd00fa69e..6052913b4183b 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts @@ -12,6 +12,7 @@ import * as H from 'history'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { url } from '../../../../../../../src/plugins/kibana_utils/public'; +import { TimelineId } from '../../../../common/types/timeline'; import { SecurityPageName } from '../../../app/types'; import { inputsSelectors, State } from '../../store'; import { UrlInputsModel } from '../../store/inputs/model'; @@ -122,7 +123,7 @@ export const makeMapStateToProps = () => { const { linkTo: globalLinkTo, timerange: globalTimerange } = inputState.global; const { linkTo: timelineLinkTo, timerange: timelineTimerange } = inputState.timeline; - const flyoutTimeline = getTimeline(state, 'timeline-1'); + const flyoutTimeline = getTimeline(state, TimelineId.active); const timeline = flyoutTimeline != null ? { diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/histogram_configs.ts b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/histogram_configs.ts index b32919f4868dc..6a05f97da2fef 100644 --- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/histogram_configs.ts +++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/histogram_configs.ts @@ -6,7 +6,7 @@ import * as i18n from './translations'; import { MatrixHistogramOption, - MatrixHisrogramConfigs, + MatrixHistogramConfigs, } from '../../../components/matrix_histogram/types'; import { HistogramType } from '../../../../graphql/types'; @@ -19,7 +19,7 @@ export const anomaliesStackByOptions: MatrixHistogramOption[] = [ const DEFAULT_STACK_BY = i18n.ANOMALIES_STACK_BY_JOB_ID; -export const histogramConfigs: MatrixHisrogramConfigs = { +export const histogramConfigs: MatrixHistogramConfigs = { defaultStackByOption: anomaliesStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? anomaliesStackByOptions[0], errorMessage: i18n.ERROR_FETCHING_ANOMALIES_DATA, diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index ab9f12a67fe89..26013915315af 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -5,7 +5,7 @@ */ import { FilterStateStore } from '../../../../../../src/plugins/data/common/es_query/filters/meta_filter'; -import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; +import { TimelineId, TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; import { @@ -2227,7 +2227,7 @@ export const defaultTimelineProps: CreateTimelineProps = { filters: [], highlightedDropAndProviderId: '', historyIds: [], - id: 'timeline-1', + id: TimelineId.active, isFavorite: false, isLive: false, isLoading: false, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index e8015f601cb18..3f95fd36b6010 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -18,7 +18,7 @@ import { } from '../../../common/mock/'; import { CreateTimeline, UpdateTimelineLoading } from './types'; import { Ecs } from '../../../graphql/types'; -import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; jest.mock('apollo-client'); @@ -67,7 +67,10 @@ describe('alert actions', () => { }); expect(updateTimelineIsLoading).toHaveBeenCalledTimes(1); - expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true }); + expect(updateTimelineIsLoading).toHaveBeenCalledWith({ + id: TimelineId.active, + isLoading: true, + }); }); test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => { @@ -313,9 +316,12 @@ describe('alert actions', () => { updateTimelineIsLoading, }); - expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true }); expect(updateTimelineIsLoading).toHaveBeenCalledWith({ - id: 'timeline-1', + id: TimelineId.active, + isLoading: true, + }); + expect(updateTimelineIsLoading).toHaveBeenCalledWith({ + id: TimelineId.active, isLoading: false, }); expect(createTimeline).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 34c0537a6d7d2..3545bfd91e553 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -10,6 +10,7 @@ import dateMath from '@elastic/datemath'; import { get, getOr, isEmpty, find } from 'lodash/fp'; import moment from 'moment'; +import { TimelineId } from '../../../../common/types/timeline'; import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; import { SendAlertToTimelineActionProps, UpdateAlertStatusActionProps } from './types'; import { @@ -67,7 +68,6 @@ export const getFilterAndRuleBounds = ( export const updateAlertStatusAction = async ({ query, alertIds, - status, selectedStatus, setEventsLoading, setEventsDeleted, @@ -126,7 +126,7 @@ export const getThresholdAggregationDataProvider = ( return [ { and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-${aggregationFieldId}-${dataProviderValue}`, + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`, name: ecsData.signal?.rule?.threshold.field, enabled: true, excluded: false, @@ -155,7 +155,7 @@ export const sendAlertToTimelineAction = async ({ if (timelineId !== '' && apolloClient != null) { try { - updateTimelineIsLoading({ id: 'timeline-1', isLoading: true }); + updateTimelineIsLoading({ id: TimelineId.active, isLoading: true }); const [responseTimeline, eventDataResp] = await Promise.all([ apolloClient.query({ query: oneTimelineQuery, @@ -236,7 +236,7 @@ export const sendAlertToTimelineAction = async ({ } } catch { openAlertInBasicTimeline = true; - updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); + updateTimelineIsLoading({ id: TimelineId.active, isLoading: false }); } } @@ -253,7 +253,7 @@ export const sendAlertToTimelineAction = async ({ dataProviders: [ { and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-${ecsData._id}`, + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${ecsData._id}`, name: ecsData._id, enabled: true, excluded: false, @@ -266,7 +266,7 @@ export const sendAlertToTimelineAction = async ({ }, ...getThresholdAggregationDataProvider(ecsData, nonEcsData), ], - id: 'timeline-1', + id: TimelineId.active, dateRange: { start: from, end: to, @@ -304,7 +304,7 @@ export const sendAlertToTimelineAction = async ({ dataProviders: [ { and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-${ecsData._id}`, + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${ecsData._id}`, name: ecsData._id, enabled: true, excluded: false, @@ -316,7 +316,7 @@ export const sendAlertToTimelineAction = async ({ }, }, ], - id: 'timeline-1', + id: TimelineId.active, dateRange: { start: from, end: to, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index ca17d331c67e5..eebabc59d9324 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -4,44 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import ApolloClient from 'apollo-client'; -import { Dispatch } from 'redux'; - -import { EuiText } from '@elastic/eui'; -import { RuleType } from '../../../../common/detection_engine/types'; -import { isMlRule } from '../../../../common/machine_learning/helpers'; import { RowRendererId } from '../../../../common/types/timeline'; -import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; -import { - TimelineRowAction, - TimelineRowActionOnClick, -} from '../../../timelines/components/timeline/body/actions'; + import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers'; -import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers'; import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH, } from '../../../timelines/components/timeline/body/constants'; -import { DEFAULT_ICON_BUTTON_WIDTH } from '../../../timelines/components/timeline/helpers'; import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { FILTER_OPEN, FILTER_CLOSED, FILTER_IN_PROGRESS } from './alerts_filter_group'; -import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; import * as i18n from './translations'; -import { - CreateTimeline, - SetEventsDeletedProps, - SetEventsLoadingProps, - UpdateTimelineLoading, -} from './types'; -import { Ecs, TimelineNonEcsData } from '../../../graphql/types'; -import { AddExceptionModalBaseProps } from '../../../common/components/exceptions/add_exception_modal'; -import { getMappedNonEcsValue } from '../../../common/components/exceptions/helpers'; -import { isThresholdRule } from '../../../../common/detection_engine/utils'; export const buildAlertStatusFilter = (status: Status): Filter[] => [ { @@ -189,13 +164,16 @@ export const alertsDefaultModel: SubsetTimelineModel = { export const requiredFieldsForActions = [ '@timestamp', + 'signal.status', 'signal.original_time', 'signal.rule.filters', 'signal.rule.from', 'signal.rule.language', 'signal.rule.query', + 'signal.rule.name', 'signal.rule.to', 'signal.rule.id', + 'signal.rule.index', 'signal.rule.type', 'signal.original_event.kind', 'signal.original_event.module', @@ -208,202 +186,3 @@ export const requiredFieldsForActions = [ 'host.os.family', 'event.code', ]; - -interface AlertActionArgs { - apolloClient?: ApolloClient<{}>; - canUserCRUD: boolean; - createTimeline: CreateTimeline; - dispatch: Dispatch; - ecsRowData: Ecs; - nonEcsRowData: TimelineNonEcsData[]; - hasIndexWrite: boolean; - onAlertStatusUpdateFailure: (status: Status, error: Error) => void; - onAlertStatusUpdateSuccess: (count: number, status: Status) => void; - setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; - setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; - status: Status; - timelineId: string; - updateTimelineIsLoading: UpdateTimelineLoading; - openAddExceptionModal: ({ - exceptionListType, - alertData, - ruleName, - ruleId, - }: AddExceptionModalBaseProps) => void; -} - -export const getAlertActions = ({ - apolloClient, - canUserCRUD, - createTimeline, - dispatch, - ecsRowData, - nonEcsRowData, - hasIndexWrite, - onAlertStatusUpdateFailure, - onAlertStatusUpdateSuccess, - setEventsDeleted, - setEventsLoading, - status, - timelineId, - updateTimelineIsLoading, - openAddExceptionModal, -}: AlertActionArgs): TimelineRowAction[] => { - const openAlertActionComponent: TimelineRowAction = { - ariaLabel: 'Open alert', - content: {i18n.ACTION_OPEN_ALERT}, - dataTestSubj: 'open-alert-status', - displayType: 'contextMenu', - id: FILTER_OPEN, - isActionDisabled: () => !canUserCRUD || !hasIndexWrite, - onClick: ({ eventId }: TimelineRowActionOnClick) => - updateAlertStatusAction({ - alertIds: [eventId], - onAlertStatusUpdateFailure, - onAlertStatusUpdateSuccess, - setEventsDeleted, - setEventsLoading, - status, - selectedStatus: FILTER_OPEN, - }), - width: DEFAULT_ICON_BUTTON_WIDTH, - }; - - const closeAlertActionComponent: TimelineRowAction = { - ariaLabel: 'Close alert', - content: {i18n.ACTION_CLOSE_ALERT}, - dataTestSubj: 'close-alert-status', - displayType: 'contextMenu', - id: FILTER_CLOSED, - isActionDisabled: () => !canUserCRUD || !hasIndexWrite, - onClick: ({ eventId }: TimelineRowActionOnClick) => - updateAlertStatusAction({ - alertIds: [eventId], - onAlertStatusUpdateFailure, - onAlertStatusUpdateSuccess, - setEventsDeleted, - setEventsLoading, - status, - selectedStatus: FILTER_CLOSED, - }), - width: DEFAULT_ICON_BUTTON_WIDTH, - }; - - const inProgressAlertActionComponent: TimelineRowAction = { - ariaLabel: 'Mark alert in progress', - content: {i18n.ACTION_IN_PROGRESS_ALERT}, - dataTestSubj: 'in-progress-alert-status', - displayType: 'contextMenu', - id: FILTER_IN_PROGRESS, - isActionDisabled: () => !canUserCRUD || !hasIndexWrite, - onClick: ({ eventId }: TimelineRowActionOnClick) => - updateAlertStatusAction({ - alertIds: [eventId], - onAlertStatusUpdateFailure, - onAlertStatusUpdateSuccess, - setEventsDeleted, - setEventsLoading, - status, - selectedStatus: FILTER_IN_PROGRESS, - }), - width: DEFAULT_ICON_BUTTON_WIDTH, - }; - - const isEndpointAlert = () => { - const [module] = getMappedNonEcsValue({ - data: nonEcsRowData, - fieldName: 'signal.original_event.module', - }); - const [kind] = getMappedNonEcsValue({ - data: nonEcsRowData, - fieldName: 'signal.original_event.kind', - }); - return module === 'endpoint' && kind === 'alert'; - }; - - const exceptionsAreAllowed = () => { - const ruleTypes = getMappedNonEcsValue({ - data: nonEcsRowData, - fieldName: 'signal.rule.type', - }); - const [ruleType] = ruleTypes as RuleType[]; - return !isMlRule(ruleType) && !isThresholdRule(ruleType); - }; - - return [ - { - ...getInvestigateInResolverAction({ dispatch, timelineId }), - }, - { - ariaLabel: 'Send alert to timeline', - content: i18n.ACTION_INVESTIGATE_IN_TIMELINE, - dataTestSubj: 'send-alert-to-timeline', - displayType: 'icon', - iconType: 'timeline', - id: 'sendAlertToTimeline', - onClick: ({ ecsData, data }: TimelineRowActionOnClick) => - sendAlertToTimelineAction({ - apolloClient, - createTimeline, - ecsData, - nonEcsData: data, - updateTimelineIsLoading, - }), - width: DEFAULT_ICON_BUTTON_WIDTH, - }, - // Context menu items - ...(FILTER_OPEN !== status ? [openAlertActionComponent] : []), - ...(FILTER_CLOSED !== status ? [closeAlertActionComponent] : []), - ...(FILTER_IN_PROGRESS !== status ? [inProgressAlertActionComponent] : []), - { - onClick: ({ ecsData, data }: TimelineRowActionOnClick) => { - const [ruleName] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' }); - const [ruleId] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' }); - const ruleIndices = getMappedNonEcsValue({ data, fieldName: 'signal.rule.index' }); - if (ruleId !== undefined) { - openAddExceptionModal({ - ruleName: ruleName ?? '', - ruleId, - ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN, - exceptionListType: 'endpoint', - alertData: { - ecsData, - nonEcsData: data, - }, - }); - } - }, - id: 'addEndpointException', - isActionDisabled: () => !canUserCRUD || !hasIndexWrite || !isEndpointAlert(), - dataTestSubj: 'add-endpoint-exception-menu-item', - ariaLabel: 'Add Endpoint Exception', - content: {i18n.ACTION_ADD_ENDPOINT_EXCEPTION}, - displayType: 'contextMenu', - }, - { - onClick: ({ ecsData, data }: TimelineRowActionOnClick) => { - const [ruleName] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' }); - const [ruleId] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' }); - const ruleIndices = getMappedNonEcsValue({ data, fieldName: 'signal.rule.index' }); - if (ruleId !== undefined) { - openAddExceptionModal({ - ruleName: ruleName ?? '', - ruleId, - ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN, - exceptionListType: 'detection', - alertData: { - ecsData, - nonEcsData: data, - }, - }); - } - }, - id: 'addException', - isActionDisabled: () => !canUserCRUD || !hasIndexWrite || !exceptionsAreAllowed(), - dataTestSubj: 'add-exception-menu-item', - ariaLabel: 'Add Exception', - content: {i18n.ACTION_ADD_EXCEPTION}, - displayType: 'contextMenu', - }, - ]; -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index d5688d84e9759..be24957602037 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -40,8 +40,6 @@ describe('AlertsTableComponent', () => { clearEventsDeleted={jest.fn()} showBuildingBlockAlerts={false} onShowBuildingBlockAlertsChanged={jest.fn()} - updateTimelineIsLoading={jest.fn()} - updateTimeline={jest.fn()} /> ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 854565ace9b4b..63e1c8aca9082 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -7,7 +7,7 @@ import { EuiPanel, EuiLoadingContent } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { connect, ConnectedProps, useDispatch } from 'react-redux'; +import { connect, ConnectedProps } from 'react-redux'; import { Dispatch } from 'redux'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; @@ -22,15 +22,10 @@ import { inputsSelectors, State, inputsModel } from '../../../common/store'; import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { - useManageTimeline, - TimelineRowActionArgs, -} from '../../../timelines/components/manage_timeline'; -import { useApolloClient } from '../../../common/utils/apollo_context'; +import { useManageTimeline } from '../../../timelines/components/manage_timeline'; import { updateAlertStatusAction } from './actions'; import { - getAlertActions, requiredFieldsForActions, alertsDefaultModel, buildAlertStatusFilter, @@ -39,23 +34,16 @@ import { FILTER_OPEN, AlertsTableFilterGroup } from './alerts_filter_group'; import { AlertsUtilityBar } from './alerts_utility_bar'; import * as i18n from './translations'; import { - CreateTimelineProps, SetEventsDeletedProps, SetEventsLoadingProps, UpdateAlertsStatusCallback, UpdateAlertsStatusProps, } from './types'; -import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; import { useStateToaster, displaySuccessToast, displayErrorToast, } from '../../../common/components/toasters'; -import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers'; -import { - AddExceptionModal, - AddExceptionModalBaseProps, -} from '../../../common/components/exceptions/add_exception_modal'; interface OwnProps { timelineId: TimelineIdLiteral; @@ -72,14 +60,6 @@ interface OwnProps { type AlertsTableComponentProps = OwnProps & PropsFromRedux; -const addExceptionModalInitialState: AddExceptionModalBaseProps = { - ruleName: '', - ruleId: '', - ruleIndices: [], - exceptionListType: 'detection', - alertData: undefined, -}; - export const AlertsTableComponent: React.FC = ({ timelineId, canUserCRUD, @@ -101,30 +81,16 @@ export const AlertsTableComponent: React.FC = ({ onShowBuildingBlockAlertsChanged, signalsIndex, to, - updateTimeline, - updateTimelineIsLoading, }) => { - const dispatch = useDispatch(); - const apolloClient = useApolloClient(); - const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - const [shouldShowAddExceptionModal, setShouldShowAddExceptionModal] = useState(false); - const [addExceptionModalState, setAddExceptionModalState] = useState( - addExceptionModalInitialState - ); const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns( signalsIndex !== '' ? [signalsIndex] : [], 'alerts_table' ); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); - const { - initializeTimeline, - setSelectAll, - setTimelineRowActions, - setIndexToAdd, - } = useManageTimeline(); + const { initializeTimeline, setSelectAll, setIndexToAdd } = useManageTimeline(); const getGlobalQuery = useCallback( (customFilters: Filter[]) => { @@ -149,27 +115,6 @@ export const AlertsTableComponent: React.FC = ({ [browserFields, defaultFilters, globalFilters, globalQuery, indexPatterns, kibana, to, from] ); - // Callback for creating a new timeline -- utilized by row/batch actions - const createTimelineCallback = useCallback( - ({ from: fromTimeline, timeline, to: toTimeline, ruleNote, notes }: CreateTimelineProps) => { - updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); - updateTimeline({ - duplicate: true, - forceNotes: true, - from: fromTimeline, - id: 'timeline-1', - notes, - timeline: { - ...timeline, - show: true, - }, - to: toTimeline, - ruleNote, - })(); - }, - [updateTimeline, updateTimelineIsLoading] - ); - const setEventsLoadingCallback = useCallback( ({ eventIds, isLoading }: SetEventsLoadingProps) => { setEventsLoading!({ id: timelineId, eventIds, isLoading }); @@ -220,28 +165,6 @@ export const AlertsTableComponent: React.FC = ({ [dispatchToaster] ); - const openAddExceptionModalCallback = useCallback( - ({ - ruleName, - ruleIndices, - ruleId, - exceptionListType, - alertData, - }: AddExceptionModalBaseProps) => { - if (alertData != null) { - setShouldShowAddExceptionModal(true); - setAddExceptionModalState({ - ruleName, - ruleId, - ruleIndices, - exceptionListType, - alertData, - }); - } - }, - [setShouldShowAddExceptionModal, setAddExceptionModalState] - ); - // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar useEffect(() => { if (isSelectAllChecked) { @@ -297,7 +220,6 @@ export const AlertsTableComponent: React.FC = ({ ? getGlobalQuery(currentStatusFilter)?.filterQuery : undefined, alertIds: Object.keys(selectedEventIds), - status, selectedStatus, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, @@ -352,42 +274,6 @@ export const AlertsTableComponent: React.FC = ({ ] ); - // Send to Timeline / Update Alert Status Actions for each table row - const additionalActions = useMemo( - () => ({ ecsData, nonEcsData }: TimelineRowActionArgs) => - getAlertActions({ - apolloClient, - canUserCRUD, - createTimeline: createTimelineCallback, - ecsRowData: ecsData, - nonEcsRowData: nonEcsData, - dispatch, - hasIndexWrite, - onAlertStatusUpdateFailure, - onAlertStatusUpdateSuccess, - setEventsDeleted: setEventsDeletedCallback, - setEventsLoading: setEventsLoadingCallback, - status: filterGroup, - timelineId, - updateTimelineIsLoading, - openAddExceptionModal: openAddExceptionModalCallback, - }), - [ - apolloClient, - canUserCRUD, - createTimelineCallback, - dispatch, - hasIndexWrite, - filterGroup, - setEventsLoadingCallback, - setEventsDeletedCallback, - timelineId, - updateTimelineIsLoading, - onAlertStatusUpdateSuccess, - onAlertStatusUpdateFailure, - openAddExceptionModalCallback, - ] - ); const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo(() => { if (isEmpty(defaultFilters)) { @@ -408,21 +294,12 @@ export const AlertsTableComponent: React.FC = ({ indexToAdd: defaultIndices, loadingText: i18n.LOADING_ALERTS, selectAll: false, - timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId })], + queryFields: requiredFieldsForActions, title: '', }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - setTimelineRowActions({ - id: timelineId, - queryFields: requiredFieldsForActions, - timelineRowActions: additionalActions, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [additionalActions]); - useEffect(() => { setIndexToAdd({ id: timelineId, indexToAdd: defaultIndices }); }, [timelineId, defaultIndices, setIndexToAdd]); @@ -432,53 +309,6 @@ export const AlertsTableComponent: React.FC = ({ [onFilterGroupChangedCallback] ); - const closeAddExceptionModal = useCallback(() => { - setShouldShowAddExceptionModal(false); - setAddExceptionModalState(addExceptionModalInitialState); - }, [setShouldShowAddExceptionModal, setAddExceptionModalState]); - - const onAddExceptionCancel = useCallback(() => { - closeAddExceptionModal(); - }, [closeAddExceptionModal]); - - const onAddExceptionConfirm = useCallback( - (refetch: inputsModel.Refetch) => (): void => { - refetch(); - closeAddExceptionModal(); - }, - [closeAddExceptionModal] - ); - - // Callback for creating the AddExceptionModal and allowing it - // access to the refetchQuery to update the page - const exceptionModalCallback = useCallback( - (refetchQuery: inputsModel.Refetch) => { - if (shouldShowAddExceptionModal) { - return ( - - ); - } else { - return <>; - } - }, - [ - addExceptionModalState, - filterGroup, - onAddExceptionCancel, - onAddExceptionConfirm, - shouldShowAddExceptionModal, - ] - ); - if (loading || indexPatternsLoading || isEmpty(signalsIndex)) { return ( @@ -489,19 +319,16 @@ export const AlertsTableComponent: React.FC = ({ } return ( - <> - - + ); }; @@ -551,9 +378,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ }) => dispatch(timelineActions.setEventsDeleted({ id, eventIds, isDeleted })), clearEventsDeleted: ({ id }: { id: string }) => dispatch(timelineActions.clearEventsDeleted({ id })), - updateTimelineIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => - dispatch(timelineActions.updateIsLoading({ id, isLoading })), - updateTimeline: dispatchUpdateTimeline(dispatch), }); const connector = connect(makeMapStateToProps, mapDispatchToProps); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx new file mode 100644 index 0000000000000..589116c901c30 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -0,0 +1,484 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { + EuiText, + EuiButtonIcon, + EuiContextMenuPanel, + EuiPopover, + EuiContextMenuItem, +} from '@elastic/eui'; +import styled from 'styled-components'; + +import { TimelineId } from '../../../../../common/types/timeline'; +import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; +import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { isThresholdRule } from '../../../../../common/detection_engine/utils'; +import { RuleType } from '../../../../../common/detection_engine/types'; +import { isMlRule } from '../../../../../common/machine_learning/helpers'; +import { timelineActions } from '../../../../timelines/store/timeline'; +import { EventsTd, EventsTdContent } from '../../../../timelines/components/timeline/styles'; +import { DEFAULT_ICON_BUTTON_WIDTH } from '../../../../timelines/components/timeline/helpers'; +import { FILTER_OPEN, FILTER_CLOSED, FILTER_IN_PROGRESS } from '../alerts_filter_group'; +import { updateAlertStatusAction } from '../actions'; +import { SetEventsDeletedProps, SetEventsLoadingProps } from '../types'; +import { Ecs, TimelineNonEcsData } from '../../../../graphql/types'; +import { + AddExceptionModal as AddExceptionModalComponent, + AddExceptionModalBaseProps, +} from '../../../../common/components/exceptions/add_exception_modal'; +import { getMappedNonEcsValue } from '../../../../common/components/exceptions/helpers'; +import * as i18n from '../translations'; +import { + useStateToaster, + displaySuccessToast, + displayErrorToast, +} from '../../../../common/components/toasters'; +import { inputsModel } from '../../../../common/store'; +import { useUserData } from '../../user_info'; + +interface AlertContextMenuProps { + disabled: boolean; + ecsRowData: Ecs; + nonEcsRowData: TimelineNonEcsData[]; + refetch: inputsModel.Refetch; + timelineId: string; +} + +const addExceptionModalInitialState: AddExceptionModalBaseProps = { + ruleName: '', + ruleId: '', + ruleIndices: [], + exceptionListType: 'detection', + alertData: undefined, +}; + +const AlertContextMenuComponent: React.FC = ({ + disabled, + ecsRowData, + nonEcsRowData, + refetch, + timelineId, +}) => { + const dispatch = useDispatch(); + const [, dispatchToaster] = useStateToaster(); + const [isPopoverOpen, setPopover] = useState(false); + const [alertStatus, setAlertStatus] = useState( + (ecsRowData.signal?.status && (ecsRowData.signal.status[0] as Status)) ?? undefined + ); + const eventId = ecsRowData._id; + + const onButtonClick = useCallback(() => { + setPopover(!isPopoverOpen); + }, [isPopoverOpen]); + + const closePopover = useCallback(() => { + setPopover(false); + }, []); + const [shouldShowAddExceptionModal, setShouldShowAddExceptionModal] = useState(false); + const [addExceptionModalState, setAddExceptionModalState] = useState( + addExceptionModalInitialState + ); + const [{ canUserCRUD, hasIndexWrite }] = useUserData(); + + const isEndpointAlert = useMemo(() => { + if (!nonEcsRowData) { + return false; + } + + const [module] = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.original_event.module', + }); + const [kind] = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.original_event.kind', + }); + return module === 'endpoint' && kind === 'alert'; + }, [nonEcsRowData]); + + const closeAddExceptionModal = useCallback(() => { + setShouldShowAddExceptionModal(false); + setAddExceptionModalState(addExceptionModalInitialState); + }, [setShouldShowAddExceptionModal, setAddExceptionModalState]); + + const onAddExceptionCancel = useCallback(() => { + closeAddExceptionModal(); + }, [closeAddExceptionModal]); + + const onAddExceptionConfirm = useCallback( + (didCloseAlert: boolean, didBulkCloseAlert) => { + closeAddExceptionModal(); + if (didCloseAlert) { + setAlertStatus('closed'); + } + if (timelineId !== TimelineId.active || didBulkCloseAlert) { + refetch(); + } + }, + [closeAddExceptionModal, timelineId, refetch] + ); + + const onAlertStatusUpdateSuccess = useCallback( + (count: number, newStatus: Status) => { + let title: string; + switch (newStatus) { + case 'closed': + title = i18n.CLOSED_ALERT_SUCCESS_TOAST(count); + break; + case 'open': + title = i18n.OPENED_ALERT_SUCCESS_TOAST(count); + break; + case 'in-progress': + title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(count); + } + displaySuccessToast(title, dispatchToaster); + setAlertStatus(newStatus); + }, + [dispatchToaster] + ); + + const onAlertStatusUpdateFailure = useCallback( + (newStatus: Status, error: Error) => { + let title: string; + switch (newStatus) { + case 'closed': + title = i18n.CLOSED_ALERT_FAILED_TOAST; + break; + case 'open': + title = i18n.OPENED_ALERT_FAILED_TOAST; + break; + case 'in-progress': + title = i18n.IN_PROGRESS_ALERT_FAILED_TOAST; + } + displayErrorToast(title, [error.message], dispatchToaster); + }, + [dispatchToaster] + ); + + const setEventsLoading = useCallback( + ({ eventIds, isLoading }: SetEventsLoadingProps) => { + dispatch(timelineActions.setEventsLoading({ id: timelineId, eventIds, isLoading })); + }, + [dispatch, timelineId] + ); + + const setEventsDeleted = useCallback( + ({ eventIds, isDeleted }: SetEventsDeletedProps) => { + dispatch(timelineActions.setEventsDeleted({ id: timelineId, eventIds, isDeleted })); + }, + [dispatch, timelineId] + ); + + const openAlertActionOnClick = useCallback(() => { + updateAlertStatusAction({ + alertIds: [eventId], + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + selectedStatus: FILTER_OPEN, + }); + closePopover(); + }, [ + closePopover, + eventId, + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + ]); + + const openAlertActionComponent = ( + + {i18n.ACTION_OPEN_ALERT} + + ); + + const closeAlertActionClick = useCallback(() => { + updateAlertStatusAction({ + alertIds: [eventId], + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + selectedStatus: FILTER_CLOSED, + }); + closePopover(); + }, [ + closePopover, + eventId, + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + ]); + + const closeAlertActionComponent = ( + + {i18n.ACTION_CLOSE_ALERT} + + ); + + const inProgressAlertActionClick = useCallback(() => { + updateAlertStatusAction({ + alertIds: [eventId], + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + selectedStatus: FILTER_IN_PROGRESS, + }); + closePopover(); + }, [ + closePopover, + eventId, + onAlertStatusUpdateFailure, + onAlertStatusUpdateSuccess, + setEventsDeleted, + setEventsLoading, + ]); + + const inProgressAlertActionComponent = ( + + {i18n.ACTION_IN_PROGRESS_ALERT} + + ); + + const openAddExceptionModal = useCallback( + ({ + ruleName, + ruleIndices, + ruleId, + exceptionListType, + alertData, + }: AddExceptionModalBaseProps) => { + if (alertData !== null && alertData !== undefined) { + setShouldShowAddExceptionModal(true); + setAddExceptionModalState({ + ruleName, + ruleId, + ruleIndices, + exceptionListType, + alertData, + }); + } + }, + [setShouldShowAddExceptionModal, setAddExceptionModalState] + ); + + const AddExceptionModal = useCallback( + () => + shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null ? ( + + ) : null, + [ + shouldShowAddExceptionModal, + addExceptionModalState.alertData, + addExceptionModalState.ruleName, + addExceptionModalState.ruleId, + addExceptionModalState.ruleIndices, + addExceptionModalState.exceptionListType, + onAddExceptionCancel, + onAddExceptionConfirm, + alertStatus, + ] + ); + + const button = ( + + ); + + const handleAddEndpointExceptionClick = useCallback(() => { + const [ruleName] = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.name', + }); + const [ruleId] = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.id', + }); + const ruleIndices = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.index', + }); + + closePopover(); + + if (ruleId !== undefined) { + openAddExceptionModal({ + ruleName: ruleName ?? '', + ruleId, + ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN, + exceptionListType: 'endpoint', + alertData: { + ecsData: ecsRowData, + nonEcsData: nonEcsRowData, + }, + }); + } + }, [closePopover, ecsRowData, nonEcsRowData, openAddExceptionModal]); + + const addEndpointExceptionComponent = ( + + {i18n.ACTION_ADD_ENDPOINT_EXCEPTION} + + ); + + const handleAddExceptionClick = useCallback(() => { + const [ruleName] = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.name', + }); + const [ruleId] = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.id', + }); + const ruleIndices = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.index', + }); + + closePopover(); + + if (ruleId !== undefined) { + openAddExceptionModal({ + ruleName: ruleName ?? '', + ruleId, + ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN, + exceptionListType: 'detection', + alertData: { + ecsData: ecsRowData, + nonEcsData: nonEcsRowData, + }, + }); + } + }, [closePopover, ecsRowData, nonEcsRowData, openAddExceptionModal]); + + const areExceptionsAllowed = useMemo(() => { + const ruleTypes = getMappedNonEcsValue({ + data: nonEcsRowData, + fieldName: 'signal.rule.type', + }); + const [ruleType] = ruleTypes as RuleType[]; + return !isMlRule(ruleType) && !isThresholdRule(ruleType); + }, [nonEcsRowData]); + + const addExceptionComponent = ( + + {i18n.ACTION_ADD_EXCEPTION} + + ); + + const statusFilters = useMemo(() => { + if (!alertStatus) { + return []; + } + + switch (alertStatus) { + case 'open': + return [inProgressAlertActionComponent, closeAlertActionComponent]; + case 'in-progress': + return [openAlertActionComponent, closeAlertActionComponent]; + case 'closed': + return [openAlertActionComponent, inProgressAlertActionComponent]; + default: + return []; + } + }, [ + alertStatus, + closeAlertActionComponent, + inProgressAlertActionComponent, + openAlertActionComponent, + ]); + + const items = useMemo( + () => [...statusFilters, addEndpointExceptionComponent, addExceptionComponent], + [addEndpointExceptionComponent, addExceptionComponent, statusFilters] + ); + + return ( + <> + + + + + + + + + + ); +}; + +const ContextMenuPanel = styled(EuiContextMenuPanel)` + font-size: ${({ theme }) => theme.eui.euiFontSizeS}; +`; + +ContextMenuPanel.displayName = 'ContextMenuPanel'; + +export const AlertContextMenu = React.memo(AlertContextMenuComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx new file mode 100644 index 0000000000000..f4080de5b4ba1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; + +import { TimelineId } from '../../../../../common/types/timeline'; +import { TimelineNonEcsData, Ecs } from '../../../../../public/graphql/types'; +import { timelineActions } from '../../../../timelines/store/timeline'; +import { useApolloClient } from '../../../../common/utils/apollo_context'; +import { sendAlertToTimelineAction } from '../actions'; +import { dispatchUpdateTimeline } from '../../../../timelines/components/open_timeline/helpers'; +import { ActionIconItem } from '../../../../timelines/components/timeline/body/actions/action_icon_item'; + +import { CreateTimelineProps } from '../types'; +import { + ACTION_INVESTIGATE_IN_TIMELINE, + ACTION_INVESTIGATE_IN_TIMELINE_ARIA_LABEL, +} from '../translations'; + +interface InvestigateInTimelineActionProps { + ecsRowData: Ecs; + nonEcsRowData: TimelineNonEcsData[]; +} + +const InvestigateInTimelineActionComponent: React.FC = ({ + ecsRowData, + nonEcsRowData, +}) => { + const dispatch = useDispatch(); + const apolloClient = useApolloClient(); + + const updateTimelineIsLoading = useCallback( + (payload) => dispatch(timelineActions.updateIsLoading(payload)), + [dispatch] + ); + + const createTimeline = useCallback( + ({ from: fromTimeline, timeline, to: toTimeline, ruleNote }: CreateTimelineProps) => { + updateTimelineIsLoading({ id: TimelineId.active, isLoading: false }); + dispatchUpdateTimeline(dispatch)({ + duplicate: true, + from: fromTimeline, + id: TimelineId.active, + notes: [], + timeline: { + ...timeline, + show: true, + }, + to: toTimeline, + ruleNote, + })(); + }, + [dispatch, updateTimelineIsLoading] + ); + + const investigateInTimelineAlertClick = useCallback( + () => + sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: ecsRowData, + nonEcsData: nonEcsRowData, + updateTimelineIsLoading, + }), + [apolloClient, createTimeline, ecsRowData, nonEcsRowData, updateTimelineIsLoading] + ); + + return ( + + ); +}; + +export const InvestigateInTimelineAction = React.memo(InvestigateInTimelineActionComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 3d6c3dc0a7a8e..b4da0267d2ea5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -115,6 +115,13 @@ export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( } ); +export const ACTION_INVESTIGATE_IN_TIMELINE_ARIA_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineAriaLabel', + { + defaultMessage: 'Send alert to timeline', + } +); + export const ACTION_ADD_EXCEPTION = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.actions.addException', { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts index 2e77e77f6b3d5..d8ba0ab2d40b9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -41,7 +41,6 @@ export type UpdateAlertsStatus = ({ export interface UpdateAlertStatusActionProps { query?: string; alertIds: string[]; - status: Status; selectedStatus: Status; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx index 50348578cb039..e1a29c3575d95 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx @@ -121,7 +121,7 @@ export const userInfoReducer = (state: State, action: Action): State => { const StateUserInfoContext = createContext<[State, Dispatch]>([initialState, () => noop]); -const useUserData = () => useContext(StateUserInfoContext); +export const useUserData = () => useContext(StateUserInfoContext); interface ManageUserInfoProps { children: React.ReactNode; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 982712cbe9797..8c21f6a1e8cb7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -19,7 +19,7 @@ import { } from '../../../common/mock'; import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { DetectionEnginePageComponent } from './detection_engine'; -import { useUserInfo } from '../../components/user_info'; +import { useUserData } from '../../components/user_info'; import { useWithSource } from '../../../common/containers/source'; import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../cases/components/__mock__/router'; @@ -73,7 +73,7 @@ const store = createStore( describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); - (useUserInfo as jest.Mock).mockReturnValue({}); + (useUserData as jest.Mock).mockReturnValue([{}]); (useWithSource as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index d76da592e1c81..3a3854f145db3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -30,7 +30,7 @@ import { NoApiIntegrationKeyCallOut } from '../../components/no_api_integration_ import { NoWriteAlertsCallOut } from '../../components/no_write_alerts_callout'; import { AlertsHistogramPanel } from '../../components/alerts_histogram_panel'; import { alertsHistogramOptions } from '../../components/alerts_histogram_panel/config'; -import { useUserInfo } from '../../components/user_info'; +import { useUserData } from '../../components/user_info'; import { OverviewEmpty } from '../../../overview/components/overview_empty'; import { DetectionEngineNoIndex } from './detection_engine_no_index'; import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; @@ -55,15 +55,17 @@ export const DetectionEnginePageComponent: React.FC = ({ }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(); const { globalFullScreen } = useFullScreen(); - const { - loading: userInfoLoading, - isSignalIndexExists, - isAuthenticated: isUserAuthenticated, - hasEncryptionKey, - canUserCRUD, - signalIndexName, - hasIndexWrite, - } = useUserInfo(); + const [ + { + loading: userInfoLoading, + isSignalIndexExists, + isAuthenticated: isUserAuthenticated, + hasEncryptionKey, + canUserCRUD, + signalIndexName, + hasIndexWrite, + }, + ] = useUserData(); const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx index d4e654321ef98..045e7d402fd2b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx @@ -12,8 +12,8 @@ import { DetectionEngineContainer } from './index'; describe('DetectionEngineContainer', () => { it('renders correctly', () => { - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find('ManageUserInfo')).toHaveLength(1); + expect(wrapper.find('Switch')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx index 914734aba4ec6..5f379f7dbb70e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx @@ -5,37 +5,32 @@ */ import React from 'react'; -import { Route, Switch, RouteComponentProps } from 'react-router-dom'; +import { Route, Switch } from 'react-router-dom'; -import { ManageUserInfo } from '../../components/user_info'; import { CreateRulePage } from './rules/create'; import { DetectionEnginePage } from './detection_engine'; import { EditRulePage } from './rules/edit'; import { RuleDetailsPage } from './rules/details'; import { RulesPage } from './rules'; -type Props = Partial> & { url: string }; - -const DetectionEngineContainerComponent: React.FC = () => ( - - - - - - - - - - - - - - - - - - - +const DetectionEngineContainerComponent: React.FC = () => ( + + + + + + + + + + + + + + + + + ); export const DetectionEngineContainer = React.memo(DetectionEngineContainerComponent); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx index 50407c5eb219b..deffee5a56d46 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx @@ -10,7 +10,7 @@ import { shallow } from 'enzyme'; import '../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../common/mock'; import { CreateRulePage } from './index'; -import { useUserInfo } from '../../../../components/user_info'; +import { useUserData } from '../../../../components/user_info'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -29,7 +29,7 @@ jest.mock('../../../../components/user_info'); describe('CreateRulePage', () => { it('renders correctly', () => { - (useUserInfo as jest.Mock).mockReturnValue({}); + (useUserData as jest.Mock).mockReturnValue([{}]); const wrapper = shallow(, { wrappingComponent: TestProviders }); expect(wrapper.find('[title="Create new rule"]')).toHaveLength(1); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx index 70f278197b005..d2eb3228cbbf3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx @@ -19,7 +19,7 @@ import { import { WrapperPage } from '../../../../../common/components/wrapper_page'; import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; -import { useUserInfo } from '../../../../components/user_info'; +import { useUserData } from '../../../../components/user_info'; import { AccordionTitle } from '../../../../components/rules/accordion_title'; import { FormData, FormHook } from '../../../../../shared_imports'; import { StepAboutRule } from '../../../../components/rules/step_about_rule'; @@ -84,13 +84,15 @@ const StepDefineRuleAccordion: StyledComponent< StepDefineRuleAccordion.displayName = 'StepDefineRuleAccordion'; const CreateRulePageComponent: React.FC = () => { - const { - loading: userInfoLoading, - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - canUserCRUD, - } = useUserInfo(); + const [ + { + loading: userInfoLoading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + }, + ] = useUserData(); const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx index 5e6587dab1736..f8f9da78b2a06 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx @@ -19,7 +19,7 @@ import { import { RuleDetailsPageComponent } from './index'; import { createStore, State } from '../../../../../common/store'; import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions'; -import { useUserInfo } from '../../../../components/user_info'; +import { useUserData } from '../../../../components/user_info'; import { useWithSource } from '../../../../../common/containers/source'; import { useParams } from 'react-router-dom'; import { mockHistory, Router } from '../../../../../cases/components/__mock__/router'; @@ -69,7 +69,7 @@ const store = createStore( describe('RuleDetailsPageComponent', () => { beforeAll(() => { - (useUserInfo as jest.Mock).mockReturnValue({}); + (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); (useWithSource as jest.Mock).mockReturnValue({ indicesExist: true, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index f48dc64966bfc..2988e031c4dd6 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -44,7 +44,7 @@ import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_ab import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel'; import { AlertsTable } from '../../../../components/alerts_table'; -import { useUserInfo } from '../../../../components/user_info'; +import { useUserData } from '../../../../components/user_info'; import { OverviewEmpty } from '../../../../../overview/components/overview_empty'; import { useAlertInfo } from '../../../../components/alerts_info'; import { StepDefineRule } from '../../../../components/rules/step_define_rule'; @@ -124,15 +124,17 @@ export const RuleDetailsPageComponent: FC = ({ setAbsoluteRangeDatePicker, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(); - const { - loading: userInfoLoading, - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - canUserCRUD, - hasIndexWrite, - signalIndexName, - } = useUserInfo(); + const [ + { + loading: userInfoLoading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + hasIndexWrite, + signalIndexName, + }, + ] = useUserData(); const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx index 2e45dbc6521b7..e89c899b12c39 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx @@ -10,7 +10,7 @@ import { shallow } from 'enzyme'; import '../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../common/mock'; import { EditRulePage } from './index'; -import { useUserInfo } from '../../../../components/user_info'; +import { useUserData } from '../../../../components/user_info'; import { useParams } from 'react-router-dom'; jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); @@ -28,7 +28,7 @@ jest.mock('react-router-dom', () => { describe('EditRulePage', () => { it('renders correctly', () => { - (useUserInfo as jest.Mock).mockReturnValue({}); + (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); const wrapper = shallow(, { wrappingComponent: TestProviders }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index 4033d247c4ecb..530222ee19624 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -26,7 +26,7 @@ import { } from '../../../../../common/components/link_to/redirect_to_detection_engine'; import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; -import { useUserInfo } from '../../../../components/user_info'; +import { useUserData } from '../../../../components/user_info'; import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; import { FormHook, FormData } from '../../../../../shared_imports'; import { StepPanel } from '../../../../components/rules/step_panel'; @@ -72,13 +72,15 @@ interface ActionsStepRuleForm extends StepRuleForm { const EditRulePageComponent: FC = () => { const history = useHistory(); const [, dispatchToaster] = useStateToaster(); - const { - loading: userInfoLoading, - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - canUserCRUD, - } = useUserInfo(); + const [ + { + loading: userInfoLoading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + }, + ] = useUserData(); const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx index 95ef85ec1317a..886a24dd7cbe8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx @@ -9,7 +9,7 @@ import { shallow } from 'enzyme'; import '../../../../common/mock/match_media'; import { RulesPage } from './index'; -import { useUserInfo } from '../../../components/user_info'; +import { useUserData } from '../../../components/user_info'; import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; jest.mock('react-router-dom', () => { @@ -30,7 +30,7 @@ jest.mock('../../../containers/detection_engine/rules'); describe('RulesPage', () => { beforeAll(() => { - (useUserInfo as jest.Mock).mockReturnValue({}); + (useUserData as jest.Mock).mockReturnValue([{}]); (usePrePackagedRules as jest.Mock).mockReturnValue({}); }); it('renders correctly', () => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx index 92ec0bb5a72cd..53c82569f94ae 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx @@ -18,7 +18,7 @@ import { DetectionEngineHeaderPage } from '../../../components/detection_engine_ import { WrapperPage } from '../../../../common/components/wrapper_page'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; -import { useUserInfo } from '../../../components/user_info'; +import { useUserData } from '../../../components/user_info'; import { AllRules } from './all'; import { ImportDataModal } from '../../../../common/components/import_data_modal'; import { ReadOnlyCallOut } from '../../../components/rules/read_only_callout'; @@ -42,14 +42,16 @@ const RulesPageComponent: React.FC = () => { const [showImportModal, setShowImportModal] = useState(false); const [showValueListsModal, setShowValueListsModal] = useState(false); const refreshRulesData = useRef(null); - const { - loading: userInfoLoading, - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - canUserCRUD, - hasIndexWrite, - } = useUserInfo(); + const [ + { + loading: userInfoLoading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + hasIndexWrite, + }, + ] = useUserData(); const { loading: listsConfigLoading, canWriteIndex: canWriteListsIndex, diff --git a/x-pack/plugins/security_solution/public/detections/routes.tsx b/x-pack/plugins/security_solution/public/detections/routes.tsx index 8f542d1f88670..b5f7bc6983752 100644 --- a/x-pack/plugins/security_solution/public/detections/routes.tsx +++ b/x-pack/plugins/security_solution/public/detections/routes.tsx @@ -12,12 +12,11 @@ import { NotFoundPage } from '../app/404'; export const AlertsRoutes: React.FC = () => ( - ( - - )} - /> - } /> + + + + + + ); diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index 7b20873bf63cc..b32083fec1b5e 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -4719,6 +4719,14 @@ "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "status", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArray", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index f7d2c81f536be..65d9212f77dcc 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -1020,6 +1020,8 @@ export interface SignalField { rule?: Maybe; original_time?: Maybe; + + status?: Maybe; } export interface RuleField { @@ -5098,6 +5100,8 @@ export namespace GetTimelineQuery { export type Signal = { __typename?: 'SignalField'; + status: Maybe; + original_time: Maybe; rule: Maybe<_Rule>; diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx index a2f53be721816..606b43c6508fb 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx @@ -4,18 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { cloneDeep } from 'lodash/fp'; import React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; // we don't have the types for waitFor just yet, so using "as waitFor" until when we do import { render, act, wait as waitFor } from '@testing-library/react'; -import { mockFirstLastSeenHostQuery } from '../../containers/hosts/first_last_seen/mock'; +import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen'; import { TestProviders } from '../../../common/mock'; - import { FirstLastSeenHost, FirstLastSeenHostType } from '.'; +const MOCKED_RESPONSE = { + firstSeen: '2019-04-08T16:09:40.692Z', + lastSeen: '2019-04-08T18:35:45.064Z', +}; + +jest.mock('../../containers/hosts/first_last_seen'); +const useFirstLastSeenHostMock = useFirstLastSeenHost as jest.Mock; +useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); + describe('FirstLastSeen Component', () => { const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; const lastSeen = 'Apr 8, 2019 @ 18:35:45.064'; @@ -31,11 +37,10 @@ describe('FirstLastSeen Component', () => { }); test('Loading', async () => { + useFirstLastSeenHostMock.mockReturnValue([true, MOCKED_RESPONSE]); const { container } = render( - - - + ); expect(container.innerHTML).toBe( @@ -44,11 +49,10 @@ describe('FirstLastSeen Component', () => { }); test('First Seen', async () => { + useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); const { container } = render( - - - + ); @@ -62,11 +66,10 @@ describe('FirstLastSeen Component', () => { }); test('Last Seen', async () => { + useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); const { container } = render( - - - + ); await act(() => @@ -79,13 +82,16 @@ describe('FirstLastSeen Component', () => { }); test('First Seen is empty but not Last Seen', async () => { - const badDateTime = cloneDeep(mockFirstLastSeenHostQuery); - badDateTime[0].result.data!.source.HostFirstLastSeen.firstSeen = null; + useFirstLastSeenHostMock.mockReturnValue([ + false, + { + ...MOCKED_RESPONSE, + firstSeen: null, + }, + ]); const { container } = render( - - - + ); @@ -99,13 +105,16 @@ describe('FirstLastSeen Component', () => { }); test('Last Seen is empty but not First Seen', async () => { - const badDateTime = cloneDeep(mockFirstLastSeenHostQuery); - badDateTime[0].result.data!.source.HostFirstLastSeen.lastSeen = null; + useFirstLastSeenHostMock.mockReturnValue([ + false, + { + ...MOCKED_RESPONSE, + lastSeen: null, + }, + ]); const { container } = render( - - - + ); @@ -119,13 +128,16 @@ describe('FirstLastSeen Component', () => { }); test('First Seen With a bad date time string', async () => { - const badDateTime = cloneDeep(mockFirstLastSeenHostQuery); - badDateTime[0].result.data!.source.HostFirstLastSeen.firstSeen = 'something-invalid'; + useFirstLastSeenHostMock.mockReturnValue([ + false, + { + ...MOCKED_RESPONSE, + firstSeen: 'something-invalid', + }, + ]); const { container } = render( - - - + ); await act(() => @@ -136,13 +148,16 @@ describe('FirstLastSeen Component', () => { }); test('Last Seen With a bad date time string', async () => { - const badDateTime = cloneDeep(mockFirstLastSeenHostQuery); - badDateTime[0].result.data!.source.HostFirstLastSeen.lastSeen = 'something-invalid'; + useFirstLastSeenHostMock.mockReturnValue([ + false, + { + ...MOCKED_RESPONSE, + lastSeen: 'something-invalid', + }, + ]); const { container } = render( - - - + ); await act(() => diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index 579c3311cf732..a1b72fb39069c 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -5,10 +5,9 @@ */ import { EuiIcon, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui'; -import React from 'react'; -import { ApolloConsumer } from 'react-apollo'; +import React, { useMemo } from 'react'; -import { useFirstLastSeenHostQuery } from '../../containers/hosts/first_last_seen'; +import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen'; import { getEmptyTagValue } from '../../../common/components/empty_value'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; @@ -17,49 +16,48 @@ export enum FirstLastSeenHostType { LAST_SEEN = 'last-seen', } -export const FirstLastSeenHost = React.memo<{ hostname: string; type: FirstLastSeenHostType }>( - ({ hostname, type }) => { +interface FirstLastSeenHostProps { + hostName: string; + type: FirstLastSeenHostType; +} + +export const FirstLastSeenHost = React.memo(({ hostName, type }) => { + const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({ + hostName, + }); + const valueSeen = useMemo( + () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), + [firstSeen, lastSeen, type] + ); + + if (errorMessage != null) { return ( - - {(client) => { - /* eslint-disable-next-line react-hooks/rules-of-hooks */ - const { loading, firstSeen, lastSeen, errorMessage } = useFirstLastSeenHostQuery( - hostname, - 'default', - client - ); - if (errorMessage != null) { - return ( - - - - ); - } - const valueSeen = type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen; - return ( - <> - {loading && } - {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' - ? valueSeen - : !loading && - valueSeen != null && ( - - - - )} - {!loading && valueSeen == null && getEmptyTagValue()} - - ); - }} - + + + ); } -); + + return ( + <> + {loading && } + {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' + ? valueSeen + : !loading && + valueSeen != null && ( + + + + )} + {!loading && valueSeen == null && getEmptyTagValue()} + + ); +}); FirstLastSeenHost.displayName = 'FirstLastSeenHost'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.ts b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.ts deleted file mode 100644 index 65e379b5ba2d8..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import ApolloClient from 'apollo-client'; -import { get } from 'lodash/fp'; -import React, { useEffect, useState } from 'react'; - -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; -import { useUiSetting$ } from '../../../../common/lib/kibana'; -import { GetHostFirstLastSeenQuery } from '../../../../graphql/types'; -import { inputsModel } from '../../../../common/store'; -import { QueryTemplateProps } from '../../../../common/containers/query_template'; -import { useWithSource } from '../../../../common/containers/source'; -import { HostFirstLastSeenGqlQuery } from './first_last_seen.gql_query'; - -export interface FirstLastSeenHostArgs { - id: string; - errorMessage: string; - firstSeen: Date; - lastSeen: Date; - loading: boolean; - refetch: inputsModel.Refetch; -} - -export interface OwnProps extends QueryTemplateProps { - children: (args: FirstLastSeenHostArgs) => React.ReactNode; - hostName: string; -} - -export function useFirstLastSeenHostQuery( - hostName: string, - sourceId: string, - apolloClient: ApolloClient -) { - const [loading, updateLoading] = useState(false); - const [firstSeen, updateFirstSeen] = useState(null); - const [lastSeen, updateLastSeen] = useState(null); - const [errorMessage, updateErrorMessage] = useState(null); - const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); - const { docValueFields } = useWithSource(sourceId); - - async function fetchFirstLastSeenHost(signal: AbortSignal) { - updateLoading(true); - return apolloClient - .query({ - query: HostFirstLastSeenGqlQuery, - fetchPolicy: 'cache-first', - variables: { - sourceId, - hostName, - defaultIndex, - docValueFields, - }, - context: { - fetchOptions: { - signal, - }, - }, - }) - .then( - (result) => { - updateLoading(false); - updateFirstSeen(get('data.source.HostFirstLastSeen.firstSeen', result)); - updateLastSeen(get('data.source.HostFirstLastSeen.lastSeen', result)); - updateErrorMessage(null); - }, - (error) => { - updateLoading(false); - updateFirstSeen(null); - updateLastSeen(null); - updateErrorMessage(error.message); - } - ); - } - - useEffect(() => { - const abortCtrl = new AbortController(); - const signal = abortCtrl.signal; - fetchFirstLastSeenHost(signal); - return () => abortCtrl.abort(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return { firstSeen, lastSeen, loading, errorMessage }; -} diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx new file mode 100644 index 0000000000000..3a93b1ee46e7b --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; + +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsQueries, + HostFirstLastSeenRequestOptions, + HostFirstLastSeenStrategyResponse, +} from '../../../../../common/search_strategy/security_solution'; +import { useWithSource } from '../../../../common/containers/source'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; + +const ID = 'firstLastSeenHostQuery'; + +export interface FirstLastSeenHostArgs { + id: string; + errorMessage: string | null; + firstSeen?: string | null; + lastSeen?: string | null; +} +interface UseHostFirstLastSeen { + hostName: string; +} + +export const useFirstLastSeenHost = ({ + hostName, +}: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { + const { docValueFields } = useWithSource('default'); + const { data, notifications, uiSettings } = useKibana().services; + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [firstLastSeenHostRequest, setFirstLastSeenHostRequest] = useState< + HostFirstLastSeenRequestOptions + >({ + defaultIndex, + docValueFields: docValueFields ?? [], + factoryQueryType: HostsQueries.firstLastSeen, + hostName, + }); + + const [firstLastSeenHostResponse, setFirstLastSeenHostResponse] = useState( + { + firstSeen: null, + lastSeen: null, + errorMessage: null, + id: ID, + } + ); + + const firstLastSeenHostSearch = useCallback( + (request: HostFirstLastSeenRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + signal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setFirstLastSeenHostResponse((prevResponse) => ({ + ...prevResponse, + errorMessage: null, + firstSeen: response.firstSeen, + lastSeen: response.lastSeen, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_FIRST_LAST_SEEN_HOST); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + setFirstLastSeenHostResponse((prevResponse) => ({ + ...prevResponse, + errorMessage: msg, + })); + notifications.toasts.addDanger({ + title: i18n.FAIL_FIRST_LAST_SEEN_HOST, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setFirstLastSeenHostRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + docValueFields: docValueFields ?? [], + hostName, + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, docValueFields, hostName]); + + useEffect(() => { + firstLastSeenHostSearch(firstLastSeenHostRequest); + }, [firstLastSeenHostRequest, firstLastSeenHostSearch]); + + return [loading, firstLastSeenHostResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/mock.ts b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/mock.ts deleted file mode 100644 index 7f1b3d97eb525..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/mock.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; -import { GetHostFirstLastSeenQuery } from '../../../../graphql/types'; - -import { HostFirstLastSeenGqlQuery } from './first_last_seen.gql_query'; - -interface MockedProvidedQuery { - request: { - query: GetHostFirstLastSeenQuery.Query; - variables: GetHostFirstLastSeenQuery.Variables; - }; - result: { - data?: { - source: { - id: string; - HostFirstLastSeen: { - firstSeen: string | null; - lastSeen: string | null; - }; - }; - }; - errors?: [{ message: string }]; - }; -} -export const mockFirstLastSeenHostQuery: MockedProvidedQuery[] = [ - { - request: { - query: HostFirstLastSeenGqlQuery, - variables: { - sourceId: 'default', - hostName: 'kibana-siem', - defaultIndex: DEFAULT_INDEX_PATTERN, - docValueFields: [], - }, - }, - result: { - data: { - source: { - id: 'default', - HostFirstLastSeen: { - firstSeen: '2019-04-08T16:09:40.692Z', - lastSeen: '2019-04-08T18:35:45.064Z', - }, - }, - }, - }, - }, -]; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/translations.ts new file mode 100644 index 0000000000000..1e0a4ad237897 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_FIRST_LAST_SEEN_HOST = i18n.translate( + 'xpack.securitySolution.firstLastSeenHost.errorSearchDescription', + { + defaultMessage: `An error has occurred on first last seen host search`, + } +); + +export const FAIL_FIRST_LAST_SEEN_HOST = i18n.translate( + 'xpack.securitySolution.firstLastSeenHost.failSearchDescription', + { + defaultMessage: `Failed to run search on first last seen host`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index 346de9f87313f..6e1ebbfd1e7bb 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -47,6 +47,7 @@ interface UseAllHost { docValueFields?: DocValueFields[]; filterQuery?: ESTermQuery | string; endDate: string; + skip?: boolean; startDate: string; type: hostsModel.HostsType; } @@ -55,6 +56,7 @@ export const useAllHost = ({ docValueFields, filterQuery, endDate, + skip = false, startDate, type, }: UseAllHost): [boolean, HostsArgs] => { @@ -189,7 +191,7 @@ export const useAllHost = ({ field: sortField, }, }; - if (!deepEqual(prevRequest, myRequest)) { + if (!skip && !deepEqual(prevRequest, myRequest)) { return myRequest; } return prevRequest; @@ -202,6 +204,7 @@ export const useAllHost = ({ endDate, filterQuery, limit, + skip, startDate, sortField, ]); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx new file mode 100644 index 0000000000000..f766f068f099f --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// REPLACE WHEN HOST ENDPOINT DATA IS AVAILABLE + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostItem, + HostsQueries, + HostOverviewRequestOptions, + HostOverviewStrategyResponse, +} from '../../../../../common/search_strategy/security_solution/hosts'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; + +const ID = 'hostOverviewQuery'; + +export interface HostOverviewArgs { + id: string; + inspect: inputsModel.InspectQuery; + hostOverview: HostItem; + refetch: inputsModel.Refetch; + startDate: string; + endDate: string; +} + +interface UseHostOverview { + id?: string; + hostName: string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useHostOverview = ({ + endDate, + hostName, + skip = false, + startDate, + id = ID, +}: UseHostOverview): [boolean, HostOverviewArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostOverviewRequest, setHostOverviewRequest] = useState({ + defaultIndex, + hostName, + factoryQueryType: HostsQueries.hostOverview, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostOverviewResponse, setHostOverviewResponse] = useState({ + endDate, + hostOverview: {}, + id: ID, + inspect: { + dsl: [], + response: [], + }, + refetch: refetch.current, + startDate, + }); + + const hostOverviewSearch = useCallback( + (request: HostOverviewRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + signal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostOverviewResponse((prevResponse) => ({ + ...prevResponse, + hostOverview: response.hostOverview, + inspect: response.inspect ?? prevResponse.inspect, + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOST_OVERVIEW); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOST_OVERVIEW, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostOverviewRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + hostName, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, hostName, startDate, skip]); + + useEffect(() => { + hostOverviewSearch(hostOverviewRequest); + }, [hostOverviewRequest, hostOverviewSearch]); + + return [loading, hostOverviewResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/translations.ts new file mode 100644 index 0000000000000..e3fa319e70cc1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_HOST_OVERVIEW = i18n.translate( + 'xpack.securitySolution.overviewHost.errorSearchDescription', + { + defaultMessage: `An error has occurred on host overview search`, + } +); + +export const FAIL_HOST_OVERVIEW = i18n.translate( + 'xpack.securitySolution.overviewHost.failSearchDescription', + { + defaultMessage: `Failed to run search on host overview`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx index 88886a874a949..6c8eb9eb04941 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx @@ -14,7 +14,7 @@ import { hostsModel } from '../../store'; import { MatrixHistogramOption, MatrixHistogramMappingTypes, - MatrixHisrogramConfigs, + MatrixHistogramConfigs, } from '../../../common/components/matrix_histogram/types'; import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram'; import { KpiHostsChartColors } from '../../components/kpi_hosts/types'; @@ -49,7 +49,7 @@ export const authMatrixDataMappingFields: MatrixHistogramMappingTypes = { }, }; -const histogramConfigs: MatrixHisrogramConfigs = { +const histogramConfigs: MatrixHistogramConfigs = { defaultStackByOption: authStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? authStackByOptions[0], errorMessage: i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx index cea987db485f4..f28c3dfa1ad77 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx @@ -14,14 +14,13 @@ import { hostsModel } from '../../store'; import { eventsDefaultModel } from '../../../common/components/events_viewer/default_model'; import { MatrixHistogramOption, - MatrixHisrogramConfigs, + MatrixHistogramConfigs, } from '../../../common/components/matrix_histogram/types'; import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram'; import { useFullScreen } from '../../../common/containers/use_full_screen'; import * as i18n from '../translations'; import { HistogramType } from '../../../graphql/types'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; -import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers'; const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery'; @@ -42,7 +41,7 @@ export const eventsStackByOptions: MatrixHistogramOption[] = [ const DEFAULT_STACK_BY = 'event.action'; -export const histogramConfigs: MatrixHisrogramConfigs = { +export const histogramConfigs: MatrixHistogramConfigs = { defaultStackByOption: eventsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? eventsStackByOptions[0], errorMessage: i18n.ERROR_FETCHING_EVENTS_DATA, @@ -52,14 +51,14 @@ export const histogramConfigs: MatrixHisrogramConfigs = { title: i18n.NAVIGATION_EVENTS_TITLE, }; -export const EventsQueryTabBody = ({ +const EventsQueryTabBodyComponent: React.FC = ({ deleteQuery, endDate, filterQuery, pageFilters, setQuery, startDate, -}: HostsComponentsQueryProps) => { +}) => { const { initializeTimeline } = useManageTimeline(); const dispatch = useDispatch(); const { globalFullScreen } = useFullScreen(); @@ -67,9 +66,6 @@ export const EventsQueryTabBody = ({ initializeTimeline({ id: TimelineId.hostsPageEvents, defaultModel: eventsDefaultModel, - timelineRowActions: () => [ - getInvestigateInResolverAction({ dispatch, timelineId: TimelineId.hostsPageEvents }), - ], }); }, [dispatch, initializeTimeline]); @@ -106,4 +102,8 @@ export const EventsQueryTabBody = ({ ); }; +EventsQueryTabBodyComponent.displayName = 'EventsQueryTabBodyComponent'; + +export const EventsQueryTabBody = React.memo(EventsQueryTabBodyComponent); + EventsQueryTabBody.displayName = 'EventsQueryTabBody'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx index 5232dcfd88189..f8dcf9635c053 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx @@ -27,7 +27,8 @@ export const HostsQueryTabBody = ({ const [ loading, { hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAllHost({ docValueFields, endDate, filterQuery, startDate, type }); + ] = useAllHost({ docValueFields, endDate, filterQuery, skip, startDate, type }); + return ( void; networkHttp: NetworkHttpEdges[]; pageInfo: PageInfoPaginated; @@ -43,118 +40,161 @@ export interface NetworkHttpArgs { totalCount: number; } -export interface OwnProps extends QueryTemplatePaginatedProps { - children: (args: NetworkHttpArgs) => React.ReactNode; +interface UseNetworkHttp { + id?: string; ip?: string; type: networkModel.NetworkType; + filterQuery?: ESTermQuery | string; + endDate: string; + startDate: string; + skip: boolean; } -export interface NetworkHttpComponentReduxProps { - activePage: number; - isInspected: boolean; - limit: number; - sort: NetworkHttpSortField; -} +export const useNetworkHttp = ({ + endDate, + filterQuery, + id = ID, + ip, + skip, + startDate, + type, +}: UseNetworkHttp): [boolean, NetworkHttpArgs] => { + const getHttpSelector = networkSelectors.httpSelector(); + const { activePage, limit, sort } = useSelector( + (state: State) => getHttpSelector(state, type), + shallowEqual + ); + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + + const [networkHttpRequest, setHostRequest] = useState({ + defaultIndex, + factoryQueryType: NetworkQueries.http, + filterQuery: createFilter(filterQuery), + ip, + pagination: generateTablePaginationOptions(activePage, limit), + sort: sort as SortField, + timerange: { + interval: '12h', + from: startDate ? startDate : '', + to: endDate ? endDate : new Date(Date.now()).toISOString(), + }, + }); + + const wrappedLoadMore = useCallback( + (newActivePage: number) => { + setHostRequest((prevRequest) => { + return { + ...prevRequest, + pagination: generateTablePaginationOptions(newActivePage, limit), + }; + }); + }, + [limit] + ); -type NetworkHttpProps = OwnProps & NetworkHttpComponentReduxProps & WithKibanaProps; + const [networkHttpResponse, setNetworkHttpResponse] = useState({ + networkHttp: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + loadPage: wrappedLoadMore, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + refetch: refetch.current, + totalCount: -1, + }); -class NetworkHttpComponentQuery extends QueryTemplatePaginated< - NetworkHttpProps, - GetNetworkHttpQuery.Query, - GetNetworkHttpQuery.Variables -> { - public render() { - const { - activePage, - children, - endDate, - filterQuery, - id = ID, - ip, - isInspected, - kibana, - limit, - skip, - sourceId, - sort, - startDate, - } = this.props; - const variables: GetNetworkHttpQuery.Variables = { - defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), - filterQuery: createFilter(filterQuery), - inspect: isInspected, - ip, - pagination: generateTablePaginationOptions(activePage, limit), - sort, - sourceId, - timerange: { - interval: '12h', - from: startDate!, - to: endDate!, - }, - }; - return ( - - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - query={networkHttpQuery} - skip={skip} - variables={variables} - > - {({ data, loading, fetchMore, networkStatus, refetch }) => { - const networkHttp = getOr([], `source.NetworkHttp.edges`, data); - this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newActivePage: number) => ({ - variables: { - pagination: generateTablePaginationOptions(newActivePage, limit), + const networkHttpSearch = useCallback( + (request: NetworkHttpRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + signal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkHttpResponse((prevResponse) => ({ + ...prevResponse, + networkHttp: response.edges, + inspect: response.inspect ?? prevResponse.inspect, + pageInfo: response.pageInfo, + refetch: refetch.current, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_HTTP); + searchSubscription$.unsubscribe(); + } }, - updateQuery: (prev, { fetchMoreResult }) => { - if (!fetchMoreResult) { - return prev; + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_NETWORK_HTTP, + text: msg.message, + }); } - return { - ...fetchMoreResult, - source: { - ...fetchMoreResult.source, - NetworkHttp: { - ...fetchMoreResult.source.NetworkHttp, - edges: [...fetchMoreResult.source.NetworkHttp.edges], - }, - }, - }; }, - })); - const isLoading = this.isItAValidLoading(loading, variables, networkStatus); - return children({ - id, - inspect: getOr(null, 'source.NetworkHttp.inspect', data), - isInspected, - loading: isLoading, - loadPage: this.wrappedLoadMore, - networkHttp, - pageInfo: getOr({}, 'source.NetworkHttp.pageInfo', data), - refetch: this.memoizedRefetchQuery(variables, limit, refetch), - totalCount: getOr(-1, 'source.NetworkHttp.totalCount', data), }); - }} - - ); - } -} + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); -const makeMapStateToProps = () => { - const getHttpSelector = networkSelectors.httpSelector(); - const getQuery = inputsSelectors.globalQueryByIdSelector(); - return (state: State, { id = ID, type }: OwnProps) => { - const { isInspected } = getQuery(state, id); - return { - ...getHttpSelector(state, type), - isInspected, - }; - }; -}; + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + sort: sort as SortField, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip]); + + useEffect(() => { + networkHttpSearch(networkHttpRequest); + }, [networkHttpRequest, networkHttpSearch]); -export const NetworkHttpQuery = compose>( - connect(makeMapStateToProps), - withKibana -)(NetworkHttpComponentQuery); + return [loading, networkHttpResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/network_http/translations.ts b/x-pack/plugins/security_solution/public/network/containers/network_http/translations.ts new file mode 100644 index 0000000000000..7909a5e48b8c4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/network_http/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_NETWORK_HTTP = i18n.translate( + 'xpack.securitySolution.networkHttp.errorSearchDescription', + { + defaultMessage: `An error has occurred on network http search`, + } +); + +export const FAIL_NETWORK_HTTP = i18n.translate( + 'xpack.securitySolution.networkHttp.failSearchDescription', + { + defaultMessage: `Failed to run search on network http`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx index 17506f9a01cb9..77c6446fbb3d0 100644 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx @@ -4,38 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr } from 'lodash/fp'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; +import { noop } from 'lodash/fp'; +import { useState, useEffect, useCallback, useRef } from 'react'; +import { shallowEqual, useSelector } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; +import { ESTermQuery } from '../../../../common/typed_json'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { - PageInfoPaginated, - TlsEdges, - TlsSortField, - GetTlsQuery, - FlowTargetSourceDest, -} from '../../../graphql/types'; -import { inputsModel, State, inputsSelectors } from '../../../common/store'; -import { withKibana, WithKibanaProps } from '../../../common/lib/kibana'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; +import { inputsModel, State } from '../../../common/store'; +import { useKibana } from '../../../common/lib/kibana'; +import { createFilter } from '../../../common/containers/helpers'; +import { TlsEdges, PageInfoPaginated, FlowTargetSourceDest } from '../../../graphql/types'; import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers'; -import { - QueryTemplatePaginated, - QueryTemplatePaginatedProps, -} from '../../../common/containers/query_template_paginated'; import { networkModel, networkSelectors } from '../../store'; -import { tlsQuery } from './index.gql_query'; +import { + NetworkQueries, + NetworkTlsRequestOptions, + NetworkTlsStrategyResponse, +} from '../../../../common/search_strategy/security_solution/network'; +import { AbortError } from '../../../../../../../src/plugins/data/common'; +import * as i18n from './translations'; -const ID = 'tlsQuery'; +const ID = 'networkTlsQuery'; -export interface TlsArgs { +export interface NetworkTlsArgs { id: string; inspect: inputsModel.InspectQuery; isInspected: boolean; - loading: boolean; loadPage: (newActivePage: number) => void; pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; @@ -43,121 +38,159 @@ export interface TlsArgs { totalCount: number; } -export interface OwnProps extends QueryTemplatePaginatedProps { - children: (args: TlsArgs) => React.ReactNode; +interface UseNetworkTls { flowTarget: FlowTargetSourceDest; ip: string; type: networkModel.NetworkType; + filterQuery?: ESTermQuery | string; + endDate: string; + startDate: string; + skip: boolean; + id?: string; } -export interface TlsComponentReduxProps { - activePage: number; - isInspected: boolean; - limit: number; - sort: TlsSortField; -} +export const useNetworkTls = ({ + endDate, + filterQuery, + flowTarget, + id = ID, + ip, + skip, + startDate, + type, +}: UseNetworkTls): [boolean, NetworkTlsArgs] => { + const getTlsSelector = networkSelectors.tlsSelector(); + const { activePage, limit, sort } = useSelector( + (state: State) => getTlsSelector(state, type, flowTarget), + shallowEqual + ); + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + + const [networkTlsRequest, setHostRequest] = useState({ + defaultIndex, + factoryQueryType: NetworkQueries.tls, + filterQuery: createFilter(filterQuery), + flowTarget, + ip, + pagination: generateTablePaginationOptions(activePage, limit), + sort, + timerange: { + interval: '12h', + from: startDate ? startDate : '', + to: endDate ? endDate : new Date(Date.now()).toISOString(), + }, + }); + + const wrappedLoadMore = useCallback( + (newActivePage: number) => { + setHostRequest((prevRequest) => ({ + ...prevRequest, + pagination: generateTablePaginationOptions(newActivePage, limit), + })); + }, + [limit] + ); -type TlsProps = OwnProps & TlsComponentReduxProps & WithKibanaProps; + const [networkTlsResponse, setNetworkTlsResponse] = useState({ + tls: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + loadPage: wrappedLoadMore, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + refetch: refetch.current, + totalCount: -1, + }); -class TlsComponentQuery extends QueryTemplatePaginated< - TlsProps, - GetTlsQuery.Query, - GetTlsQuery.Variables -> { - public render() { - const { - activePage, - children, - endDate, - filterQuery, - flowTarget, - id = ID, - ip, - isInspected, - kibana, - limit, - skip, - sourceId, - startDate, - sort, - } = this.props; - const variables: GetTlsQuery.Variables = { - defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), - filterQuery: createFilter(filterQuery), - flowTarget, - inspect: isInspected, - ip, - pagination: generateTablePaginationOptions(activePage, limit), - sort, - sourceId, - timerange: { - interval: '12h', - from: startDate ? startDate : '', - to: endDate ? endDate : new Date(Date.now()).toISOString(), - }, - }; - return ( - - query={tlsQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - skip={skip} - variables={variables} - > - {({ data, loading, fetchMore, networkStatus, refetch }) => { - const tls = getOr([], 'source.Tls.edges', data); - this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newActivePage: number) => ({ - variables: { - pagination: generateTablePaginationOptions(newActivePage, limit), + const networkTlsSearch = useCallback( + (request: NetworkTlsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + signal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setNetworkTlsResponse((prevResponse) => ({ + ...prevResponse, + tls: response.edges, + inspect: response.inspect ?? prevResponse.inspect, + pageInfo: response.pageInfo, + refetch: refetch.current, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_NETWORK_TLS); + searchSubscription$.unsubscribe(); + } }, - updateQuery: (prev, { fetchMoreResult }) => { - if (!fetchMoreResult) { - return prev; + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_NETWORK_TLS, text: msg.message }); } - return { - ...fetchMoreResult, - source: { - ...fetchMoreResult.source, - Tls: { - ...fetchMoreResult.source.Tls, - edges: [...fetchMoreResult.source.Tls.edges], - }, - }, - }; }, - })); - const isLoading = this.isItAValidLoading(loading, variables, networkStatus); - return children({ - id, - inspect: getOr(null, 'source.Tls.inspect', data), - isInspected, - loading: isLoading, - loadPage: this.wrappedLoadMore, - pageInfo: getOr({}, 'source.Tls.pageInfo', data), - refetch: this.memoizedRefetchQuery(variables, limit, refetch), - tls, - totalCount: getOr(-1, 'source.Tls.totalCount', data), }); - }} - - ); - } -} + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); -const makeMapStateToProps = () => { - const getTlsSelector = networkSelectors.tlsSelector(); - const getQuery = inputsSelectors.globalQueryByIdSelector(); - return (state: State, { flowTarget, id = ID, type }: OwnProps) => { - const { isInspected } = getQuery(state, id); - return { - ...getTlsSelector(state, type, flowTarget), - isInspected, - }; - }; -}; + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + sort, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip]); + + useEffect(() => { + networkTlsSearch(networkTlsRequest); + }, [networkTlsRequest, networkTlsSearch]); -export const TlsQuery = compose>( - connect(makeMapStateToProps), - withKibana -)(TlsComponentQuery); + return [loading, networkTlsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/translations.ts b/x-pack/plugins/security_solution/public/network/containers/tls/translations.ts new file mode 100644 index 0000000000000..aafa3ff0a98b0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/tls/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_NETWORK_TLS = i18n.translate( + 'xpack.securitySolution.networkTls.errorSearchDescription', + { + defaultMessage: `An error has occurred on network tls search`, + } +); + +export const FAIL_NETWORK_TLS = i18n.translate( + 'xpack.securitySolution.networkTls.failSearchDescription', + { + defaultMessage: `Failed to run search on network tls`, + } +); diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/network_http_query_table.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/network_http_query_table.tsx index 551de698cfa08..1b1b2b5f4f46e 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/network_http_query_table.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/network_http_query_table.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { getOr } from 'lodash/fp'; import { manageQuery } from '../../../common/components/page/manage_query'; import { OwnProps } from './types'; -import { NetworkHttpQuery } from '../../containers/network_http'; +import { useNetworkHttp } from '../../containers/network_http'; import { NetworkHttpTable } from '../../components/network_http_table'; const NetworkHttpTableManage = manageQuery(NetworkHttpTable); @@ -21,43 +21,35 @@ export const NetworkHttpQueryTable = ({ skip, startDate, type, -}: OwnProps) => ( - - {({ - id, - inspect, - isInspected, - loading, - loadPage, - networkHttp, - pageInfo, - refetch, - totalCount, - }) => ( - - )} - -); +}: OwnProps) => { + const [ + loading, + { id, inspect, isInspected, loadPage, networkHttp, pageInfo, refetch, totalCount }, + ] = useNetworkHttp({ + endDate, + filterQuery, + ip, + skip, + startDate, + type, + }); + + return ( + + ); +}; NetworkHttpQueryTable.displayName = 'NetworkHttpQueryTable'; diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx index f0c3628af78d8..5184fccecf07a 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/tls_query_table.tsx @@ -8,7 +8,7 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { manageQuery } from '../../../common/components/page/manage_query'; import { TlsTable } from '../../components/tls_table'; -import { TlsQuery } from '../../containers/tls'; +import { useNetworkTls } from '../../containers/tls'; import { TlsQueryTableComponentProps } from './types'; const TlsTableManage = manageQuery(TlsTable); @@ -22,34 +22,36 @@ export const TlsQueryTable = ({ skip, startDate, type, -}: TlsQueryTableComponentProps) => ( - - {({ id, inspect, isInspected, tls, totalCount, pageInfo, loading, loadPage, refetch }) => ( - - )} - -); +}: TlsQueryTableComponentProps) => { + const [ + loading, + { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }, + ] = useNetworkTls({ + endDate, + filterQuery, + flowTarget, + ip, + skip, + startDate, + type, + }); + + return ( + + ); +}; TlsQueryTable.displayName = 'TlsQueryTable'; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx index 77283dc330257..2886089a1eb99 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx @@ -16,7 +16,7 @@ import { networkModel } from '../../store'; import { MatrixHistogramOption, - MatrixHisrogramConfigs, + MatrixHistogramConfigs, } from '../../../common/components/matrix_histogram/types'; import * as i18n from '../translations'; import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram'; @@ -33,7 +33,7 @@ const dnsStackByOptions: MatrixHistogramOption[] = [ const DEFAULT_STACK_BY = 'dns.question.registered_domain'; -export const histogramConfigs: Omit = { +export const histogramConfigs: Omit = { defaultStackByOption: dnsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? dnsStackByOptions[0], errorMessage: i18n.ERROR_FETCHING_DNS_DATA, @@ -64,7 +64,7 @@ export const DnsQueryTabBody = ({ [] ); - const dnsHistogramConfigs: MatrixHisrogramConfigs = useMemo( + const dnsHistogramConfigs: MatrixHistogramConfigs = useMemo( () => ({ ...histogramConfigs, title: getTitle, diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx index 7e0c4025d6cac..3caff05734c1e 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { getOr } from 'lodash/fp'; import { NetworkHttpTable } from '../../components/network_http_table'; -import { NetworkHttpQuery } from '../../containers/network_http'; +import { useNetworkHttp } from '../../containers/network_http'; import { networkModel } from '../../store'; import { manageQuery } from '../../../common/components/page/manage_query'; @@ -22,42 +22,34 @@ export const HttpQueryTabBody = ({ skip, startDate, setQuery, -}: HttpQueryTabBodyProps) => ( - - {({ - id, - inspect, - isInspected, - loading, - loadPage, - networkHttp, - pageInfo, - refetch, - totalCount, - }) => ( - - )} - -); +}: HttpQueryTabBodyProps) => { + const [ + loading, + { id, inspect, isInspected, loadPage, networkHttp, pageInfo, refetch, totalCount }, + ] = useNetworkHttp({ + endDate, + filterQuery, + skip, + startDate, + type: networkModel.NetworkType.page, + }); + + return ( + + ); +}; HttpQueryTabBody.displayName = 'HttpQueryTabBody'; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx index 00da5496e5440..279891cc181e3 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx @@ -6,13 +6,13 @@ import React from 'react'; import { getOr } from 'lodash/fp'; import { manageQuery } from '../../../common/components/page/manage_query'; -import { TlsQuery } from '../../../network/containers/tls'; +import { useNetworkTls } from '../../../network/containers/tls'; import { TlsTable } from '../../components/tls_table'; import { TlsQueryTabBodyProps } from './types'; const TlsTableManage = manageQuery(TlsTable); -export const TlsQueryTabBody = ({ +const TlsQueryTabBodyComponent: React.FC = ({ endDate, filterQuery, flowTarget, @@ -21,32 +21,38 @@ export const TlsQueryTabBody = ({ skip, startDate, type, -}: TlsQueryTabBodyProps) => ( - - {({ id, inspect, isInspected, tls, totalCount, pageInfo, loading, loadPage, refetch }) => ( - - )} - -); +}) => { + const [ + loading, + { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }, + ] = useNetworkTls({ + endDate, + filterQuery, + flowTarget, + ip, + skip, + startDate, + type, + }); + + return ( + + ); +}; + +TlsQueryTabBodyComponent.displayName = 'TlsQueryTabBodyComponent'; + +export const TlsQueryTabBody = React.memo(TlsQueryTabBodyComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx index 6e59d81a1eae9..111935782949b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx @@ -26,7 +26,7 @@ import { alertsStackByOptions, histogramConfigs, } from '../../../common/components/alerts_viewer/histogram_configs'; -import { MatrixHisrogramConfigs } from '../../../common/components/matrix_histogram/types'; +import { MatrixHistogramConfigs } from '../../../common/components/matrix_histogram/types'; import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { SecurityPageName } from '../../../app/types'; @@ -93,7 +93,7 @@ const AlertsByCategoryComponent: React.FC = ({ [goToHostAlerts, formatUrl] ); - const alertsByCategoryHistogramConfigs: MatrixHisrogramConfigs = useMemo( + const alertsByCategoryHistogramConfigs: MatrixHistogramConfigs = useMemo( () => ({ ...histogramConfigs, defaultStackByOption: diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index f18fccee50e22..2e9c25f01b3c1 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -14,7 +14,7 @@ import { SHOWING, UNIT } from '../../../common/components/events_viewer/translat import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram'; import { - MatrixHisrogramConfigs, + MatrixHistogramConfigs, MatrixHistogramOption, } from '../../../common/components/matrix_histogram/types'; import { eventsStackByOptions } from '../../../hosts/pages/navigation'; @@ -127,7 +127,7 @@ const EventsByDatasetComponent: React.FC = ({ [combinedQueries, kibana, indexPattern, query, filters] ); - const eventsByDatasetHistogramConfigs: MatrixHisrogramConfigs = useMemo( + const eventsByDatasetHistogramConfigs: MatrixHistogramConfigs = useMemo( () => ({ ...histogramConfigs, stackByOptions: diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx index c7aba6fcc8a5b..08f3f01bc99f6 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx @@ -91,7 +91,7 @@ export const HostOverview = React.memo( description: data.host != null && data.host.name && data.host.name.length ? ( ) : ( @@ -103,7 +103,7 @@ export const HostOverview = React.memo( description: data.host != null && data.host.name && data.host.name.length ? ( ) : ( diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap index 8d4de5b90fae9..23732e88ba1f9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/__snapshots__/index.test.tsx.snap @@ -40,6 +40,8 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt buttonContentClassName="accordion-button" id="host-stat-accordion-groupauditbeat" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > @@ -273,6 +275,8 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt buttonContentClassName="accordion-button" id="host-stat-accordion-groupendgame" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > @@ -538,6 +542,8 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt buttonContentClassName="accordion-button" id="host-stat-accordion-groupfilebeat" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > @@ -611,6 +617,8 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt buttonContentClassName="accordion-button" id="host-stat-accordion-groupwinlogbeat" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap index f6060fc060958..167866ef3c606 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/__snapshots__/index.test.tsx.snap @@ -40,6 +40,8 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet buttonContentClassName="accordion-button" id="network-stat-accordion-groupauditbeat" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > @@ -113,6 +115,8 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet buttonContentClassName="accordion-button" id="network-stat-accordion-groupfilebeat" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > @@ -314,6 +318,8 @@ exports[`Overview Network Stat Data rendering it renders the default OverviewNet buttonContentClassName="accordion-button" id="network-stat-accordion-grouppacketbeat" initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} paddingSize="none" > diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts index e087db9f74685..21c4f92f8e502 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts @@ -59,6 +59,7 @@ describe('Resolver Data Middleware', () => { let firstChildNodeInTree: TreeNode; let eventStatsForFirstChildNode: { total: number; byCategory: Record }; let categoryToOverCount: string; + let aggregateCategoryTotalForFirstChildNode: number; let tree: ResolverTree; /** @@ -73,6 +74,7 @@ describe('Resolver Data Middleware', () => { firstChildNodeInTree, eventStatsForFirstChildNode, categoryToOverCount, + aggregateCategoryTotalForFirstChildNode, } = mockedTree()); if (tree) { dispatchTree(tree); @@ -138,6 +140,13 @@ describe('Resolver Data Middleware', () => { expect(notDisplayed(typeCounted)).toBe(0); } }); + it('should return an overall correct count for the number of related events', () => { + const aggregateTotalByEntityId = selectors.relatedEventAggregateTotalByEntityId( + store.getState() + ); + const countForId = aggregateTotalByEntityId(firstChildNodeInTree.id); + expect(countForId).toBe(aggregateCategoryTotalForFirstChildNode); + }); }); describe('when data was received and stats show more related events than the API can provide', () => { beforeEach(() => { @@ -262,6 +271,7 @@ function mockedTree() { tree: tree!, firstChildNodeInTree, eventStatsForFirstChildNode: statsResults.eventStats, + aggregateCategoryTotalForFirstChildNode: statsResults.aggregateCategoryTotal, categoryToOverCount: statsResults.firstCategory, }; } @@ -288,6 +298,7 @@ function compileStatsForChild( }; /** The category of the first event. */ firstCategory: string; + aggregateCategoryTotal: number; } { const totalRelatedEvents = node.relatedEvents.length; // For the purposes of testing, we pick one category to fake an extra event for @@ -295,6 +306,12 @@ function compileStatsForChild( let firstCategory: string | undefined; + // This is the "aggregate total" which is displayed to users as the total count + // of related events for the node. It is tallied by incrementing for every discrete + // event.category in an event.category array (or just 1 for a plain string). E.g. two events + // categories 'file' and ['dns','network'] would have an `aggregate total` of 3. + let aggregateCategoryTotal: number = 0; + const compiledStats = node.relatedEvents.reduce( (counts: Record, relatedEvent) => { // `relatedEvent.event.category` is `string | string[]`. @@ -310,6 +327,7 @@ function compileStatsForChild( // Increment the count of events with this category counts[category] = counts[category] ? counts[category] + 1 : 1; + aggregateCategoryTotal++; } return counts; }, @@ -327,5 +345,6 @@ function compileStatsForChild( byCategory: compiledStats, }, firstCategory, + aggregateCategoryTotal, }; } diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 965547f1e309a..eaa80b46471fa 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -170,6 +170,26 @@ export const relatedEventsStats: ( } ); +/** + * This returns the "aggregate total" for related events, tallied as the sum + * of their individual `event.category`s. E.g. a [DNS, Network] would count as two + * towards the aggregate total. + */ +export const relatedEventAggregateTotalByEntityId: ( + state: DataState +) => (entityId: string) => number = createSelector(relatedEventsStats, (relatedStats) => { + return (entityId) => { + const statsForEntity = relatedStats(entityId); + if (statsForEntity === undefined) { + return 0; + } + return Object.values(statsForEntity?.events?.byCategory || {}).reduce( + (sum, val) => sum + val, + 0 + ); + }; +}); + /** * returns a map of entity_ids to related event data. */ diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 909a907626f30..bdea08df3d7f5 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -114,6 +114,18 @@ export const relatedEventsStats: ( dataSelectors.relatedEventsStats ); +/** + * This returns the "aggregate total" for related events, tallied as the sum + * of their individual `event.category`s. E.g. a [DNS, Network] would count as two + * towards the aggregate total. + */ +export const relatedEventAggregateTotalByEntityId: ( + state: ResolverState +) => (nodeID: string) => number = composeSelectors( + dataStateSelector, + dataSelectors.relatedEventAggregateTotalByEntityId +); + /** * Map of related events... by entity id */ diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx index 98b737de8fa59..133dcd21e7f56 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx @@ -17,6 +17,7 @@ import { EventCountsForProcess } from './event_counts_for_process'; import { ProcessDetails } from './process_details'; import { ProcessListWithCounts } from './process_list_with_counts'; import { RelatedEventDetail } from './related_event_detail'; +import { ResolverState } from '../../types'; /** * The team decided to use this table to determine which breadcrumbs/view to display: @@ -102,6 +103,12 @@ const PanelContent = memo(function PanelContent() { ? relatedEventStats(idFromParams) : undefined; + const parentCount = useSelector((state: ResolverState) => { + if (idFromParams === '') { + return 0; + } + return selectors.relatedEventAggregateTotalByEntityId(state)(idFromParams); + }); /** * Determine which set of breadcrumbs to display based on the query parameters * for the table & breadcrumb nav. @@ -186,9 +193,6 @@ const PanelContent = memo(function PanelContent() { } if (panelToShow === 'relatedEventDetail') { - const parentCount: number = Object.values( - relatedStatsForIdFromParams?.events.byCategory || {} - ).reduce((sum, val) => sum + val, 0); return ( ; - }, [uiSelectedEvent, crumbEvent, crumbId, relatedStatsForIdFromParams, panelToShow]); + }, [uiSelectedEvent, crumbEvent, crumbId, relatedStatsForIdFromParams, panelToShow, parentCount]); return <>{panelInstance}; }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx index 01fa912caa866..4fcc557742643 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx @@ -13,6 +13,7 @@ import { EuiText, EuiTextColor, EuiDescriptionList, + EuiLink, } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from 'react-intl'; @@ -58,6 +59,9 @@ export const ProcessDetails = memo(function ProcessDetails({ const isProcessTerminated = useSelector((state: ResolverState) => selectors.isProcessTerminated(state)(entityId) ); + const relatedEventTotal = useSelector((state: ResolverState) => { + return selectors.relatedEventAggregateTotalByEntityId(state)(entityId); + }); const processInfoEntry: EuiDescriptionListProps['listItems'] = useMemo(() => { const eventTime = event.eventTimestamp(processEvent); const dateTime = eventTime === undefined ? null : formatDate(eventTime); @@ -164,6 +168,12 @@ export const ProcessDetails = memo(function ProcessDetails({ return cubeAssetsForNode(isProcessTerminated, false); }, [processEvent, cubeAssetsForNode, isProcessTerminated]); + const handleEventsLinkClick = useMemo(() => { + return () => { + pushToQueryParams({ crumbId: entityId, crumbEvent: 'all' }); + }; + }, [entityId, pushToQueryParams]); + const titleID = useMemo(() => htmlIdGenerator('resolverTable')(), []); return ( <> @@ -185,6 +195,14 @@ export const ProcessDetails = memo(function ProcessDetails({ {descriptionText} + + + + & { /** * The current timeline column headers @@ -106,7 +100,6 @@ const FieldsBrowserComponent: React.FC = ({ browserFields, columnHeaders, filteredBrowserFields, - isEventViewer, isSearching, onCategorySelected, onFieldSelected, @@ -193,7 +186,6 @@ const FieldsBrowserComponent: React.FC = ({
{ ); - expect(wrapper.find('strong').first().text()).toEqual(highlight); + expect(wrapper.find('mark').first().text()).toEqual(highlight); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx index 9ad460db4c7b7..916240ac411e5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/header.tsx @@ -54,7 +54,6 @@ SearchContainer.displayName = 'SearchContainer'; interface Props { filteredBrowserFields: BrowserFields; - isEventViewer?: boolean; isSearching: boolean; onOutsideClick: () => void; onSearchInputChange: (event: React.ChangeEvent) => void; @@ -93,7 +92,6 @@ CountRow.displayName = 'CountRow'; const TitleRow = React.memo<{ id: string; - isEventViewer?: boolean; onOutsideClick: () => void; onUpdateColumns: OnUpdateColumns; }>(({ id, onOutsideClick, onUpdateColumns }) => { @@ -130,7 +128,6 @@ TitleRow.displayName = 'TitleRow'; export const Header = React.memo( ({ - isEventViewer, isSearching, filteredBrowserFields, onOutsideClick, @@ -140,12 +137,7 @@ export const Header = React.memo( timelineId, }) => ( - + = ({ columnHeaders, browserFields, height, - isEventViewer = false, onFieldSelected, onUpdateColumns, timelineId, @@ -164,7 +163,6 @@ export const StatefulFieldsBrowserComponent: React.FC = ({ filteredBrowserFields != null ? filteredBrowserFields : browserFieldsWithDefaultCategory } height={height} - isEventViewer={isEventViewer} isSearching={isSearching} onCategorySelected={updateSelectedCategoryId} onFieldSelected={onFieldSelected} diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx index b918e5abc652b..fe0f0c8f8b91f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx @@ -8,7 +8,6 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { getTimelineDefaults, useTimelineManager, UseTimelineManager } from './'; import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { TimelineRowAction } from '../timeline/body/actions'; const isStringifiedComparisonEqual = (a: {}, b: {}): boolean => JSON.stringify(a) === JSON.stringify(b); @@ -17,13 +16,14 @@ describe('useTimelineManager', () => { const setupMock = coreMock.createSetup(); const testId = 'coolness'; const timelineDefaults = getTimelineDefaults(testId); - const timelineRowActions = () => []; const mockFilterManager = new FilterManager(setupMock.uiSettings); + beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); - it('initilizes an undefined timeline', async () => { + + it('initializes an undefined timeline', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useTimelineManager() @@ -33,6 +33,7 @@ describe('useTimelineManager', () => { expect(isStringifiedComparisonEqual(uninitializedTimeline, timelineDefaults)).toBeTruthy(); }); }); + it('getIndexToAddById', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => @@ -43,6 +44,7 @@ describe('useTimelineManager', () => { expect(data).toEqual(timelineDefaults.indexToAdd); }); }); + it('setIndexToAdd', async () => { await act(async () => { const indexToAddArgs = { id: testId, indexToAdd: ['example'] }; @@ -52,13 +54,13 @@ describe('useTimelineManager', () => { await waitForNextUpdate(); result.current.initializeTimeline({ id: testId, - timelineRowActions, }); result.current.setIndexToAdd(indexToAddArgs); const data = result.current.getIndexToAddById(testId); expect(data).toEqual(indexToAddArgs.indexToAdd); }); }); + it('setIsTimelineLoading', async () => { await act(async () => { const isLoadingArgs = { id: testId, isLoading: true }; @@ -68,7 +70,6 @@ describe('useTimelineManager', () => { await waitForNextUpdate(); result.current.initializeTimeline({ id: testId, - timelineRowActions, }); let timeline = result.current.getManageTimelineById(testId); expect(timeline.isLoading).toBeFalsy(); @@ -77,29 +78,7 @@ describe('useTimelineManager', () => { expect(timeline.isLoading).toBeTruthy(); }); }); - it('setTimelineRowActions', async () => { - await act(async () => { - const timelineRowActionsEx = () => [ - { id: 'wow', content: 'hey', displayType: 'icon', onClick: () => {} } as TimelineRowAction, - ]; - const { result, waitForNextUpdate } = renderHook(() => - useTimelineManager() - ); - await waitForNextUpdate(); - result.current.initializeTimeline({ - id: testId, - timelineRowActions, - }); - let timeline = result.current.getManageTimelineById(testId); - expect(timeline.timelineRowActions).toEqual(timelineRowActions); - result.current.setTimelineRowActions({ - id: testId, - timelineRowActions: timelineRowActionsEx, - }); - timeline = result.current.getManageTimelineById(testId); - expect(timeline.timelineRowActions).toEqual(timelineRowActionsEx); - }); - }); + it('getTimelineFilterManager undefined on uninitialized', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => @@ -110,6 +89,7 @@ describe('useTimelineManager', () => { expect(data).toEqual(undefined); }); }); + it('getTimelineFilterManager defined at initialize', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => @@ -118,13 +98,13 @@ describe('useTimelineManager', () => { await waitForNextUpdate(); result.current.initializeTimeline({ id: testId, - timelineRowActions, filterManager: mockFilterManager, }); const data = result.current.getTimelineFilterManager(testId); expect(data).toEqual(mockFilterManager); }); }); + it('isManagedTimeline returns false when unset and then true when set', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => @@ -135,7 +115,6 @@ describe('useTimelineManager', () => { expect(data).toBeFalsy(); result.current.initializeTimeline({ id: testId, - timelineRowActions, filterManager: mockFilterManager, }); data = result.current.isManagedTimeline(testId); diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx index 560d4c6928e4e..f82158fe65c11 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx @@ -9,12 +9,10 @@ import { noop } from 'lodash/fp'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager'; -import { TimelineRowAction } from '../timeline/body/actions'; import { SubsetTimelineModel } from '../../store/timeline/model'; import * as i18n from '../../../common/components/events_viewer/translations'; import * as i18nF from '../timeline/footer/translations'; import { timelineDefaults as timelineDefaultModel } from '../../store/timeline/defaults'; -import { Ecs, TimelineNonEcsData } from '../../../graphql/types'; interface ManageTimelineInit { documentType?: string; @@ -25,16 +23,11 @@ interface ManageTimelineInit { indexToAdd?: string[] | null; loadingText?: string; selectAll?: boolean; - timelineRowActions: ({ ecsData, nonEcsData }: TimelineRowActionArgs) => TimelineRowAction[]; + queryFields?: string[]; title?: string; unit?: (totalCount: number) => string; } -export interface TimelineRowActionArgs { - ecsData: Ecs; - nonEcsData: TimelineNonEcsData[]; -} - interface ManageTimeline { documentType: string; defaultModel: SubsetTimelineModel; @@ -46,7 +39,6 @@ interface ManageTimeline { loadingText: string; queryFields: string[]; selectAll: boolean; - timelineRowActions: ({ ecsData, nonEcsData }: TimelineRowActionArgs) => TimelineRowAction[]; title: string; unit: (totalCount: number) => string; } @@ -75,14 +67,6 @@ type ActionManageTimeline = type: 'SET_SELECT_ALL'; id: string; payload: boolean; - } - | { - type: 'SET_TIMELINE_ACTIONS'; - id: string; - payload: { - queryFields?: string[]; - timelineRowActions: ({ ecsData, nonEcsData }: TimelineRowActionArgs) => TimelineRowAction[]; - }; }; export const getTimelineDefaults = (id: string) => ({ @@ -95,7 +79,6 @@ export const getTimelineDefaults = (id: string) => ({ id, isLoading: false, queryFields: [], - timelineRowActions: () => [], title: i18n.EVENTS, unit: (n: number) => i18n.UNIT(n), }); @@ -129,14 +112,7 @@ const reducerManageTimeline = ( selectAll: action.payload, }, } as ManageTimelineById; - case 'SET_TIMELINE_ACTIONS': - return { - ...state, - [action.id]: { - ...state[action.id], - ...action.payload, - }, - } as ManageTimelineById; + case 'SET_IS_LOADING': return { ...state, @@ -159,11 +135,6 @@ export interface UseTimelineManager { setIndexToAdd: (indexToAddArgs: { id: string; indexToAdd: string[] }) => void; setIsTimelineLoading: (isLoadingArgs: { id: string; isLoading: boolean }) => void; setSelectAll: (selectAllArgs: { id: string; selectAll: boolean }) => void; - setTimelineRowActions: (actionsArgs: { - id: string; - queryFields?: string[]; - timelineRowActions: ({ ecsData, nonEcsData }: TimelineRowActionArgs) => TimelineRowAction[]; - }) => void; } export const useTimelineManager = ( @@ -181,25 +152,6 @@ export const useTimelineManager = ( }); }, []); - const setTimelineRowActions = useCallback( - ({ - id, - queryFields, - timelineRowActions, - }: { - id: string; - queryFields?: string[]; - timelineRowActions: ({ ecsData, nonEcsData }: TimelineRowActionArgs) => TimelineRowAction[]; - }) => { - dispatch({ - type: 'SET_TIMELINE_ACTIONS', - id, - payload: { queryFields, timelineRowActions }, - }); - }, - [] - ); - const setIsTimelineLoading = useCallback( ({ id, isLoading }: { id: string; isLoading: boolean }) => { dispatch({ @@ -236,7 +188,7 @@ export const useTimelineManager = ( if (state[id] != null) { return state[id]; } - initializeTimeline({ id, timelineRowActions: () => [] }); + initializeTimeline({ id }); return getTimelineDefaults(id); }, [initializeTimeline, state] @@ -261,7 +213,6 @@ export const useTimelineManager = ( setIndexToAdd, setIsTimelineLoading, setSelectAll, - setTimelineRowActions, }; }; @@ -274,7 +225,6 @@ const init = { setIndexToAdd: () => undefined, setIsTimelineLoading: () => noop, setSelectAll: () => noop, - setTimelineRowActions: () => noop, }; const ManageTimelineContext = createContext(init); diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap index 6d2f35750d8b1..9c0a9b26f5332 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap @@ -24,6 +24,7 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "size": "64px", }, }, + "browserDefaultFontSize": "16px", "euiAnimSlightBounce": "cubic-bezier(0.34, 1.61, 0.7, 1)", "euiAnimSlightResistance": "cubic-bezier(0.694, 0.0482, 0.335, 1)", "euiAnimSpeedExtraFast": "90ms", @@ -434,6 +435,28 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiScrollBarCorner": "6px", "euiSelectableListItemBorder": "1px solid #202128", "euiSelectableListItemPadding": "4px 12px", + "euiSelectableTemplateSitewideTypes": Object { + "application": Object { + "color": "#6092c0", + "font-weight": 500, + }, + "article": Object { + "color": "#9777bc", + "font-weight": 500, + }, + "case": Object { + "color": "#e7664c", + "font-weight": 500, + }, + "deployment": Object { + "color": "#54b399", + "font-weight": 500, + }, + "platform": Object { + "color": "#d6bf57", + "font-weight": 500, + }, + }, "euiShadowColor": "#000000", "euiShadowColorLarge": "#000000", "euiSize": "16px", diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index ac6c61b33b35e..ed44fc14e3efa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -36,7 +36,7 @@ import { KueryFilterQueryKind } from '../../../common/store/model'; import { Note } from '../../../common/lib/note'; import moment from 'moment'; import sinon from 'sinon'; -import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; jest.mock('../../../common/store/inputs/actions'); jest.mock('../../../common/components/url_state/normalize_time_range.ts'); @@ -942,7 +942,7 @@ describe('helpers', () => { test('it invokes date range picker dispatch', () => { timelineDispatch({ duplicate: true, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [], @@ -958,7 +958,7 @@ describe('helpers', () => { test('it invokes add timeline dispatch', () => { timelineDispatch({ duplicate: true, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [], @@ -966,7 +966,7 @@ describe('helpers', () => { })(); expect(dispatchAddTimeline).toHaveBeenCalledWith({ - id: 'timeline-1', + id: TimelineId.active, savedTimeline: true, timeline: mockTimelineModel, }); @@ -975,7 +975,7 @@ describe('helpers', () => { test('it does not invoke kql filter query dispatches if timeline.kqlQuery.filterQuery is null', () => { timelineDispatch({ duplicate: true, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [], @@ -989,7 +989,7 @@ describe('helpers', () => { test('it does not invoke notes dispatch if duplicate is true', () => { timelineDispatch({ duplicate: true, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [], @@ -1012,7 +1012,7 @@ describe('helpers', () => { }; timelineDispatch({ duplicate: true, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [], @@ -1036,7 +1036,7 @@ describe('helpers', () => { }; timelineDispatch({ duplicate: true, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [], @@ -1044,14 +1044,14 @@ describe('helpers', () => { })(); expect(dispatchSetKqlFilterQueryDraft).toHaveBeenCalledWith({ - id: 'timeline-1', + id: TimelineId.active, filterQueryDraft: { kind: 'kuery', expression: 'expression', }, }); expect(dispatchApplyKqlFilterQuery).toHaveBeenCalledWith({ - id: 'timeline-1', + id: TimelineId.active, filterQuery: { kuery: { kind: 'kuery', @@ -1065,7 +1065,7 @@ describe('helpers', () => { test('it invokes dispatchAddNotes if duplicate is false', () => { timelineDispatch({ duplicate: false, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [ @@ -1099,7 +1099,7 @@ describe('helpers', () => { test('it invokes dispatch to create a timeline note if duplicate is true and ruleNote exists', () => { timelineDispatch({ duplicate: true, - id: 'timeline-1', + id: TimelineId.active, from: '2020-03-26T14:35:56.356Z', to: '2020-03-26T14:41:56.356Z', notes: [], @@ -1119,7 +1119,7 @@ describe('helpers', () => { expect(dispatchAddNotes).not.toHaveBeenCalled(); expect(dispatchUpdateNote).toHaveBeenCalledWith({ note: expectedNote }); expect(dispatchAddGlobalTimelineNote).toHaveBeenLastCalledWith({ - id: 'timeline-1', + id: TimelineId.active, noteId: 'uuid.v4()', }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index c2e23cc19d89e..b6b6148340a4a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -22,7 +22,12 @@ import { DataProviderResult, } from '../../../graphql/types'; -import { DataProviderType, TimelineStatus, TimelineType } from '../../../../common/types/timeline'; +import { + DataProviderType, + TimelineId, + TimelineStatus, + TimelineType, +} from '../../../../common/types/timeline'; import { addNotes as dispatchAddNotes, @@ -315,7 +320,7 @@ export const queryTimelineById = ({ updateIsLoading, updateTimeline, }: QueryTimelineById) => { - updateIsLoading({ id: 'timeline-1', isLoading: true }); + updateIsLoading({ id: TimelineId.active, isLoading: true }); if (apolloClient) { apolloClient .query({ @@ -343,7 +348,7 @@ export const queryTimelineById = ({ updateTimeline({ duplicate, from, - id: 'timeline-1', + id: TimelineId.active, notes, timeline: { ...timeline, @@ -355,7 +360,7 @@ export const queryTimelineById = ({ } }) .finally(() => { - updateIsLoading({ id: 'timeline-1', isLoading: false }); + updateIsLoading({ id: TimelineId.active, isLoading: false }); }); } }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 4c5db80a6c916..f681043a9047d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -11,6 +11,7 @@ import { Dispatch } from 'redux'; import { DeleteTimelineMutation, SortFieldTimeline, Direction } from '../../../graphql/types'; import { State } from '../../../common/store'; +import { TimelineId } from '../../../../common/types/timeline'; import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; @@ -192,7 +193,7 @@ export const StatefulOpenTimelineComponent = React.memo( const deleteTimelines: DeleteTimelines = useCallback( async (timelineIds: string[]) => { if (timelineIds.includes(timeline.savedObjectId || '')) { - createNewTimeline({ id: 'timeline-1', columns: defaultHeaders, show: false }); + createNewTimeline({ id: TimelineId.active, columns: defaultHeaders, show: false }); } await apolloClient.mutate< @@ -369,7 +370,7 @@ export const StatefulOpenTimelineComponent = React.memo( const makeMapStateToProps = () => { const getTimeline = timelineSelectors.getTimelineByIdSelector(); const mapStateToProps = (state: State) => { - const timeline = getTimeline(state, 'timeline-1') ?? timelineDefaults; + const timeline = getTimeline(state, TimelineId.active) ?? timelineDefaults; return { timeline, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx index 1f5f0ccca3b70..e9ae66703f017 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx @@ -224,7 +224,7 @@ export const OpenTimeline = React.memo( popoverContent={getBatchItemsPopoverContent} data-test-subj="utility-bar-action" > - {i18n.BATCH_ACTIONS} + {i18n.BATCH_ACTIONS} )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/action_icon_item.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/action_icon_item.tsx new file mode 100644 index 0000000000000..64f8ce3727f39 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/action_icon_item.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { MouseEvent } from 'react'; +import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; + +import { EventsTd, EventsTdContent } from '../../styles'; +import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers'; + +interface ActionIconItemProps { + ariaLabel?: string; + id: string; + width?: number; + dataTestSubj?: string; + content?: string; + iconType?: string; + isDisabled?: boolean; + onClick?: (event: MouseEvent) => void; + children?: React.ReactNode; +} + +const ActionIconItemComponent: React.FC = ({ + id, + width = DEFAULT_ICON_BUTTON_WIDTH, + dataTestSubj, + content, + ariaLabel, + iconType, + isDisabled = false, + onClick, + children, +}) => ( + + + {children ?? ( + + + + )} + + +); + +ActionIconItemComponent.displayName = 'ActionIconItemComponent'; + +export const ActionIconItem = React.memo(ActionIconItemComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/add_note_icon_item.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/add_note_icon_item.tsx new file mode 100644 index 0000000000000..a82821675d956 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/add_note_icon_item.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { TimelineType, TimelineStatus } from '../../../../../../common/types/timeline'; +import { AssociateNote, UpdateNote } from '../../../notes/helpers'; +import * as i18n from '../translations'; +import { NotesButton } from '../../properties/helpers'; +import { Note } from '../../../../../common/lib/note'; +import { ActionIconItem } from './action_icon_item'; + +interface AddEventNoteActionProps { + associateNote: AssociateNote; + getNotesByIds: (noteIds: string[]) => Note[]; + noteIds: string[]; + showNotes: boolean; + status: TimelineStatus; + timelineType: TimelineType; + toggleShowNotes: () => void; + updateNote: UpdateNote; +} + +const AddEventNoteActionComponent: React.FC = ({ + associateNote, + getNotesByIds, + noteIds, + showNotes, + status, + timelineType, + toggleShowNotes, + updateNote, +}) => ( + + + +); + +AddEventNoteActionComponent.displayName = 'AddEventNoteActionComponent'; + +export const AddEventNoteAction = React.memo(AddEventNoteActionComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx index 78ee9bdd053b2..fb1709df01320 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx @@ -9,10 +9,7 @@ import { useSelector } from 'react-redux'; import { TestProviders, mockTimelineModel } from '../../../../../common/mock'; import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants'; -import * as i18n from '../translations'; - import { Actions } from '.'; -import { TimelineType } from '../../../../../../common/types/timeline'; jest.mock('react-redux', () => { const origin = jest.requireActual('react-redux'); @@ -30,22 +27,14 @@ describe('Actions', () => { ); @@ -58,22 +47,14 @@ describe('Actions', () => { ); @@ -86,22 +67,14 @@ describe('Actions', () => { ); @@ -116,22 +89,14 @@ describe('Actions', () => { ); @@ -140,197 +105,4 @@ describe('Actions', () => { expect(onEventToggled).toBeCalled(); }); - - test('it does NOT render a notes button when isEventsViewer is true', () => { - const toggleShowNotes = jest.fn(); - - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="timeline-notes-button-small"]').exists()).toBe(false); - }); - - test('it invokes toggleShowNotes when the button for adding notes is clicked', () => { - const toggleShowNotes = jest.fn(); - - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="timeline-notes-button-small"]').first().simulate('click'); - - expect(toggleShowNotes).toBeCalled(); - }); - - test('it renders correct tooltip for NotesButton - timeline', () => { - const toggleShowNotes = jest.fn(); - - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual(i18n.NOTES_TOOLTIP); - }); - - test('it renders correct tooltip for NotesButton - timeline template', () => { - (useSelector as jest.Mock).mockReturnValue({ - ...mockTimelineModel, - timelineType: TimelineType.template, - }); - const toggleShowNotes = jest.fn(); - - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual( - i18n.NOTES_DISABLE_TOOLTIP - ); - (useSelector as jest.Mock).mockReturnValue(mockTimelineModel); - }); - - test('it does NOT render a pin button when isEventViewer is true', () => { - const onPinClicked = jest.fn(); - - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="pin"]').exists()).toBe(false); - }); - - test('it invokes onPinClicked when the button for pinning events is clicked', () => { - const onPinClicked = jest.fn(); - - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="pin"]').first().simulate('click'); - - expect(onPinClicked).toHaveBeenCalled(); - }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx index c9c8250922161..3d08d56d6fb19 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx @@ -3,203 +3,90 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { useSelector } from 'react-redux'; -import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { EuiButtonIcon, EuiLoadingSpinner, EuiCheckbox } from '@elastic/eui'; -import { Note } from '../../../../../common/lib/note'; -import { StoreState } from '../../../../../common/store/types'; -import { TimelineType } from '../../../../../../common/types/timeline'; - -import { TimelineModel } from '../../../../store/timeline/model'; - -import { AssociateNote, UpdateNote } from '../../../notes/helpers'; -import { Pin } from '../../pin'; -import { NotesButton } from '../../properties/helpers'; import { EventsLoading, EventsTd, EventsTdContent, EventsTdGroupActions } from '../../styles'; -import { eventHasNotes, getPinTooltip } from '../helpers'; import * as i18n from '../translations'; import { OnRowSelected } from '../../events'; -import { Ecs, TimelineNonEcsData } from '../../../../../graphql/types'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers'; -export interface TimelineRowActionOnClick { - eventId: string; - ecsData: Ecs; - data: TimelineNonEcsData[]; -} - -export interface TimelineRowAction { - ariaLabel?: string; - dataTestSubj?: string; - displayType: 'icon' | 'contextMenu'; - iconType?: string; - id: string; - isActionDisabled?: (ecsData?: Ecs) => boolean; - onClick: ({ eventId, ecsData }: TimelineRowActionOnClick) => void; - content: string | JSX.Element; - width?: number; -} - interface Props { actionsColumnWidth: number; additionalActions?: JSX.Element[]; - associateNote: AssociateNote; checked: boolean; onRowSelected: OnRowSelected; expanded: boolean; eventId: string; - eventIsPinned: boolean; - getNotesByIds: (noteIds: string[]) => Note[]; - isEventViewer?: boolean; loading: boolean; loadingEventIds: Readonly; - noteIds: string[]; onEventToggled: () => void; - onPinClicked: () => void; - showNotes: boolean; showCheckboxes: boolean; - toggleShowNotes: () => void; - updateNote: UpdateNote; } -const emptyNotes: string[] = []; - -export const Actions = React.memo( - ({ - actionsColumnWidth, - additionalActions, - associateNote, - checked, - expanded, - eventId, - eventIsPinned, - getNotesByIds, - isEventViewer = false, - loading = false, - loadingEventIds, - noteIds, - onEventToggled, - onPinClicked, - onRowSelected, - showCheckboxes, - showNotes, - toggleShowNotes, - updateNote, - }) => { - const timeline = useSelector((state) => { - return state.timeline.timelineById['timeline-1']; - }); - return ( - - {showCheckboxes && ( - - - {loadingEventIds.includes(eventId) ? ( - - ) : ( - ) => { - onRowSelected({ - eventIds: [eventId], - isSelected: event.currentTarget.checked, - }); - }} - /> - )} - - - )} +const ActionsComponent: React.FC = ({ + actionsColumnWidth, + additionalActions, + checked, + expanded, + eventId, + loading = false, + loadingEventIds, + onEventToggled, + onRowSelected, + showCheckboxes, +}) => { + const handleSelectEvent = useCallback( + (event: React.ChangeEvent) => + onRowSelected({ + eventIds: [eventId], + isSelected: event.currentTarget.checked, + }), + [eventId, onRowSelected] + ); - + return ( + + {showCheckboxes && ( + - {loading ? ( - + {loadingEventIds.includes(eventId) ? ( + ) : ( - )} + )} + + + {loading ? ( + + ) : ( + + )} + + - <>{additionalActions} + <>{additionalActions} + + ); +}; - {!isEventViewer && ( - <> - - - - - - - +ActionsComponent.displayName = 'ActionsComponent'; - - - - - - - )} - - ); - }, - (nextProps, prevProps) => { - return ( - prevProps.actionsColumnWidth === nextProps.actionsColumnWidth && - prevProps.additionalActions === nextProps.additionalActions && - prevProps.checked === nextProps.checked && - prevProps.expanded === nextProps.expanded && - prevProps.eventId === nextProps.eventId && - prevProps.eventIsPinned === nextProps.eventIsPinned && - prevProps.loading === nextProps.loading && - prevProps.loadingEventIds === nextProps.loadingEventIds && - prevProps.noteIds === nextProps.noteIds && - prevProps.onRowSelected === nextProps.onRowSelected && - prevProps.showCheckboxes === nextProps.showCheckboxes && - prevProps.showNotes === nextProps.showNotes - ); - } -); -Actions.displayName = 'Actions'; +export const Actions = React.memo(ActionsComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/pin_event_action.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/pin_event_action.tsx new file mode 100644 index 0000000000000..2f9f15938cad6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/pin_event_action.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo } from 'react'; +import { EuiToolTip } from '@elastic/eui'; + +import { EventsTd, EventsTdContent } from '../../styles'; +import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers'; +import { eventHasNotes, getPinTooltip } from '../helpers'; +import { Pin } from '../../pin'; +import { TimelineType } from '../../../../../../common/types/timeline'; + +interface PinEventActionProps { + noteIds: string[]; + onPinClicked: () => void; + eventIsPinned: boolean; + timelineType: TimelineType; +} + +const PinEventActionComponent: React.FC = ({ + noteIds, + onPinClicked, + eventIsPinned, + timelineType, +}) => { + const tooltipContent = useMemo( + () => + getPinTooltip({ + isPinned: eventIsPinned, + eventHasNotes: eventHasNotes(noteIds), + timelineType, + }), + [eventIsPinned, noteIds, timelineType] + ); + + return ( + + + + + + + + ); +}; + +PinEventActionComponent.displayName = 'PinEventActionComponent'; + +export const PinEventAction = React.memo(PinEventActionComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx index a3e177604fbd4..120fc12b425f4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx @@ -235,7 +235,6 @@ export const ColumnHeadersComponent = ({ columnHeaders={columnHeaders} data-test-subj="field-browser" height={FIELD_BROWSER_HEIGHT} - isEventViewer={isEventViewer} onUpdateColumns={onUpdateColumns} timelineId={timelineId} toggleColumn={toggleColumn} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx new file mode 100644 index 0000000000000..ae552ade665cb --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { mount } from 'enzyme'; +import React from 'react'; +import { useSelector } from 'react-redux'; + +import { TestProviders, mockTimelineModel } from '../../../../../common/mock'; +import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants'; +import * as i18n from '../translations'; + +import { EventColumnView } from './event_column_view'; +import { TimelineType } from '../../../../../../common/types/timeline'; + +jest.mock('react-redux', () => { + const origin = jest.requireActual('react-redux'); + return { + ...origin, + useSelector: jest.fn(), + }; +}); + +describe('EventColumnView', () => { + (useSelector as jest.Mock).mockReturnValue(mockTimelineModel); + + const props = { + id: 'event-id', + actionsColumnWidth: DEFAULT_ACTIONS_COLUMN_WIDTH, + associateNote: jest.fn(), + columnHeaders: [], + columnRenderers: [], + data: [ + { + field: 'host.name', + }, + ], + ecsData: { + _id: 'id', + }, + eventIdToNoteIds: {}, + expanded: false, + getNotesByIds: jest.fn(), + loading: false, + loadingEventIds: [], + onColumnResized: jest.fn(), + onEventToggled: jest.fn(), + onPinEvent: jest.fn(), + onRowSelected: jest.fn(), + onUnPinEvent: jest.fn(), + refetch: jest.fn(), + selectedEventIds: {}, + showCheckboxes: false, + showNotes: false, + timelineId: 'timeline-1', + toggleShowNotes: jest.fn(), + updateNote: jest.fn(), + isEventPinned: false, + }; + + test('it does NOT render a notes button when isEventsViewer is true', () => { + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('[data-test-subj="timeline-notes-button-small"]').exists()).toBe(false); + }); + + test('it invokes toggleShowNotes when the button for adding notes is clicked', () => { + const wrapper = mount(, { wrappingComponent: TestProviders }); + + expect(props.toggleShowNotes).not.toHaveBeenCalled(); + + wrapper.find('[data-test-subj="timeline-notes-button-small"]').first().simulate('click'); + + expect(props.toggleShowNotes).toHaveBeenCalled(); + }); + + test('it renders correct tooltip for NotesButton - timeline', () => { + const wrapper = mount(, { wrappingComponent: TestProviders }); + + expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual(i18n.NOTES_TOOLTIP); + }); + + test('it renders correct tooltip for NotesButton - timeline template', () => { + (useSelector as jest.Mock).mockReturnValue({ + ...mockTimelineModel, + timelineType: TimelineType.template, + }); + + const wrapper = mount(, { wrappingComponent: TestProviders }); + + expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual( + i18n.NOTES_DISABLE_TOOLTIP + ); + (useSelector as jest.Mock).mockReturnValue(mockTimelineModel); + }); + + test('it does NOT render a pin button when isEventViewer is true', () => { + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(wrapper.find('[data-test-subj="pin"]').exists()).toBe(false); + }); + + test('it invokes onPinClicked when the button for pinning events is clicked', () => { + const wrapper = mount(, { wrappingComponent: TestProviders }); + + expect(props.onPinEvent).not.toHaveBeenCalled(); + + wrapper.find('[data-test-subj="pin"]').first().simulate('click'); + + expect(props.onPinEvent).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index e7462188001e9..f1d45d5458554 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -4,29 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import uuid from 'uuid'; +import { useSelector, shallowEqual } from 'react-redux'; -import { - EuiButtonIcon, - EuiToolTip, - EuiContextMenuPanel, - EuiPopover, - EuiContextMenuItem, -} from '@elastic/eui'; -import styled from 'styled-components'; import { TimelineNonEcsData, Ecs } from '../../../../../graphql/types'; -import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers'; import { Note } from '../../../../../common/lib/note'; import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; import { AssociateNote, UpdateNote } from '../../../notes/helpers'; import { OnColumnResized, OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events'; -import { EventsTd, EventsTdContent, EventsTrData } from '../../styles'; +import { EventsTrData } from '../../styles'; import { Actions } from '../actions'; import { DataDrivenColumns } from '../data_driven_columns'; -import { eventHasNotes, getPinOnClick } from '../helpers'; +import { + eventHasNotes, + getEventType, + getPinOnClick, + InvestigateInResolverAction, +} from '../helpers'; import { ColumnRenderer } from '../renderers/column_renderer'; -import { useManageTimeline } from '../../../manage_timeline'; +import { AlertContextMenu } from '../../../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; +import { InvestigateInTimelineAction } from '../../../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action'; +import { AddEventNoteAction } from '../actions/add_note_icon_item'; +import { PinEventAction } from '../actions/pin_event_action'; +import { StoreState } from '../../../../../common/store/types'; +import { inputsModel } from '../../../../../common/store'; +import { TimelineId } from '../../../../../../common/types/timeline'; + +import { TimelineModel } from '../../../../store/timeline/model'; interface Props { id: string; @@ -48,6 +53,7 @@ interface Props { onPinEvent: OnPinEvent; onRowSelected: OnRowSelected; onUnPinEvent: OnUnPinEvent; + refetch: inputsModel.Refetch; selectedEventIds: Readonly>; showCheckboxes: boolean; showNotes: boolean; @@ -81,6 +87,7 @@ export const EventColumnView = React.memo( onPinEvent, onRowSelected, onUnPinEvent, + refetch, selectedEventIds, showCheckboxes, showNotes, @@ -88,114 +95,10 @@ export const EventColumnView = React.memo( toggleShowNotes, updateNote, }) => { - const { getManageTimelineById } = useManageTimeline(); - const timelineActions = useMemo( - () => getManageTimelineById(timelineId).timelineRowActions({ nonEcsData: data, ecsData }), - [data, ecsData, getManageTimelineById, timelineId] + const { timelineType, status } = useSelector( + (state) => state.timeline.timelineById[timelineId], + shallowEqual ); - const [isPopoverOpen, setPopover] = useState(false); - - const onButtonClick = useCallback(() => { - setPopover(!isPopoverOpen); - }, [isPopoverOpen]); - - const closePopover = useCallback(() => { - setPopover(false); - }, []); - - const button = ( - - ); - - const onClickCb = useCallback((cb: () => void) => { - cb(); - closePopover(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const additionalActions = useMemo(() => { - const grouped = timelineActions.reduce( - ( - acc: { - contextMenu: JSX.Element[]; - icon: JSX.Element[]; - }, - action - ) => { - if (action.displayType === 'icon') { - return { - ...acc, - icon: [ - ...acc.icon, - - - - action.onClick({ eventId: id, ecsData, data })} - /> - - - , - ], - }; - } - return { - ...acc, - contextMenu: [ - ...acc.contextMenu, - onClickCb(() => action.onClick({ eventId: id, ecsData, data }))} - > - {action.content} - , - ], - }; - }, - { icon: [], contextMenu: [] } - ); - return grouped.contextMenu.length > 0 - ? [ - ...grouped.icon, - - - - - - - , - ] - : grouped.icon; - }, [button, closePopover, id, onClickCb, data, ecsData, timelineActions, isPopoverOpen]); const handlePinClicked = useCallback( () => @@ -209,29 +112,90 @@ export const EventColumnView = React.memo( [eventIdToNoteIds, id, isEventPinned, onPinEvent, onUnPinEvent] ); + const eventType = getEventType(ecsData); + + const additionalActions = useMemo( + () => [ + , + ...(timelineId !== TimelineId.active && eventType === 'signal' + ? [ + , + ] + : []), + ...(!isEventViewer + ? [ + , + , + ] + : []), + , + ], + [ + associateNote, + data, + ecsData, + eventIdToNoteIds, + eventType, + getNotesByIds, + handlePinClicked, + id, + isEventPinned, + isEventViewer, + refetch, + showNotes, + status, + timelineId, + timelineType, + toggleShowNotes, + updateNote, + ] + ); + return ( ( /> ); - }, - (prevProps, nextProps) => { - return ( - prevProps.id === nextProps.id && - prevProps.actionsColumnWidth === nextProps.actionsColumnWidth && - prevProps.columnHeaders === nextProps.columnHeaders && - prevProps.columnRenderers === nextProps.columnRenderers && - prevProps.data === nextProps.data && - prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds && - prevProps.expanded === nextProps.expanded && - prevProps.loading === nextProps.loading && - prevProps.loadingEventIds === nextProps.loadingEventIds && - prevProps.isEventPinned === nextProps.isEventPinned && - prevProps.onRowSelected === nextProps.onRowSelected && - prevProps.selectedEventIds === nextProps.selectedEventIds && - prevProps.showCheckboxes === nextProps.showCheckboxes && - prevProps.showNotes === nextProps.showNotes && - prevProps.timelineId === nextProps.timelineId - ); } ); -const ContextMenuPanel = styled(EuiContextMenuPanel)` - font-size: ${({ theme }) => theme.eui.euiFontSizeS}; -`; -ContextMenuPanel.displayName = 'ContextMenuPanel'; +EventColumnView.displayName = 'EventColumnView'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx index ca7a64db58c95..64d55f8cf6c6a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import { inputsModel } from '../../../../../common/store'; import { BrowserFields, DocValueFields } from '../../../../../common/containers/source'; import { TimelineItem, TimelineNonEcsData } from '../../../../../graphql/types'; import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; @@ -44,6 +45,7 @@ interface Props { onUpdateColumns: OnUpdateColumns; onUnPinEvent: OnUnPinEvent; pinnedEventIds: Readonly>; + refetch: inputsModel.Refetch; rowRenderers: RowRenderer[]; selectedEventIds: Readonly>; showCheckboxes: boolean; @@ -71,6 +73,7 @@ const EventsComponent: React.FC = ({ onUpdateColumns, onUnPinEvent, pinnedEventIds, + refetch, rowRenderers, selectedEventIds, showCheckboxes, @@ -78,7 +81,7 @@ const EventsComponent: React.FC = ({ updateNote, }) => ( - {data.map((event, i) => ( + {data.map((event) => ( = ({ onRowSelected={onRowSelected} onUnPinEvent={onUnPinEvent} onUpdateColumns={onUpdateColumns} + refetch={refetch} rowRenderers={rowRenderers} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 3236482e6bc27..c91fc473708e2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -9,6 +9,7 @@ import { useSelector } from 'react-redux'; import uuid from 'uuid'; import VisibilitySensor from 'react-visibility-sensor'; +import { TimelineId } from '../../../../../../common/types/timeline'; import { BrowserFields, DocValueFields } from '../../../../../common/containers/source'; import { TimelineDetailsQuery } from '../../../../containers/details'; import { TimelineItem, DetailItem, TimelineNonEcsData } from '../../../../../graphql/types'; @@ -33,7 +34,7 @@ import { getEventType } from '../helpers'; import { NoteCards } from '../../../notes/note_cards'; import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context'; import { EventColumnView } from './event_column_view'; -import { StoreState } from '../../../../../common/store'; +import { inputsModel, StoreState } from '../../../../../common/store'; interface Props { actionsColumnWidth: number; @@ -55,6 +56,7 @@ interface Props { onUnPinEvent: OnUnPinEvent; onUpdateColumns: OnUpdateColumns; isEventPinned: boolean; + refetch: inputsModel.Refetch; rowRenderers: RowRenderer[]; selectedEventIds: Readonly>; showCheckboxes: boolean; @@ -121,6 +123,7 @@ const StatefulEventComponent: React.FC = ({ onRowSelected, onUnPinEvent, onUpdateColumns, + refetch, rowRenderers, selectedEventIds, showCheckboxes, @@ -130,9 +133,9 @@ const StatefulEventComponent: React.FC = ({ }) => { const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>({}); const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); - const timeline = useSelector((state) => { - return state.timeline.timelineById['timeline-1']; - }); + const { status: timelineStatus } = useSelector( + (state) => state.timeline.timelineById[TimelineId.active] + ); const divElement = useRef(null); const onToggleShowNotes = useCallback(() => { @@ -206,6 +209,7 @@ const StatefulEventComponent: React.FC = ({ onPinEvent={onPinEvent} onRowSelected={onRowSelected} onUnPinEvent={onUnPinEvent} + refetch={refetch} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} showNotes={!!showNotes[event._id]} @@ -226,7 +230,7 @@ const StatefulEventComponent: React.FC = ({ getNotesByIds={getNotesByIds} noteIds={eventIdToNoteIds[event._id] || emptyNotes} showAddNote={!!showNotes[event._id]} - status={timeline.status} + status={timelineStatus} toggleShowAddNote={onToggleShowNotes} updateNote={updateNote} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx similarity index 67% rename from x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts rename to x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index b62888fbf8427..5753efa2bf1bb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -4,16 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { useCallback, useMemo } from 'react'; import { get, isEmpty } from 'lodash/fp'; -import { Dispatch } from 'redux'; +import { useDispatch } from 'react-redux'; import { Ecs, TimelineItem, TimelineNonEcsData } from '../../../../graphql/types'; -import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers'; import { updateTimelineGraphEventId } from '../../../store/timeline/actions'; -import { EventType } from '../../../../timelines/store/timeline/model'; +import { EventType } from '../../../store/timeline/model'; import { OnPinEvent, OnUnPinEvent } from '../events'; - -import { TimelineRowAction, TimelineRowActionOnClick } from './actions'; +import { ActionIconItem } from './actions/action_icon_item'; import * as i18n from './translations'; import { TimelineTypeLiteral, TimelineType } from '../../../../../common/types/timeline'; @@ -89,8 +88,8 @@ export const getEventIdToDataMapping = ( timelineData: TimelineItem[], eventIds: string[], fieldsToKeep: string[] -): Record => { - return timelineData.reduce((acc, v) => { +): Record => + timelineData.reduce((acc, v) => { const fvm = eventIds.includes(v._id) ? { [v._id]: v.data.filter((ti) => fieldsToKeep.includes(ti.field)) } : {}; @@ -99,7 +98,6 @@ export const getEventIdToDataMapping = ( ...fvm, }; }, {}); -}; /** Return eventType raw or signal */ export const getEventType = (event: Ecs): Omit => { @@ -109,29 +107,40 @@ export const getEventType = (event: Ecs): Omit => { return 'raw'; }; -export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => { +export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => + get(['agent', 'type', 0], ecsData) === 'endpoint' && + get(['process', 'entity_id'], ecsData)?.length === 1 && + get(['process', 'entity_id', 0], ecsData) !== ''; + +interface InvestigateInResolverActionProps { + timelineId: string; + ecsData: Ecs; +} + +const InvestigateInResolverActionComponent: React.FC = ({ + timelineId, + ecsData, +}) => { + const dispatch = useDispatch(); + const isDisabled = useMemo(() => !isInvestigateInResolverActionEnabled(ecsData), [ecsData]); + const handleClick = useCallback( + () => dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: ecsData._id })), + [dispatch, ecsData._id, timelineId] + ); + return ( - get(['agent', 'type', 0], ecsData) === 'endpoint' && - get(['process', 'entity_id'], ecsData)?.length === 1 && - get(['process', 'entity_id', 0], ecsData) !== '' + ); }; -export const getInvestigateInResolverAction = ({ - dispatch, - timelineId, -}: { - dispatch: Dispatch; - timelineId: string; -}): TimelineRowAction => ({ - ariaLabel: i18n.ACTION_INVESTIGATE_IN_RESOLVER, - content: i18n.ACTION_INVESTIGATE_IN_RESOLVER, - dataTestSubj: 'investigate-in-resolver', - displayType: 'icon', - iconType: 'node', - id: 'investigateInResolver', - isActionDisabled: (ecsData?: Ecs) => !isInvestigateInResolverActionEnabled(ecsData), - onClick: ({ eventId }: TimelineRowActionOnClick) => - dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: eventId })), - width: DEFAULT_ICON_BUTTON_WIDTH, -}); +InvestigateInResolverActionComponent.displayName = 'InvestigateInResolverActionComponent'; + +export const InvestigateInResolverAction = React.memo(InvestigateInResolverActionComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 4eac5360321c1..657e1617e8d24 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -64,7 +64,6 @@ describe('Body', () => { data: mockTimelineData, docValueFields: [], eventIdToNoteIds: {}, - id: 'timeline-test', isSelectAllChecked: false, getNotesByIds: mockGetNotesByIds, loadingEventIds: [], @@ -78,11 +77,13 @@ describe('Body', () => { onUnPinEvent: jest.fn(), onUpdateColumns: jest.fn(), pinnedEventIds: {}, + refetch: jest.fn(), rowRenderers, selectedEventIds: {}, show: true, sort: mockSort, showCheckboxes: false, + timelineId: 'timeline-test', timelineType: TimelineType.default, toggleColumn: jest.fn(), updateNote: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 6f578ffe3e956..40cc12afde51d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -6,10 +6,11 @@ import React, { useMemo, useRef } from 'react'; +import { inputsModel } from '../../../../common/store'; import { BrowserFields, DocValueFields } from '../../../../common/containers/source'; import { TimelineItem, TimelineNonEcsData } from '../../../../graphql/types'; import { Note } from '../../../../common/lib/note'; -import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions, EventType } from '../../../../timelines/store/timeline/model'; import { AddNoteToEvent, UpdateNote } from '../../notes/helpers'; import { OnColumnRemoved, @@ -42,10 +43,10 @@ export interface BodyProps { docValueFields: DocValueFields[]; getNotesByIds: (noteIds: string[]) => Note[]; graphEventId?: string; - id: string; isEventViewer?: boolean; isSelectAllChecked: boolean; eventIdToNoteIds: Readonly>; + eventType?: EventType; loadingEventIds: Readonly; onColumnRemoved: OnColumnRemoved; onColumnResized: OnColumnResized; @@ -57,18 +58,23 @@ export interface BodyProps { onUpdateColumns: OnUpdateColumns; onUnPinEvent: OnUnPinEvent; pinnedEventIds: Readonly>; + refetch: inputsModel.Refetch; rowRenderers: RowRenderer[]; selectedEventIds: Readonly>; show: boolean; showCheckboxes: boolean; sort: Sort; + timelineId: string; timelineType: TimelineType; toggleColumn: (column: ColumnHeaderOptions) => void; updateNote: UpdateNote; } -export const hasAdditonalActions = (id: string): boolean => - id === TimelineId.detectionsPage || id === TimelineId.detectionsRulesDetailsPage; +export const hasAdditionalActions = (id: string, eventType?: EventType): boolean => + id === TimelineId.detectionsPage || + id === TimelineId.detectionsRulesDetailsPage || + ((id === TimelineId.active && eventType && ['all', 'signal', 'alert'].includes(eventType)) ?? + false); const EXTRA_WIDTH = 4; // px @@ -82,9 +88,9 @@ export const Body = React.memo( data, docValueFields, eventIdToNoteIds, + eventType, getNotesByIds, graphEventId, - id, isEventViewer = false, isSelectAllChecked, loadingEventIds, @@ -99,11 +105,13 @@ export const Body = React.memo( onUnPinEvent, pinnedEventIds, rowRenderers, + refetch, selectedEventIds, show, showCheckboxes, sort, toggleColumn, + timelineId, timelineType, updateNote, }) => { @@ -113,9 +121,9 @@ export const Body = React.memo( getActionsColumnWidth( isEventViewer, showCheckboxes, - hasAdditonalActions(id) ? DEFAULT_ICON_BUTTON_WIDTH + EXTRA_WIDTH : 0 + hasAdditionalActions(timelineId, eventType) ? DEFAULT_ICON_BUTTON_WIDTH + EXTRA_WIDTH : 0 ), - [isEventViewer, showCheckboxes, id] + [isEventViewer, showCheckboxes, timelineId, eventType] ); const columnWidths = useMemo( @@ -127,11 +135,15 @@ export const Body = React.memo( return ( <> {graphEventId && ( - + )} @@ -151,7 +163,7 @@ export const Body = React.memo( showEventsSelect={false} showSelectAllCheckbox={showCheckboxes} sort={sort} - timelineId={id} + timelineId={timelineId} toggleColumn={toggleColumn} /> @@ -166,7 +178,7 @@ export const Body = React.memo( docValueFields={docValueFields} eventIdToNoteIds={eventIdToNoteIds} getNotesByIds={getNotesByIds} - id={id} + id={timelineId} isEventViewer={isEventViewer} loadingEventIds={loadingEventIds} onColumnResized={onColumnResized} @@ -175,6 +187,7 @@ export const Body = React.memo( onUpdateColumns={onUpdateColumns} onUnPinEvent={onUnPinEvent} pinnedEventIds={pinnedEventIds} + refetch={refetch} rowRenderers={rowRenderers} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx index 8deda03ece70e..9b7b896a2ec69 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/stateful_body.tsx @@ -14,7 +14,7 @@ import { RowRendererId, TimelineId } from '../../../../../common/types/timeline' import { BrowserFields, DocValueFields } from '../../../../common/containers/source'; import { TimelineItem } from '../../../../graphql/types'; import { Note } from '../../../../common/lib/note'; -import { appSelectors, State } from '../../../../common/store'; +import { appSelectors, inputsModel, State } from '../../../../common/store'; import { appActions } from '../../../../common/store/actions'; import { useManageTimeline } from '../../manage_timeline'; import { ColumnHeaderOptions, TimelineModel } from '../../../store/timeline/model'; @@ -46,6 +46,7 @@ interface OwnProps { isEventViewer?: boolean; sort: Sort; toggleColumn: (column: ColumnHeaderOptions) => void; + refetch: inputsModel.Refetch; } type StatefulBodyComponentProps = OwnProps & PropsFromRedux; @@ -61,6 +62,7 @@ const StatefulBodyComponent = React.memo( data, docValueFields, eventIdToNoteIds, + eventType, excludedRowRendererIds, id, isEventViewer = false, @@ -76,6 +78,7 @@ const StatefulBodyComponent = React.memo( show, showCheckboxes, graphEventId, + refetch, sort, timelineType, toggleColumn, @@ -195,9 +198,9 @@ const StatefulBodyComponent = React.memo( data={data} docValueFields={docValueFields} eventIdToNoteIds={eventIdToNoteIds} + eventType={eventType} getNotesByIds={getNotesByIds} graphEventId={graphEventId} - id={id} isEventViewer={isEventViewer} isSelectAllChecked={isSelectAllChecked} loadingEventIds={loadingEventIds} @@ -211,11 +214,13 @@ const StatefulBodyComponent = React.memo( onUnPinEvent={onUnPinEvent} onUpdateColumns={onUpdateColumns} pinnedEventIds={pinnedEventIds} + refetch={refetch} rowRenderers={enabledRowRenderers} selectedEventIds={selectedEventIds} show={id === TimelineId.active ? show : true} showCheckboxes={showCheckboxes} sort={sort} + timelineId={id} timelineType={timelineType} toggleColumn={toggleColumn} updateNote={onUpdateNote} @@ -229,6 +234,7 @@ const StatefulBodyComponent = React.memo( deepEqual(prevProps.excludedRowRendererIds, nextProps.excludedRowRendererIds) && deepEqual(prevProps.docValueFields, nextProps.docValueFields) && prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds && + prevProps.eventType === nextProps.eventType && prevProps.graphEventId === nextProps.graphEventId && deepEqual(prevProps.notesById, nextProps.notesById) && prevProps.id === nextProps.id && diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index 86334308558b8..4ab05af5dd6d4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -272,9 +272,6 @@ export interface NewTimelineProps { export const NewTimeline = React.memo( ({ closeGearMenu, outline = false, timelineId, title = i18n.NEW_TIMELINE }) => { - const uiCapabilities = useKibana().services.application.capabilities; - const capabilitiesCanUserCRUD: boolean = !!uiCapabilities.siem.crud; - const { getButton } = useCreateTimelineButton({ timelineId, timelineType: TimelineType.default, @@ -282,7 +279,7 @@ export const NewTimeline = React.memo( }); const button = getButton({ outline, title }); - return capabilitiesCanUserCRUD ? button : null; + return button; } ); NewTimeline.displayName = 'NewTimeline'; @@ -334,26 +331,23 @@ const LargeNotesButton = React.memo(({ noteIds, text, tog LargeNotesButton.displayName = 'LargeNotesButton'; interface SmallNotesButtonProps { - noteIds: string[]; toggleShowNotes: () => void; timelineType: TimelineTypeLiteral; } -const SmallNotesButton = React.memo( - ({ noteIds, toggleShowNotes, timelineType }) => { - const isTemplate = timelineType === TimelineType.template; - - return ( - toggleShowNotes()} - isDisabled={isTemplate} - /> - ); - } -); +const SmallNotesButton = React.memo(({ toggleShowNotes, timelineType }) => { + const isTemplate = timelineType === TimelineType.template; + + return ( + toggleShowNotes()} + isDisabled={isTemplate} + /> + ); +}); SmallNotesButton.displayName = 'SmallNotesButton'; /** @@ -378,11 +372,7 @@ const NotesButtonComponent = React.memo( {size === 'l' ? ( ) : ( - + )} {size === 'l' && showNotes ? ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.tsx index e88ecee81d364..b5aadaa6f1ef8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { TimelineType } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineType } from '../../../../../common/types/timeline'; import { useKibana } from '../../../../common/lib/kibana'; import { useCreateTimelineButton } from './use_create_timeline'; @@ -22,7 +22,7 @@ export const NewTemplateTimelineComponent: React.FC = ({ closeGearMenu, outline, title, - timelineId = 'timeline-1', + timelineId = TimelineId.active, }) => { const uiCapabilities = useKibana().services.application.capabilities; const capabilitiesCanUserCRUD: boolean = !!uiCapabilities.siem.crud; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_right.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_right.tsx index 70257c97a6887..12eab4942128f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_right.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_right.tsx @@ -22,7 +22,6 @@ import { TimelineType, } from '../../../../../common/types/timeline'; import { InspectButton, InspectButtonContainer } from '../../../../common/components/inspect'; -import { useKibana } from '../../../../common/lib/kibana'; import { Note } from '../../../../common/lib/note'; import { AssociateNote } from '../../notes/helpers'; @@ -121,8 +120,6 @@ const PropertiesRightComponent: React.FC = ({ updateNote, usersViewing, }) => { - const uiCapabilities = useKibana().services.application.capabilities; - const capabilitiesCanUserCRUD: boolean = !!uiCapabilities.siem.crud; return ( @@ -143,15 +140,13 @@ const PropertiesRightComponent: React.FC = ({ repositionOnScroll > - {capabilitiesCanUserCRUD && ( - - - - )} + + + = ({ toggleColumn, usersViewing, }) => { - const dispatch = useDispatch(); const kibana = useKibana(); const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); const esQueryConfig = useMemo(() => esQuery.getEsQueryConfig(kibana.services.uiSettings), [ @@ -213,7 +211,10 @@ export const TimelineComponent: React.FC = ({ [isLoadingSource, combinedQueries, start, end] ); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; - const timelineQueryFields = useMemo(() => columnsHeader.map((c) => c.id), [columnsHeader]); + const timelineQueryFields = useMemo(() => { + const columnFields = columnsHeader.map((c) => c.id); + return [...columnFields, ...requiredFieldsForActions]; + }, [columnsHeader]); const timelineQuerySortField = useMemo( () => ({ sortFieldId: sort.columnId, @@ -228,7 +229,6 @@ export const TimelineComponent: React.FC = ({ filterManager, id, indexToAdd, - timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId: id })], }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -317,6 +317,7 @@ export const TimelineComponent: React.FC = ({ data={events} docValueFields={docValueFields} id={id} + refetch={refetch} sort={sort} toggleColumn={toggleColumn} /> diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts index 42c01da7e23c9..8ef740f7bc1d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts @@ -220,6 +220,109 @@ describe('persistTimeline', () => { }); }); + describe('create draft timeline in read-only permission', () => { + const timelineId = null; + const initialDraftTimeline = { + columns: [ + { + columnHeaderType: 'not-filtered', + id: '@timestamp', + }, + { + columnHeaderType: 'not-filtered', + id: 'message', + }, + { + columnHeaderType: 'not-filtered', + id: 'event.category', + }, + { + columnHeaderType: 'not-filtered', + id: 'event.action', + }, + { + columnHeaderType: 'not-filtered', + id: 'host.name', + }, + { + columnHeaderType: 'not-filtered', + id: 'source.ip', + }, + { + columnHeaderType: 'not-filtered', + id: 'destination.ip', + }, + { + columnHeaderType: 'not-filtered', + id: 'user.name', + }, + ], + dataProviders: [], + description: 'x', + eventType: 'all', + filters: [], + kqlMode: 'filter', + kqlQuery: { + filterQuery: null, + }, + title: '', + timelineType: TimelineType.default, + templateTimelineVersion: null, + templateTimelineId: null, + dateRange: { + start: 1590998565409, + end: 1591084965409, + }, + savedQueryId: null, + sort: { + columnId: '@timestamp', + sortDirection: 'desc', + }, + status: TimelineStatus.draft, + }; + + const version = null; + const fetchMock = jest.fn(); + const postMock = jest.fn(); + const patchMock = jest.fn(); + + beforeAll(() => { + jest.resetAllMocks(); + jest.resetModules(); + + (KibanaServices.get as jest.Mock).mockReturnValue({ + http: { + fetch: fetchMock.mockRejectedValue({ + body: { status_code: 403, message: 'you do not have the permission' }, + }), + post: postMock.mockRejectedValue({ + body: { status_code: 403, message: 'you do not have the permission' }, + }), + patch: patchMock.mockRejectedValue({ + body: { status_code: 403, message: 'you do not have the permission' }, + }), + }, + }); + }); + + test('it should return your request timeline with code and message', async () => { + const persist = await api.persistTimeline({ + timelineId, + timeline: initialDraftTimeline, + version, + }); + expect(persist).toEqual({ + data: { + persistTimeline: { + code: 403, + message: 'you do not have the permission', + timeline: { ...initialDraftTimeline, savedObjectId: '', version: '' }, + }, + }, + }); + }); + }); + describe('create active timeline (import)', () => { const timelineId = null; const importTimeline = { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index e08d52066ebdc..c6794d125368e 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -6,6 +6,7 @@ import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; +import isEmpty from 'lodash/isEmpty'; import { throwErrors } from '../../../../case/common/api'; import { @@ -99,44 +100,63 @@ export const persistTimeline = async ({ timeline, version, }: RequestPersistTimeline): Promise => { - if (timelineId == null && timeline.status === TimelineStatus.draft && timeline) { - const draftTimeline = await cleanDraftTimeline({ - timelineType: timeline.timelineType!, - templateTimelineId: timeline.templateTimelineId ?? undefined, - templateTimelineVersion: timeline.templateTimelineVersion ?? undefined, - }); - - const templateTimelineInfo = - timeline.timelineType! === TimelineType.template - ? { - templateTimelineId: - draftTimeline.data.persistTimeline.timeline.templateTimelineId ?? - timeline.templateTimelineId, - templateTimelineVersion: - draftTimeline.data.persistTimeline.timeline.templateTimelineVersion ?? - timeline.templateTimelineVersion, - } - : {}; + try { + if (isEmpty(timelineId) && timeline.status === TimelineStatus.draft && timeline) { + const draftTimeline = await cleanDraftTimeline({ + timelineType: timeline.timelineType!, + templateTimelineId: timeline.templateTimelineId ?? undefined, + templateTimelineVersion: timeline.templateTimelineVersion ?? undefined, + }); + + const templateTimelineInfo = + timeline.timelineType! === TimelineType.template + ? { + templateTimelineId: + draftTimeline.data.persistTimeline.timeline.templateTimelineId ?? + timeline.templateTimelineId, + templateTimelineVersion: + draftTimeline.data.persistTimeline.timeline.templateTimelineVersion ?? + timeline.templateTimelineVersion, + } + : {}; + + return patchTimeline({ + timelineId: draftTimeline.data.persistTimeline.timeline.savedObjectId, + timeline: { + ...timeline, + ...templateTimelineInfo, + }, + version: draftTimeline.data.persistTimeline.timeline.version ?? '', + }); + } + + if (isEmpty(timelineId)) { + return postTimeline({ timeline }); + } return patchTimeline({ - timelineId: draftTimeline.data.persistTimeline.timeline.savedObjectId, - timeline: { - ...timeline, - ...templateTimelineInfo, - }, - version: draftTimeline.data.persistTimeline.timeline.version ?? '', + timelineId: timelineId ?? '-1', + timeline, + version: version ?? '', }); + } catch (err) { + if (err.status_code === 403 || err.body.status_code === 403) { + return Promise.resolve({ + data: { + persistTimeline: { + code: 403, + message: err.message || err.body.message, + timeline: { + ...timeline, + savedObjectId: '', + version: '', + }, + }, + }, + }); + } + return Promise.resolve(err); } - - if (timelineId == null) { - return postTimeline({ timeline }); - } - - return patchTimeline({ - timelineId, - timeline, - version: version ?? '', - }); }; export const importTimelines = async ({ diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/index.gql_query.ts index 5a162fd2206a1..c67ad45bede94 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.gql_query.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.gql_query.ts @@ -200,6 +200,7 @@ export const timelineQuery = gql` country_iso_code } signal { + status original_time rule { id diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index c22acf6ba7cc1..79d0f909c7d59 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useState } from 'react'; import styled from 'styled-components'; import { useParams } from 'react-router-dom'; -import { TimelineType } from '../../../common/types/timeline'; +import { TimelineId, TimelineType } from '../../../common/types/timeline'; import { HeaderPage } from '../../common/components/header_page'; import { WrapperPage } from '../../common/components/wrapper_page'; import { useKibana } from '../../common/lib/kibana'; @@ -64,13 +64,11 @@ export const TimelinesPageComponent: React.FC = () => { {tabName === TimelineType.default ? ( - {capabilitiesCanUserCRUD && ( - - )} + ) : ( diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index 06dd6f44bea94..8c3f30c75c35b 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -42,7 +42,7 @@ import { Direction } from '../../../graphql/types'; import { addTimelineInStorage } from '../../containers/local_storage'; import { isPageTimeline } from './epic_local_storage'; -import { TimelineStatus, TimelineType } from '../../../../common/types/timeline'; +import { TimelineId, TimelineStatus, TimelineType } from '../../../../common/types/timeline'; jest.mock('../../containers/local_storage'); @@ -115,7 +115,7 @@ describe('epicLocalStorage', () => { }); it('filters correctly page timelines', () => { - expect(isPageTimeline('timeline-1')).toBe(false); + expect(isPageTimeline(TimelineId.active)).toBe(false); expect(isPageTimeline('hosts-page-alerts')).toBe(true); }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/README.md b/x-pack/plugins/security_solution/scripts/endpoint/README.md index bd9502f2f59e0..2827ab065504b 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/README.md +++ b/x-pack/plugins/security_solution/scripts/endpoint/README.md @@ -5,10 +5,6 @@ The default behavior is to create 1 endpoint with 1 alert and a moderate number A seed value can be provided as a string for the random number generator for repeatable behavior, useful for demos etc. Use the `-d` option if you want to delete and remake the indices, otherwise it will add documents to existing indices. -The sample data generator script depends on ts-node, install with npm: - -`npm install -g ts-node` - Example command sequence to get ES and kibana running with sample data after installing ts-node: `yarn es snapshot` -> starts ES diff --git a/x-pack/plugins/security_solution/scripts/endpoint/cli_tsconfig.json b/x-pack/plugins/security_solution/scripts/endpoint/cli_tsconfig.json deleted file mode 100644 index 5c68f8ee0abf2..0000000000000 --- a/x-pack/plugins/security_solution/scripts/endpoint/cli_tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../../../tsconfig.json", - "compilerOptions": { - "target": "es2019", - "resolveJsonModule": true - } - } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.d.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.js similarity index 73% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.d.ts rename to x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.js index fa1b1129523eb..fb5a865439bc2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.d.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export declare const PolicyTable: any; +require('../../../../../src/setup_node_env'); +require('./resolver_generator_script'); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts similarity index 100% rename from x-pack/plugins/security_solution/scripts/endpoint/resolver_generator.ts rename to x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts diff --git a/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json b/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json index ea7a11b89dab2..ac56a6af31c72 100644 --- a/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json +++ b/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json @@ -10,7 +10,6 @@ "exclude": [ "test/**/*", "**/__fixtures__/**/*", - "plugins/security_solution/cypress/**/*", - "**/typespec_tests.ts" + "plugins/security_solution/cypress/**/*" ] } diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts index 73f2124736a79..c28ffcf5b7a3f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts @@ -49,6 +49,36 @@ describe('ingest_integration tests ', () => { relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', }, + 'endpoint-trustlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + decoded_size: 287, + encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c', + encoded_size: 133, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + }, + 'endpoint-trustlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + decoded_size: 287, + encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c', + encoded_size: 133, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + }, + 'endpoint-trustlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + decoded_size: 287, + encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c', + encoded_size: 133, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + }, }, manifest_version: '1.0.0', schema_version: 'v1', diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts index 7f90aa7b91063..457e50b686863 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts @@ -14,6 +14,8 @@ export const ArtifactConstants = { GLOBAL_ALLOWLIST_NAME: 'endpoint-exceptionlist', SAVED_OBJECT_TYPE: 'endpoint:user-artifact', SUPPORTED_OPERATING_SYSTEMS: ['macos', 'windows'], + SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS: ['macos', 'windows', 'linux'], + GLOBAL_TRUSTED_APPS_NAME: 'endpoint-trustlist', }; export const ManifestConstants = { diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts index fea3b2b9a4526..a10ba9d6be38c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts @@ -11,6 +11,8 @@ import { getExceptionListItemSchemaMock } from '../../../../../lists/common/sche import { EntriesArray, EntryList } from '../../../../../lists/common/schemas/types'; import { buildArtifact, getFullEndpointExceptionList } from './lists'; import { TranslatedEntry, TranslatedExceptionListItem } from '../../schemas/artifacts'; +import { ArtifactConstants } from './common'; +import { ENDPOINT_LIST_ID } from '../../../../../lists/common'; describe('buildEventTypeSignal', () => { let mockExceptionClient: ExceptionListClient; @@ -47,7 +49,12 @@ describe('buildEventTypeSignal', () => { const first = getFoundExceptionListItemSchemaMock(); mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp).toEqual({ entries: [expectedEndpointExceptions], }); @@ -88,7 +95,12 @@ describe('buildEventTypeSignal', () => { first.data[0].entries = testEntries; mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp).toEqual({ entries: [expectedEndpointExceptions], }); @@ -134,7 +146,12 @@ describe('buildEventTypeSignal', () => { first.data[0].entries = testEntries; mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp).toEqual({ entries: [expectedEndpointExceptions], }); @@ -182,7 +199,12 @@ describe('buildEventTypeSignal', () => { first.data[0].entries = testEntries; mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp).toEqual({ entries: [expectedEndpointExceptions], }); @@ -229,7 +251,12 @@ describe('buildEventTypeSignal', () => { first.data[0].entries = testEntries; mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp).toEqual({ entries: [expectedEndpointExceptions], }); @@ -267,7 +294,12 @@ describe('buildEventTypeSignal', () => { first.data[1].entries = testEntries; mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp).toEqual({ entries: [expectedEndpointExceptions], }); @@ -305,7 +337,12 @@ describe('buildEventTypeSignal', () => { first.data[0].entries = testEntries; mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp).toEqual({ entries: [expectedEndpointExceptions], }); @@ -329,7 +366,12 @@ describe('buildEventTypeSignal', () => { .mockReturnValueOnce(first) .mockReturnValueOnce(second); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); // Expect 2 exceptions, the first two calls returned the same exception list items expect(resp.entries.length).toEqual(2); @@ -340,7 +382,12 @@ describe('buildEventTypeSignal', () => { exceptionsResponse.data = []; exceptionsResponse.total = 0; mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionsResponse); - const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1'); + const resp = await getFullEndpointExceptionList( + mockExceptionClient, + 'linux', + 'v1', + ENDPOINT_LIST_ID + ); expect(resp.entries.length).toEqual(0); }); @@ -385,8 +432,18 @@ describe('buildEventTypeSignal', () => { ], }; - const artifact1 = await buildArtifact(translatedExceptionList, 'linux', 'v1'); - const artifact2 = await buildArtifact(translatedExceptionListReversed, 'linux', 'v1'); + const artifact1 = await buildArtifact( + translatedExceptionList, + 'linux', + 'v1', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + const artifact2 = await buildArtifact( + translatedExceptionListReversed, + 'linux', + 'v1', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); }); @@ -430,8 +487,18 @@ describe('buildEventTypeSignal', () => { entries: translatedItems.reverse(), }; - const artifact1 = await buildArtifact(translatedExceptionList, 'linux', 'v1'); - const artifact2 = await buildArtifact(translatedExceptionListReversed, 'linux', 'v1'); + const artifact1 = await buildArtifact( + translatedExceptionList, + 'linux', + 'v1', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + const artifact2 = await buildArtifact( + translatedExceptionListReversed, + 'linux', + 'v1', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts index e41781dd605a0..731b083f3293c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts @@ -28,19 +28,20 @@ import { internalArtifactCompleteSchema, InternalArtifactCompleteSchema, } from '../../schemas'; -import { ArtifactConstants } from './common'; +import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; export async function buildArtifact( exceptions: WrappedTranslatedExceptionList, os: string, - schemaVersion: string + schemaVersion: string, + name: string ): Promise { const exceptionsBuffer = Buffer.from(JSON.stringify(exceptions)); const sha256 = createHash('sha256').update(exceptionsBuffer.toString()).digest('hex'); // Keep compression info empty in case its a duplicate. Lazily compress before committing if needed. return { - identifier: `${ArtifactConstants.GLOBAL_ALLOWLIST_NAME}-${os}-${schemaVersion}`, + identifier: `${name}-${os}-${schemaVersion}`, compressionAlgorithm: 'none', encryptionAlgorithm: 'none', decodedSha256: sha256, @@ -76,7 +77,8 @@ export function isCompressed(artifact: InternalArtifactSchema) { export async function getFullEndpointExceptionList( eClient: ExceptionListClient, os: string, - schemaVersion: string + schemaVersion: string, + listId: typeof ENDPOINT_LIST_ID | typeof ENDPOINT_TRUSTED_APPS_LIST_ID ): Promise { const exceptions: WrappedTranslatedExceptionList = { entries: [] }; let page = 1; @@ -84,7 +86,7 @@ export async function getFullEndpointExceptionList( while (paging) { const response = await eClient.findExceptionListItem({ - listId: ENDPOINT_LIST_ID, + listId, namespaceType: 'agnostic', filter: `exception-list-agnostic.attributes._tags:\"os:${os}\"`, perPage: 100, diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts index 3d70f7266277f..507b81b12e10b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/manifest.test.ts @@ -94,6 +94,36 @@ describe('manifest', () => { relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', }, + 'endpoint-trustlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + decoded_size: 432, + encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e', + encoded_size: 147, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + }, + 'endpoint-trustlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + decoded_size: 432, + encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e', + encoded_size: 147, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + }, + 'endpoint-trustlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + decoded_size: 432, + encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e', + encoded_size: 147, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + }, }, manifest_version: '1.0.0', schema_version: 'v1', @@ -107,6 +137,9 @@ describe('manifest', () => { ids: [ 'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', 'endpoint-exceptionlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + 'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + 'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + 'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', ], }); }); @@ -119,6 +152,21 @@ describe('manifest', () => { 'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', type: 'delete', }, + { + id: + 'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, + { + id: + 'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, + { + id: + 'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, { id: 'endpoint-exceptionlist-macos-v1-0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8', @@ -139,6 +187,9 @@ describe('manifest', () => { expect(keys).toEqual([ 'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', 'endpoint-exceptionlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + 'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + 'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + 'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', ]); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts index 61850bfb3bc7d..cdfbb551940e1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts @@ -16,13 +16,20 @@ import { ArtifactConstants } from './common'; import { Manifest } from './manifest'; export const getMockArtifacts = async (opts?: { compress: boolean }) => { - return Promise.all( - ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.map>( + return Promise.all([ + // Exceptions items + ...ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.map>( async (os) => { return getInternalArtifactMock(os, 'v1', opts); } - ) - ); + ), + // Trusted Apps items + ...ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS.map< + Promise + >(async (os) => { + return getInternalArtifactMock(os, 'v1', opts, ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME); + }), + ]); }; export const getMockArtifactsWithDiff = async (opts?: { compress: boolean }) => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts index b233ff1af30fc..5993b0b0e752e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts @@ -11,6 +11,8 @@ import { getHostPolicyResponseHandler } from './handlers'; export const BASE_POLICY_RESPONSE_ROUTE = `/api/endpoint/policy_response`; +export const INITIAL_POLICY_ID = '00000000-0000-0000-0000-000000000000'; + export function registerPolicyRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { router.get( { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.test.ts index 7c8d006687a6b..f05d9ef5b821a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.test.ts @@ -5,6 +5,7 @@ */ import { GetPolicyResponseSchema } from '../../../../common/endpoint/schema/policy'; +import { getESQueryPolicyResponseByHostID } from './service'; describe('test policy handlers schema', () => { it('validate that get policy response query schema', async () => { @@ -17,3 +18,21 @@ describe('test policy handlers schema', () => { expect(() => GetPolicyResponseSchema.query.validate({})).toThrowError(); }); }); + +describe('test policy query', () => { + it('queries for the correct host', async () => { + const hostID = 'f757d3c0-e874-11ea-9ad9-015510b487f4'; + const query = getESQueryPolicyResponseByHostID(hostID, 'anyindex'); + expect(query.body.query.bool.filter.term).toEqual({ 'host.id': hostID }); + }); + + it('filters out initial policy by ID', async () => { + const query = getESQueryPolicyResponseByHostID( + 'f757d3c0-e874-11ea-9ad9-015510b487f4', + 'anyindex' + ); + expect(query.body.query.bool.must_not.term).toEqual({ + 'Endpoint.policy.applied.id': '00000000-0000-0000-0000-000000000000', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts index 703c46b05f766..1b3d232f9421c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts @@ -7,13 +7,23 @@ import { SearchResponse } from 'elasticsearch'; import { ILegacyScopedClusterClient } from 'kibana/server'; import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/endpoint/types'; +import { INITIAL_POLICY_ID } from './index'; export function getESQueryPolicyResponseByHostID(hostID: string, index: string) { return { body: { query: { - match: { - 'host.id': hostID, + bool: { + filter: { + term: { + 'host.id': hostID, + }, + }, + must_not: { + term: { + 'Endpoint.policy.applied.id': INITIAL_POLICY_ID, + }, + }, }, }, sort: [ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts index 6c29a2244c203..977683ab55495 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts @@ -8,9 +8,10 @@ import { RequestHandler } from 'kibana/server'; import { GetTrustedAppsListRequest, GetTrustedListAppsResponse, + PostTrustedAppCreateRequest, } from '../../../../common/endpoint/types'; import { EndpointAppContext } from '../../types'; -import { exceptionItemToTrustedAppItem } from './utils'; +import { exceptionItemToTrustedAppItem, newTrustedAppItemToExceptionItem } from './utils'; import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; export const getTrustedAppsListRouteHandler = ( @@ -24,7 +25,7 @@ export const getTrustedAppsListRouteHandler = ( try { // Ensure list is created if it does not exist - await exceptionsListService?.createTrustedAppsList(); + await exceptionsListService.createTrustedAppsList(); const results = await exceptionsListService.findExceptionListItem({ listId: ENDPOINT_TRUSTED_APPS_LIST_ID, page, @@ -47,3 +48,32 @@ export const getTrustedAppsListRouteHandler = ( } }; }; + +export const getTrustedAppsCreateRouteHandler = ( + endpointAppContext: EndpointAppContext +): RequestHandler => { + const logger = endpointAppContext.logFactory.get('trusted_apps'); + + return async (constext, req, res) => { + const exceptionsListService = endpointAppContext.service.getExceptionsList(); + const newTrustedApp = req.body; + + try { + // Ensure list is created if it does not exist + await exceptionsListService.createTrustedAppsList(); + + const createdTrustedAppExceptionItem = await exceptionsListService.createExceptionListItem( + newTrustedAppItemToExceptionItem(newTrustedApp) + ); + + return res.ok({ + body: { + data: exceptionItemToTrustedAppItem(createdTrustedAppExceptionItem), + }, + }); + } catch (error) { + logger.error(error); + return res.internalError({ body: error }); + } + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts index 178aa06eee877..1302b10533ccf 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts @@ -5,9 +5,15 @@ */ import { IRouter } from 'kibana/server'; -import { GetTrustedAppsRequestSchema } from '../../../../common/endpoint/schema/trusted_apps'; -import { TRUSTED_APPS_LIST_API } from '../../../../common/endpoint/constants'; -import { getTrustedAppsListRouteHandler } from './handlers'; +import { + GetTrustedAppsRequestSchema, + PostTrustedAppCreateRequestSchema, +} from '../../../../common/endpoint/schema/trusted_apps'; +import { + TRUSTED_APPS_CREATE_API, + TRUSTED_APPS_LIST_API, +} from '../../../../common/endpoint/constants'; +import { getTrustedAppsCreateRouteHandler, getTrustedAppsListRouteHandler } from './handlers'; import { EndpointAppContext } from '../../types'; export const registerTrustedAppsRoutes = ( @@ -23,4 +29,14 @@ export const registerTrustedAppsRoutes = ( }, getTrustedAppsListRouteHandler(endpointAppContext) ); + + // CREATE + router.post( + { + path: TRUSTED_APPS_CREATE_API, + validate: PostTrustedAppCreateRequestSchema, + options: { authRequired: true }, + }, + getTrustedAppsCreateRouteHandler(endpointAppContext) + ); }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts index 1d4a7919b89f5..488c8390411b0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts @@ -12,12 +12,20 @@ import { import { IRouter, RequestHandler } from 'kibana/server'; import { httpServerMock, httpServiceMock } from '../../../../../../../src/core/server/mocks'; import { registerTrustedAppsRoutes } from './index'; -import { TRUSTED_APPS_LIST_API } from '../../../../common/endpoint/constants'; -import { GetTrustedAppsListRequest } from '../../../../common/endpoint/types'; +import { + TRUSTED_APPS_CREATE_API, + TRUSTED_APPS_LIST_API, +} from '../../../../common/endpoint/constants'; +import { + GetTrustedAppsListRequest, + PostTrustedAppCreateRequest, +} from '../../../../common/endpoint/types'; import { xpackMocks } from '../../../../../../mocks'; import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; import { EndpointAppContext } from '../../types'; import { ExceptionListClient } from '../../../../../lists/server'; +import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; +import { ExceptionListItemSchema } from '../../../../../lists/common/schemas/response'; describe('when invoking endpoint trusted apps route handlers', () => { let routerMock: jest.Mocked; @@ -105,4 +113,111 @@ describe('when invoking endpoint trusted apps route handlers', () => { expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled(); }); }); + + describe('when creating a trusted app', () => { + let routeHandler: RequestHandler; + const createNewTrustedAppBody = (): PostTrustedAppCreateRequest => ({ + name: 'Some Anti-Virus App', + description: 'this one is ok', + os: 'windows', + entries: [ + { + field: 'path', + type: 'match', + operator: 'included', + value: 'c:/programs files/Anti-Virus', + }, + ], + }); + const createPostRequest = () => { + return httpServerMock.createKibanaRequest({ + path: TRUSTED_APPS_LIST_API, + method: 'post', + body: createNewTrustedAppBody(), + }); + }; + + beforeEach(() => { + // Get the registered POST handler from the IRouter instance + [, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => + path.startsWith(TRUSTED_APPS_CREATE_API) + )!; + + // Mock the impelementation of `createExceptionListItem()` so that the return value + // merges in the provided input + exceptionsListClient.createExceptionListItem.mockImplementation(async (newExceptionItem) => { + return ({ + ...getExceptionListItemSchemaMock(), + ...newExceptionItem, + } as unknown) as ExceptionListItemSchema; + }); + }); + + it('should create trusted app list first', async () => { + const request = createPostRequest(); + await routeHandler(context, request, response); + expect(exceptionsListClient.createTrustedAppsList).toHaveBeenCalled(); + expect(response.ok).toHaveBeenCalled(); + }); + + it('should map new trusted app item to an exception list item', async () => { + const request = createPostRequest(); + await routeHandler(context, request, response); + expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0]).toEqual({ + _tags: ['os:windows'], + comments: [], + description: 'this one is ok', + entries: [ + { + field: 'path', + operator: 'included', + type: 'match', + value: 'c:/programs files/Anti-Virus', + }, + ], + itemId: expect.stringMatching(/.*/), + listId: 'endpoint_trusted_apps', + meta: undefined, + name: 'Some Anti-Virus App', + namespaceType: 'agnostic', + tags: [], + type: 'simple', + }); + }); + + it('should return new trusted app item', async () => { + const request = createPostRequest(); + await routeHandler(context, request, response); + expect(response.ok.mock.calls[0][0]).toEqual({ + body: { + data: { + created_at: '2020-04-20T15:25:31.830Z', + created_by: 'some user', + description: 'this one is ok', + entries: [ + { + field: 'path', + operator: 'included', + type: 'match', + value: 'c:/programs files/Anti-Virus', + }, + ], + id: '1', + name: 'Some Anti-Virus App', + os: 'windows', + }, + }, + }); + }); + + it('should log unexpected error if one occurs', async () => { + exceptionsListClient.createExceptionListItem.mockImplementation(() => { + throw new Error('expected error for create'); + }); + const request = createPostRequest(); + await routeHandler(context, request, response); + expect(response.internalError).toHaveBeenCalled(); + expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts index 2b417a4c6a8e1..794c1db4b49aa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts @@ -4,8 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import uuid from 'uuid'; import { ExceptionListItemSchema } from '../../../../../lists/common/shared_exports'; -import { TrustedApp } from '../../../../common/endpoint/types'; +import { NewTrustedApp, TrustedApp } from '../../../../common/endpoint/types'; +import { ExceptionListClient } from '../../../../../lists/server'; +import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; + +type NewExecptionItem = Parameters[0]; /** * Map an ExcptionListItem to a TrustedApp item @@ -40,3 +45,28 @@ const osFromTagsList = (tags: string[]): TrustedApp['os'] | 'unknown' => { } return 'unknown'; }; + +export const newTrustedAppItemToExceptionItem = ({ + os, + entries, + name, + description = '', +}: NewTrustedApp): NewExecptionItem => { + return { + _tags: tagsListFromOs(os), + comments: [], + description, + entries, + itemId: uuid.v4(), + listId: ENDPOINT_TRUSTED_APPS_LIST_ID, + meta: undefined, + name, + namespaceType: 'agnostic', + tags: [], + type: 'simple', + }; +}; + +const tagsListFromOs = (os: NewTrustedApp['os']): NewExecptionItem['_tags'] => { + return [`os:${os}`]; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts index ae565f785c399..5d46fd0f2456b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { buildArtifact, maybeCompressArtifact, isCompressed } from '../../lib/artifacts'; +import { + buildArtifact, + maybeCompressArtifact, + isCompressed, + ArtifactConstants, +} from '../../lib/artifacts'; import { getTranslatedExceptionListMock } from './lists.mock'; import { InternalManifestSchema, @@ -25,9 +30,15 @@ const compressArtifact = async (artifact: InternalArtifactCompleteSchema) => { export const getInternalArtifactMock = async ( os: string, schemaVersion: string, - opts?: { compress: boolean } + opts?: { compress: boolean }, + artifactName: string = ArtifactConstants.GLOBAL_ALLOWLIST_NAME ): Promise => { - const artifact = await buildArtifact(getTranslatedExceptionListMock(), os, schemaVersion); + const artifact = await buildArtifact( + getTranslatedExceptionListMock(), + os, + schemaVersion, + artifactName + ); return opts?.compress ? compressArtifact(artifact) : artifact; }; @@ -36,7 +47,12 @@ export const getEmptyInternalArtifactMock = async ( schemaVersion: string, opts?: { compress: boolean } ): Promise => { - const artifact = await buildArtifact({ entries: [] }, os, schemaVersion); + const artifact = await buildArtifact( + { entries: [] }, + os, + schemaVersion, + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); return opts?.compress ? compressArtifact(artifact) : artifact; }; @@ -47,7 +63,12 @@ export const getInternalArtifactMockWithDiffs = async ( ): Promise => { const mock = getTranslatedExceptionListMock(); mock.entries.pop(); - const artifact = await buildArtifact(mock, os, schemaVersion); + const artifact = await buildArtifact( + mock, + os, + schemaVersion, + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); return opts?.compress ? compressArtifact(artifact) : artifact; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index bb6504de6e0a4..40b408166b17f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -24,11 +24,41 @@ describe('manifest_manager', () => { 'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', type: 'delete', }, + { + id: + 'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, + { + id: + 'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, + { + id: + 'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, { id: 'endpoint-exceptionlist-macos-v1-0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8', type: 'add', }, + { + id: + 'endpoint-trustlist-macos-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + type: 'add', + }, + { + id: + 'endpoint-trustlist-windows-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + type: 'add', + }, + { + id: + 'endpoint-trustlist-linux-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + type: 'add', + }, ]); }); @@ -44,16 +74,53 @@ describe('manifest_manager', () => { 'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', type: 'delete', }, + { + id: + 'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, + { + id: + 'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, + { + id: + 'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', + type: 'delete', + }, { id: 'endpoint-exceptionlist-macos-v1-0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8', type: 'add', }, + { + id: + 'endpoint-trustlist-macos-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + type: 'add', + }, + { + id: + 'endpoint-trustlist-windows-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + type: 'add', + }, + { + id: + 'endpoint-trustlist-linux-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + type: 'add', + }, ]); - const newArtifactId = diffs[1].id; - await newManifest.compressArtifact(newArtifactId); - const artifact = newManifest.getArtifact(newArtifactId)!; + const firstNewArtifactId = diffs.find((diff) => diff.type === 'add')!.id; + + // Compress all `add` artifacts + for (const artifactDiff of diffs) { + if (artifactDiff.type === 'add') { + await newManifest.compressArtifact(artifactDiff.id); + } + } + + const artifact = newManifest.getArtifact(firstNewArtifactId)!; if (isCompleteArtifact(artifact)) { await manifestManager.pushArtifacts([artifact]); // caches the artifact @@ -61,7 +128,7 @@ describe('manifest_manager', () => { throw new Error('Artifact is missing a body.'); } - const entry = JSON.parse(inflateSync(cache.get(newArtifactId)! as Buffer).toString()); + const entry = JSON.parse(inflateSync(cache.get(firstNewArtifactId)! as Buffer).toString()); expect(entry).toEqual({ entries: [ { @@ -107,8 +174,12 @@ describe('manifest_manager', () => { const oldManifest = await manifestManager.getLastComputedManifest(); const newManifest = await manifestManager.buildNewManifest(oldManifest!); const diffs = newManifest.diff(oldManifest!); - const newArtifactId = diffs[1].id; - await newManifest.compressArtifact(newArtifactId); + + for (const artifactDiff of diffs) { + if (artifactDiff.type === 'add') { + await newManifest.compressArtifact(artifactDiff.id); + } + } newManifest.bumpSemanticVersion(); @@ -145,6 +216,36 @@ describe('manifest_manager', () => { relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3', }, + 'endpoint-trustlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + decoded_size: 287, + encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c', + encoded_size: 133, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + }, + 'endpoint-trustlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + decoded_size: 287, + encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c', + encoded_size: 133, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + }, + 'endpoint-trustlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + decoded_size: 287, + encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c', + encoded_size: 133, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc', + }, }, }); }); @@ -155,8 +256,12 @@ describe('manifest_manager', () => { const oldManifest = await manifestManager.getLastComputedManifest(); const newManifest = await manifestManager.buildNewManifest(oldManifest!); const diffs = newManifest.diff(oldManifest!); - const newArtifactId = diffs[1].id; - await newManifest.compressArtifact(newArtifactId); + + for (const artifactDiff of diffs) { + if (artifactDiff.type === 'add') { + await newManifest.compressArtifact(artifactDiff.id); + } + } newManifest.bumpSemanticVersion(); @@ -174,11 +279,17 @@ describe('manifest_manager', () => { const oldManifest = await manifestManager.getLastComputedManifest(); const newManifest = await manifestManager.buildNewManifest(oldManifest!); const diffs = newManifest.diff(oldManifest!); - const oldArtifactId = diffs[0].id; - const newArtifactId = diffs[1].id; - await newManifest.compressArtifact(newArtifactId); + const firstOldArtifactId = diffs.find((diff) => diff.type === 'delete')!.id; + const FirstNewArtifactId = diffs.find((diff) => diff.type === 'add')!.id; + + // Compress all new artifacts + for (const artifactDiff of diffs) { + if (artifactDiff.type === 'add') { + await newManifest.compressArtifact(artifactDiff.id); + } + } - const artifact = newManifest.getArtifact(newArtifactId)!; + const artifact = newManifest.getArtifact(FirstNewArtifactId)!; if (isCompleteArtifact(artifact)) { await manifestManager.pushArtifacts([artifact]); } else { @@ -186,7 +297,7 @@ describe('manifest_manager', () => { } await manifestManager.commit(newManifest); - await manifestManager.deleteArtifacts([oldArtifactId]); + await manifestManager.deleteArtifacts([firstOldArtifactId]); // created new artifact expect(savedObjectsClient.create.mock.calls[0][0]).toEqual( @@ -201,7 +312,7 @@ describe('manifest_manager', () => { // deleted old artifact expect(savedObjectsClient.delete).toHaveBeenCalledWith( ArtifactConstants.SAVED_OBJECT_TYPE, - oldArtifactId + firstOldArtifactId ); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 70557886e57c5..f9836c7ecdc30 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -13,11 +13,11 @@ import { manifestDispatchSchema } from '../../../../../common/endpoint/schema/ma import { ArtifactConstants, - Manifest, buildArtifact, + getArtifactId, getFullEndpointExceptionList, + Manifest, ManifestDiff, - getArtifactId, } from '../../../lib/artifacts'; import { InternalArtifactCompleteSchema, @@ -25,6 +25,8 @@ import { } from '../../../schemas/artifacts'; import { ArtifactClient } from '../artifact_client'; import { ManifestClient } from '../manifest_client'; +import { ENDPOINT_LIST_ID } from '../../../../../../lists/common'; +import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../../lists/common/constants'; export interface ManifestManagerContext { savedObjectsClient: SavedObjectsClientContract; @@ -87,9 +89,43 @@ export class ManifestManager { const exceptionList = await getFullEndpointExceptionList( this.exceptionListClient, os, - artifactSchemaVersion ?? 'v1' + artifactSchemaVersion ?? 'v1', + ENDPOINT_LIST_ID + ); + const artifact = await buildArtifact( + exceptionList, + os, + artifactSchemaVersion ?? 'v1', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + artifacts.push(artifact); + } + return artifacts; + } + + /** + * Builds an array of artifacts (one per supported OS) based on the current state of the + * Trusted Apps list (which uses the `exception-list-agnostic` SO type) + * @param artifactSchemaVersion + */ + protected async buildTrustedAppsArtifacts( + artifactSchemaVersion?: string + ): Promise { + const artifacts: InternalArtifactCompleteSchema[] = []; + + for (const os of ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS) { + const trustedApps = await getFullEndpointExceptionList( + this.exceptionListClient, + os, + artifactSchemaVersion ?? 'v1', + ENDPOINT_TRUSTED_APPS_LIST_ID + ); + const artifact = await buildArtifact( + trustedApps, + os, + 'v1', + ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME ); - const artifact = await buildArtifact(exceptionList, os, artifactSchemaVersion ?? 'v1'); artifacts.push(artifact); } return artifacts; @@ -205,7 +241,9 @@ export class ManifestManager { */ public async buildNewManifest(baselineManifest?: Manifest): Promise { // Build new exception list artifacts - const artifacts = await this.buildExceptionListArtifacts(); + const artifacts = ( + await Promise.all([this.buildExceptionListArtifacts(), this.buildTrustedAppsArtifacts()]) + ).flat(); // Build new manifest const manifest = Manifest.fromArtifacts( diff --git a/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts index bdc69f85d3542..60c2ce8ceca64 100644 --- a/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts @@ -424,6 +424,7 @@ export const ecsSchema = gql` type SignalField { rule: RuleField original_time: ToStringArray + status: ToStringArray } type RuleEcsField { diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index fa55af351651e..7638ebd03f6b1 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -1022,6 +1022,8 @@ export interface SignalField { rule?: Maybe; original_time?: Maybe; + + status?: Maybe; } export interface RuleField { @@ -4930,6 +4932,8 @@ export namespace SignalFieldResolvers { rule?: RuleResolver, TypeParent, TContext>; original_time?: OriginalTimeResolver, TypeParent, TContext>; + + status?: StatusResolver, TypeParent, TContext>; } export type RuleResolver< @@ -4942,6 +4946,11 @@ export namespace SignalFieldResolvers { Parent = SignalField, TContext = SiemContext > = Resolver; + export type StatusResolver< + R = Maybe, + Parent = SignalField, + TContext = SiemContext + > = Resolver; } export namespace RuleFieldResolvers { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts index 18a64a12431b8..bd9bf50688b58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts @@ -6,13 +6,12 @@ import dateMath from '@elastic/datemath'; -import { ILegacyScopedClusterClient, KibanaRequest } from '../../../../../../../src/core/server'; +import { KibanaRequest } from '../../../../../../../src/core/server'; import { MlPluginSetup } from '../../../../../ml/server'; import { getAnomalies } from '../../machine_learning'; export const findMlSignals = async ({ ml, - clusterClient, request, jobId, anomalyThreshold, @@ -20,14 +19,13 @@ export const findMlSignals = async ({ to, }: { ml: MlPluginSetup; - clusterClient: ILegacyScopedClusterClient; request: KibanaRequest; jobId: string; anomalyThreshold: number; from: string; to: string; }) => { - const { mlAnomalySearch } = ml.mlSystemProvider(clusterClient, request); + const { mlAnomalySearch } = ml.mlSystemProvider(request); const params = { jobIds: [jobId], threshold: anomalyThreshold, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index c5124edcaf187..9d688736a9846 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -185,12 +185,12 @@ export const signalRulesAlertType = ({ ); } - const scopedClusterClient = services.getLegacyScopedClusterClient(ml.mlClient); // Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is // currently unused by the jobsSummary function. - const summaryJobs = await ( - await ml.jobServiceProvider(scopedClusterClient, ({} as unknown) as KibanaRequest) - ).jobsSummary([machineLearningJobId]); + const fakeRequest = {} as KibanaRequest; + const summaryJobs = await ml + .jobServiceProvider(fakeRequest) + .jobsSummary([machineLearningJobId]); const jobSummary = summaryJobs.find((job) => job.id === machineLearningJobId); if (jobSummary == null || !isJobStarted(jobSummary.jobState, jobSummary.datafeedState)) { @@ -207,7 +207,6 @@ export const signalRulesAlertType = ({ const anomalyResults = await findMlSignals({ ml, - clusterClient: scopedClusterClient, // Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is // currently unused by the mlAnomalySearch function. request: ({} as unknown) as KibanaRequest, diff --git a/x-pack/plugins/security_solution/server/lib/ecs_fields/index.ts b/x-pack/plugins/security_solution/server/lib/ecs_fields/index.ts index 19b16bd4bc6d2..d1c8290b3462d 100644 --- a/x-pack/plugins/security_solution/server/lib/ecs_fields/index.ts +++ b/x-pack/plugins/security_solution/server/lib/ecs_fields/index.ts @@ -324,6 +324,7 @@ export const signalFieldsMap: Readonly> = { 'signal.rule.note': 'signal.rule.note', 'signal.rule.threshold': 'signal.rule.threshold', 'signal.rule.exceptions_list': 'signal.rule.exceptions_list', + 'signal.status': 'signal.status', }; export const ruleFieldsMap: Readonly> = { diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts index 98de9536b1baa..386eda5281f0c 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts @@ -114,7 +114,6 @@ export const isMlAdmin = async ({ request: KibanaRequest; ml: MlPluginSetup; }): Promise => { - const scopedMlClient = ml.mlClient.asScoped(request); - const mlCapabilities = await ml.mlSystemProvider(scopedMlClient, request).mlCapabilities(); + const mlCapabilities = await ml.mlSystemProvider(request).mlCapabilities(); return hasMlAdminPermissions(mlCapabilities); }; diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts index ad2f1e5a8285c..c17d9ab1bb46e 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/index.ts @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse, SearchParams } from 'elasticsearch'; +import { SearchResponse } from 'elasticsearch'; +import { RequestParams } from '@elastic/elasticsearch'; import { AnomalyRecordDoc as Anomaly } from '../../../../ml/server'; export { Anomaly }; export type AnomalyResults = SearchResponse; -type MlAnomalySearch = (searchParams: SearchParams) => Promise>; +type MlAnomalySearch = (searchParams: RequestParams.Search) => Promise>; export interface AnomaliesSearchParams { jobIds: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/import_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/import_timelines.ts index 245146dda183f..90d5b538a5200 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/import_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/import_timelines.ts @@ -5,7 +5,7 @@ */ import { omit } from 'lodash/fp'; -import { TimelineType, TimelineStatus } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineType, TimelineStatus } from '../../../../../common/types/timeline'; export const mockDuplicateIdErrors = []; @@ -332,8 +332,7 @@ export const mockCheckTimelinesStatusBeforeInstallResult = { value: '3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509', operator: ':', }, - id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509', + id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-signal-id-3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509`, enabled: true, }, ], @@ -496,8 +495,7 @@ export const mockCheckTimelinesStatusBeforeInstallResult = { value: '30d47c8a1b179ae435058e5b23b96118125a451fe58efd77be288f00456ff77d', operator: ':', }, - id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-30d47c8a1b179ae435058e5b23b96118125a451fe58efd77be288f00456ff77d', + id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-signal-id-30d47c8a1b179ae435058e5b23b96118125a451fe58efd77be288f00456ff77d`, enabled: true, }, ], @@ -675,8 +673,7 @@ export const mockCheckTimelinesStatusBeforeInstallResult = { value: '590eb946a7fdbacaa587ed0f6b1a16f5ad3d659ec47ef35ad0826c47af133bde', operator: ':', }, - id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-590eb946a7fdbacaa587ed0f6b1a16f5ad3d659ec47ef35ad0826c47af133bde', + id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-signal-id-590eb946a7fdbacaa587ed0f6b1a16f5ad3d659ec47ef35ad0826c47af133bde`, enabled: true, }, ], @@ -848,8 +845,7 @@ export const mockCheckTimelinesStatusAfterInstallResult = { value: '30d47c8a1b179ae435058e5b23b96118125a451fe58efd77be288f00456ff77d', operator: ':', }, - id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-30d47c8a1b179ae435058e5b23b96118125a451fe58efd77be288f00456ff77d', + id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-signal-id-30d47c8a1b179ae435058e5b23b96118125a451fe58efd77be288f00456ff77d`, enabled: true, }, ], @@ -1031,8 +1027,7 @@ export const mockCheckTimelinesStatusAfterInstallResult = { value: '590eb946a7fdbacaa587ed0f6b1a16f5ad3d659ec47ef35ad0826c47af133bde', operator: ':', }, - id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-590eb946a7fdbacaa587ed0f6b1a16f5ad3d659ec47ef35ad0826c47af133bde', + id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-signal-id-590eb946a7fdbacaa587ed0f6b1a16f5ad3d659ec47ef35ad0826c47af133bde`, enabled: true, }, ], @@ -1152,8 +1147,7 @@ export const mockCheckTimelinesStatusAfterInstallResult = { value: '3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509', operator: ':', }, - id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509', + id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-signal-id-3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509`, enabled: true, }, ], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts new file mode 100644 index 0000000000000..5c29d2747f68d --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { set } from '@elastic/safer-lodash-set/fp'; +import { get, has, head } from 'lodash/fp'; +import { HostsEdges } from '../../../../../../common/search_strategy/security_solution/hosts'; +import { hostFieldsMap } from '../../../../../lib/ecs_fields'; + +import { HostAggEsItem, HostBuckets, HostValue } from '../../../../../lib/hosts/types'; + +const HOSTS_FIELDS = ['_id', 'lastSeen', 'host.id', 'host.name', 'host.os.name', 'host.os.version']; + +export const formatHostEdgesData = (bucket: HostAggEsItem): HostsEdges => + HOSTS_FIELDS.reduce( + (flattenedFields, fieldName) => { + const hostId = get('key', bucket); + flattenedFields.node._id = hostId || null; + flattenedFields.cursor.value = hostId || ''; + const fieldValue = getHostFieldValue(fieldName, bucket); + if (fieldValue != null) { + return set( + `node.${fieldName}`, + Array.isArray(fieldValue) ? fieldValue : [fieldValue], + flattenedFields + ); + } + return flattenedFields; + }, + { + node: {}, + cursor: { + value: '', + tiebreaker: null, + }, + } as HostsEdges + ); + +const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | string[] | null => { + const aggField = hostFieldsMap[fieldName] + ? hostFieldsMap[fieldName].replace(/\./g, '_') + : fieldName.replace(/\./g, '_'); + if ( + [ + 'host.ip', + 'host.mac', + 'cloud.instance.id', + 'cloud.machine.type', + 'cloud.provider', + 'cloud.region', + ].includes(fieldName) && + has(aggField, bucket) + ) { + const data: HostBuckets = get(aggField, bucket); + return data.buckets.map((obj) => obj.key); + } else if (has(`${aggField}.buckets`, bucket)) { + return getFirstItem(get(`${aggField}`, bucket)); + } else if (has(aggField, bucket)) { + const valueObj: HostValue = get(aggField, bucket); + return valueObj.value_as_string; + } else if (['host.name', 'host.os.name', 'host.os.version'].includes(fieldName)) { + switch (fieldName) { + case 'host.name': + return get('key', bucket) || null; + case 'host.os.name': + return get('os.hits.hits[0]._source.host.os.name', bucket) || null; + case 'host.os.version': + return get('os.hits.hits[0]._source.host.os.version', bucket) || null; + } + } + return null; +}; + +const getFirstItem = (data: HostBuckets): string | null => { + const firstItem = head(data.buckets); + if (firstItem == null) { + return null; + } + return firstItem.key; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts new file mode 100644 index 0000000000000..d4c2214b98645 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; +import { + HostAggEsItem, + HostsStrategyResponse, + HostsQueries, + HostsRequestOptions, +} from '../../../../../../common/search_strategy/security_solution/hosts'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; +import { buildHostsQuery } from './query.all_hosts.dsl'; +import { formatHostEdgesData } from './helpers'; + +export const allHosts: SecuritySolutionFactory = { + buildDsl: (options: HostsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildHostsQuery(options); + }, + parse: async ( + options: HostsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; + const totalCount = getOr(0, 'aggregations.host_count.value', response.rawResponse); + const buckets: HostAggEsItem[] = getOr( + [], + 'aggregations.host_data.buckets', + response.rawResponse + ); + const hostsEdges = buckets.map((bucket) => formatHostEdgesData(bucket)); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = hostsEdges.splice(cursorStart, querySize - cursorStart); + const inspect = { + dsl: [inspectStringifyObject(buildHostsQuery(options))], + response: [inspectStringifyObject(response)], + }; + const showMorePagesIndicator = totalCount > fakeTotalCount; + + return { + ...response, + inspect, + edges, + totalCount, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts similarity index 88% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.hosts.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts index a9101f54ada55..ea1b896452c4e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.hosts.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/query.all_hosts.dsl.ts @@ -10,8 +10,10 @@ import { Direction, HostsRequestOptions, SortField, + HostsFields, } from '../../../../../../common/search_strategy/security_solution'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; +import { assertUnreachable } from '../../../../../../common/utility_types'; export const buildHostsQuery = ({ defaultIndex, @@ -77,11 +79,13 @@ export const buildHostsQuery = ({ type QueryOrder = { lastSeen: Direction } | { _key: Direction }; -const getQueryOrder = (sort: SortField): QueryOrder => { +const getQueryOrder = (sort: SortField): QueryOrder => { switch (sort.field) { - case 'lastSeen': + case HostsFields.lastSeen: return { lastSeen: sort.direction }; - case 'hostName': + case HostsFields.hostName: return { _key: sort.direction }; + default: + return assertUnreachable(sort.field as never); } }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 443e524d71ca3..34676fc1932fe 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -4,89 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, getOr } from 'lodash/fp'; - -import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; - -import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants'; import { FactoryQueryTypes } from '../../../../../common/search_strategy/security_solution'; -import { - HostsStrategyResponse, - HostOverviewStrategyResponse, - HostsQueries, - HostsRequestOptions, - HostOverviewRequestOptions, -} from '../../../../../common/search_strategy/security_solution/hosts'; +import { HostsQueries } from '../../../../../common/search_strategy/security_solution/hosts'; -// TO DO need to move all this types in common -import { HostAggEsData, HostAggEsItem } from '../../../../lib/hosts/types'; - -import { inspectStringifyObject } from '../../../../utils/build_query'; import { SecuritySolutionFactory } from '../types'; -import { buildHostOverviewQuery } from './dsl/query.detail_host.dsl'; -import { buildHostsQuery } from './dsl/query.hosts.dsl'; -import { formatHostEdgesData, formatHostItem } from './helpers'; - -export const allHosts: SecuritySolutionFactory = { - buildDsl: (options: HostsRequestOptions) => { - if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { - throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); - } - return buildHostsQuery(options); - }, - parse: async ( - options: HostsRequestOptions, - response: IEsSearchResponse - ): Promise => { - const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; - const totalCount = getOr(0, 'aggregations.host_count.value', response.rawResponse); - const buckets: HostAggEsItem[] = getOr( - [], - 'aggregations.host_data.buckets', - response.rawResponse - ); - const hostsEdges = buckets.map((bucket) => formatHostEdgesData(bucket)); - const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; - const edges = hostsEdges.splice(cursorStart, querySize - cursorStart); - const inspect = { - dsl: [inspectStringifyObject(buildHostsQuery(options))], - response: [inspectStringifyObject(response)], - }; - const showMorePagesIndicator = totalCount > fakeTotalCount; - - return { - ...response, - inspect, - edges, - totalCount, - pageInfo: { - activePage: activePage ? activePage : 0, - fakeTotalCount, - showMorePagesIndicator, - }, - }; - }, -}; - -export const overviewHost: SecuritySolutionFactory = { - buildDsl: (options: HostOverviewRequestOptions) => { - return buildHostOverviewQuery(options); - }, - parse: async ( - options: HostOverviewRequestOptions, - response: IEsSearchResponse - ): Promise => { - const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; - const inspect = { - dsl: [inspectStringifyObject(buildHostOverviewQuery(options))], - response: [inspectStringifyObject(response)], - }; - const formattedHostItem = formatHostItem(aggregations); - return { ...response, inspect, _id: options.hostName, ...formattedHostItem }; - }, -}; +import { allHosts } from './all'; +import { overviewHost } from './overview'; +import { firstLastSeenHost } from './last_first_seen'; export const hostsFactory: Record> = { [HostsQueries.hosts]: allHosts, [HostsQueries.hostOverview]: overviewHost, + [HostsQueries.firstLastSeen]: firstLastSeenHost, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts new file mode 100644 index 0000000000000..56895583c2ae9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + HostAggEsData, + HostAggEsItem, + HostFirstLastSeenStrategyResponse, + HostsQueries, + HostFirstLastSeenRequestOptions, +} from '../../../../../../common/search_strategy/security_solution/hosts'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; +import { buildFirstLastSeenHostQuery } from './query.last_first_seen_host.dsl'; + +export const firstLastSeenHost: SecuritySolutionFactory = { + buildDsl: (options: HostFirstLastSeenRequestOptions) => buildFirstLastSeenHostQuery(options), + parse: async ( + options: HostFirstLastSeenRequestOptions, + response: IEsSearchResponse + ): Promise => { + const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; + const inspect = { + dsl: [inspectStringifyObject(buildFirstLastSeenHostQuery(options))], + response: [inspectStringifyObject(response)], + }; + + return { + ...response, + inspect, + firstSeen: get('firstSeen.value_as_string', aggregations), + lastSeen: get('lastSeen.value_as_string', aggregations), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.last_first_seen_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts similarity index 80% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.last_first_seen_host.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts index b57bbd2960e4f..2c65f62b258a9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.last_first_seen_host.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts @@ -6,13 +6,13 @@ import { isEmpty } from 'lodash/fp'; import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; -import { HostLastFirstSeenRequestOptions } from '../../../../../../common/search_strategy/security_solution'; +import { HostFirstLastSeenRequestOptions } from '../../../../../../common/search_strategy/security_solution/hosts'; -export const buildLastFirstSeenHostQuery = ({ +export const buildFirstLastSeenHostQuery = ({ hostName, defaultIndex, docValueFields, -}: HostLastFirstSeenRequestOptions): ISearchRequestParams => { +}: HostFirstLastSeenRequestOptions): ISearchRequestParams => { const filter = [{ term: { 'host.name': hostName } }]; const dslQuery = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/helpers.ts new file mode 100644 index 0000000000000..c7b0d8acc8782 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/helpers.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { set } from '@elastic/safer-lodash-set/fp'; +import { get, has, head } from 'lodash/fp'; +import { HostItem } from '../../../../../../common/search_strategy/security_solution/hosts'; +import { hostFieldsMap } from '../../../../../lib/ecs_fields'; + +import { HostAggEsItem, HostBuckets, HostValue } from '../../../../../lib/hosts/types'; + +export const HOST_FIELDS = [ + '_id', + 'host.architecture', + 'host.id', + 'host.ip', + 'host.id', + 'host.mac', + 'host.name', + 'host.os.family', + 'host.os.name', + 'host.os.platform', + 'host.os.version', + 'host.type', + 'cloud.instance.id', + 'cloud.machine.type', + 'cloud.provider', + 'cloud.region', + 'endpoint.endpointPolicy', + 'endpoint.policyStatus', + 'endpoint.sensorVersion', +]; + +export const formatHostItem = (bucket: HostAggEsItem): HostItem => + HOST_FIELDS.reduce((flattenedFields, fieldName) => { + const fieldValue = getHostFieldValue(fieldName, bucket); + if (fieldValue != null) { + return set(fieldName, fieldValue, flattenedFields); + } + return flattenedFields; + }, {}); + +const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | string[] | null => { + const aggField = hostFieldsMap[fieldName] + ? hostFieldsMap[fieldName].replace(/\./g, '_') + : fieldName.replace(/\./g, '_'); + if ( + [ + 'host.ip', + 'host.mac', + 'cloud.instance.id', + 'cloud.machine.type', + 'cloud.provider', + 'cloud.region', + ].includes(fieldName) && + has(aggField, bucket) + ) { + const data: HostBuckets = get(aggField, bucket); + return data.buckets.map((obj) => obj.key); + } else if (has(`${aggField}.buckets`, bucket)) { + return getFirstItem(get(`${aggField}`, bucket)); + } else if (has(aggField, bucket)) { + const valueObj: HostValue = get(aggField, bucket); + return valueObj.value_as_string; + } else if (['host.name', 'host.os.name', 'host.os.version'].includes(fieldName)) { + switch (fieldName) { + case 'host.name': + return get('key', bucket) || null; + case 'host.os.name': + return get('os.hits.hits[0]._source.host.os.name', bucket) || null; + case 'host.os.version': + return get('os.hits.hits[0]._source.host.os.version', bucket) || null; + } + } + return null; +}; + +const getFirstItem = (data: HostBuckets): string | null => { + const firstItem = head(data.buckets); + if (firstItem == null) { + return null; + } + return firstItem.key; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts new file mode 100644 index 0000000000000..8bdda9ef895b2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + HostAggEsData, + HostAggEsItem, + HostOverviewStrategyResponse, + HostsQueries, + HostOverviewRequestOptions, +} from '../../../../../../common/search_strategy/security_solution/hosts'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; +import { buildHostOverviewQuery } from './query.host_overview.dsl'; +import { formatHostItem } from './helpers'; + +export const overviewHost: SecuritySolutionFactory = { + buildDsl: (options: HostOverviewRequestOptions) => { + return buildHostOverviewQuery(options); + }, + parse: async ( + options: HostOverviewRequestOptions, + response: IEsSearchResponse + ): Promise => { + const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; + const inspect = { + dsl: [inspectStringifyObject(buildHostOverviewQuery(options))], + response: [inspectStringifyObject(response)], + }; + const formattedHostItem = formatHostItem(aggregations); + + return { ...response, inspect, hostOverview: formattedHostItem }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.detail_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.host_overview.dsl.ts similarity index 91% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.detail_host.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.host_overview.dsl.ts index 5c5dec92a5100..913bc90df04be 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.detail_host.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.host_overview.dsl.ts @@ -9,14 +9,14 @@ import { HostOverviewRequestOptions } from '../../../../../../common/search_stra import { cloudFieldsMap, hostFieldsMap } from '../../../../../lib/ecs_fields'; import { buildFieldsTermAggregation } from '../../../../../lib/hosts/helpers'; import { reduceFields } from '../../../../../utils/build_query/reduce_fields'; +import { HOST_FIELDS } from './helpers'; export const buildHostOverviewQuery = ({ - fields, hostName, defaultIndex, timerange: { from, to }, }: HostOverviewRequestOptions): ISearchRequestParams => { - const esFields = reduceFields(fields, { ...hostFieldsMap, ...cloudFieldsMap }); + const esFields = reduceFields(HOST_FIELDS, { ...hostFieldsMap, ...cloudFieldsMap }); const filter = [ { term: { 'host.name': hostName } }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts index 53433dfc208cb..a50c9e4004856 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts @@ -7,6 +7,7 @@ import { FactoryQueryTypes } from '../../../../common/search_strategy/security_solution'; import { hostsFactory } from './hosts'; +import { networkFactory } from './network'; import { SecuritySolutionFactory } from './types'; export const securitySolutionFactory: Record< @@ -14,4 +15,5 @@ export const securitySolutionFactory: Record< SecuritySolutionFactory > = { ...hostsFactory, + ...networkFactory, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/helpers.ts new file mode 100644 index 0000000000000..b8a28441337c7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/helpers.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get, getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + NetworkHttpBuckets, + NetworkHttpEdges, +} from '../../../../../../common/search_strategy/security_solution/network'; + +export const getHttpEdges = (response: IEsSearchResponse): NetworkHttpEdges[] => + formatHttpEdges(getOr([], `aggregations.url.buckets`, response.rawResponse)); + +const formatHttpEdges = (buckets: NetworkHttpBuckets[]): NetworkHttpEdges[] => + buckets.map((bucket: NetworkHttpBuckets) => ({ + node: { + _id: bucket.key, + domains: bucket.domains.buckets.map(({ key }) => key), + methods: bucket.methods.buckets.map(({ key }) => key), + statuses: bucket.status.buckets.map(({ key }) => `${key}`), + lastHost: get('source.hits.hits[0]._source.host.name', bucket), + lastSourceIp: get('source.hits.hits[0]._source.source.ip', bucket), + path: bucket.key, + requestCount: bucket.doc_count, + }, + cursor: { + value: bucket.key, + tiebreaker: null, + }, + })); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts new file mode 100644 index 0000000000000..b6c26cd533de2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/index.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; + +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; +import { + NetworkHttpStrategyResponse, + NetworkQueries, + NetworkHttpRequestOptions, + NetworkHttpEdges, +} from '../../../../../../common/search_strategy/security_solution/network'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; + +import { getHttpEdges } from './helpers'; +import { buildHttpQuery } from './query.http_network.dsl'; + +export const networkHttp: SecuritySolutionFactory = { + buildDsl: (options: NetworkHttpRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildHttpQuery(options); + }, + parse: async ( + options: NetworkHttpRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; + const totalCount = getOr(0, 'aggregations.http_count.value', response.rawResponse); + const networkHttpEdges: NetworkHttpEdges[] = getHttpEdges(response); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = networkHttpEdges.splice(cursorStart, querySize - cursorStart); + const inspect = { + dsl: [inspectStringifyObject(buildHttpQuery(options))], + response: [inspectStringifyObject(response)], + }; + const showMorePagesIndicator = totalCount > fakeTotalCount; + + return { + ...response, + edges, + inspect, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, + totalCount, + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/query.http_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/query.http_network.dsl.ts new file mode 100644 index 0000000000000..31d695d6a0591 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/query.http_network.dsl.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createQueryFilterClauses } from '../../../../../utils/build_query'; + +import { + NetworkHttpRequestOptions, + SortField, +} from '../../../../../../common/search_strategy/security_solution'; + +const getCountAgg = () => ({ + http_count: { + cardinality: { + field: 'url.path', + }, + }, +}); + +export const buildHttpQuery = ({ + defaultIndex, + filterQuery, + sort, + pagination: { querySize }, + timerange: { from, to }, + ip, +}: NetworkHttpRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { gte: from, lte: to, format: 'strict_date_optional_time' }, + }, + }, + { exists: { field: 'http.request.method' } }, + ]; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + aggregations: { + ...getCountAgg(), + ...getHttpAggs(sort, querySize), + }, + query: { + bool: ip + ? { + filter, + should: [ + { + term: { + 'source.ip': ip, + }, + }, + { + term: { + 'destination.ip': ip, + }, + }, + ], + minimum_should_match: 1, + } + : { + filter, + }, + }, + }, + size: 0, + track_total_hits: false, + }; + return dslQuery; +}; + +const getHttpAggs = (sortField: SortField, querySize: number) => ({ + url: { + terms: { + field: `url.path`, + size: querySize, + order: { + _count: sortField.direction, + }, + }, + aggs: { + methods: { + terms: { + field: 'http.request.method', + size: 4, + }, + }, + domains: { + terms: { + field: 'url.domain', + size: 4, + }, + }, + status: { + terms: { + field: 'http.response.status_code', + size: 4, + }, + }, + source: { + top_hits: { + size: 1, + _source: { + includes: ['host.name', 'source.ip'], + }, + }, + }, + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts new file mode 100644 index 0000000000000..7d40b034c66bb --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FactoryQueryTypes } from '../../../../../common/search_strategy/security_solution'; +import { NetworkQueries } from '../../../../../common/search_strategy/security_solution/network'; + +import { SecuritySolutionFactory } from '../types'; +import { networkHttp } from './http'; +import { networkTls } from './tls'; + +export const networkFactory: Record> = { + [NetworkQueries.http]: networkHttp, + [NetworkQueries.tls]: networkTls, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/helpers.ts new file mode 100644 index 0000000000000..59359fd35a34e --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/helpers.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + TlsBuckets, + TlsEdges, +} from '../../../../../../common/search_strategy/security_solution/network'; + +export const getTlsEdges = (response: IEsSearchResponse): TlsEdges[] => + formatTlsEdges(getOr([], 'aggregations.sha1.buckets', response.rawResponse)); + +export const formatTlsEdges = (buckets: TlsBuckets[]): TlsEdges[] => + buckets.map((bucket: TlsBuckets) => { + const edge: TlsEdges = { + node: { + _id: bucket.key, + subjects: bucket.subjects.buckets.map(({ key }) => key), + ja3: bucket.ja3.buckets.map(({ key }) => key), + issuers: bucket.issuers.buckets.map(({ key }) => key), + // eslint-disable-next-line @typescript-eslint/naming-convention + notAfter: bucket.not_after.buckets.map(({ key_as_string }) => key_as_string), + }, + cursor: { + value: bucket.key, + tiebreaker: null, + }, + }; + return edge; + }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts new file mode 100644 index 0000000000000..32836c0ef6869 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/index.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; + +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; +import { + NetworkTlsStrategyResponse, + NetworkQueries, + NetworkTlsRequestOptions, + TlsEdges, +} from '../../../../../../common/search_strategy/security_solution/network'; + +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../types'; + +import { getTlsEdges } from './helpers'; +import { buildTlsQuery } from './query.tls_network.dsl'; + +export const networkTls: SecuritySolutionFactory = { + buildDsl: (options: NetworkTlsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildTlsQuery(options); + }, + parse: async ( + options: NetworkTlsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; + const totalCount = getOr(0, 'aggregations.count.value', response.rawResponse); + const tlsEdges: TlsEdges[] = getTlsEdges(response); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = tlsEdges.splice(cursorStart, querySize - cursorStart); + const inspect = { + dsl: [inspectStringifyObject(buildTlsQuery(options))], + response: [inspectStringifyObject(response)], + }; + const showMorePagesIndicator = totalCount > fakeTotalCount; + + return { + ...response, + edges, + inspect, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, + totalCount, + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts new file mode 100644 index 0000000000000..eb4e25c29e3a1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { assertUnreachable } from '../../../../../../common/utility_types'; +import { createQueryFilterClauses } from '../../../../../utils/build_query'; + +import { + NetworkTlsRequestOptions, + SortField, + Direction, + TlsFields, +} from '../../../../../../common/search_strategy/security_solution'; + +const getAggs = (querySize: number, sort: SortField) => ({ + count: { + cardinality: { + field: 'tls.server.hash.sha1', + }, + }, + sha1: { + terms: { + field: 'tls.server.hash.sha1', + size: querySize, + order: { + ...getQueryOrder(sort), + }, + }, + aggs: { + issuers: { + terms: { + field: 'tls.server.issuer', + }, + }, + subjects: { + terms: { + field: 'tls.server.subject', + }, + }, + not_after: { + terms: { + field: 'tls.server.not_after', + }, + }, + ja3: { + terms: { + field: 'tls.server.ja3s', + }, + }, + }, + }, +}); + +export const buildTlsQuery = ({ + ip, + sort, + filterQuery, + flowTarget, + pagination: { querySize }, + defaultIndex, + timerange: { from, to }, +}: NetworkTlsRequestOptions) => { + const defaultFilter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { gte: from, lte: to, format: 'strict_date_optional_time' }, + }, + }, + ]; + + const filter = ip ? [...defaultFilter, { term: { [`${flowTarget}.ip`]: ip } }] : defaultFilter; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + aggs: { + ...getAggs(querySize, sort), + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; + +interface QueryOrder { + _key: Direction; +} + +const getQueryOrder = (sort: SortField): QueryOrder => { + switch (sort.field) { + case TlsFields._id: + return { _key: sort.direction }; + default: + return assertUnreachable(sort.field); + } +}; diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts index a6d4dc7a38e14..5cf17af2fa9c0 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts @@ -172,18 +172,12 @@ export const getMlJobsUsage = async (ml: MlPluginSetup | undefined): Promise module.jobs); - const jobs = await ml.jobServiceProvider(internalMlClient, fakeRequest).jobsSummary(); + const jobs = await ml.jobServiceProvider(fakeRequest).jobsSummary(); jobsUsage = jobs.filter(isSecurityJob).reduce((usage, job) => { const isElastic = moduleJobs.some((moduleJob) => moduleJob.id === job.id); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index 0abc47686a6b4..6021ab2a42c90 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -20,13 +20,6 @@ import { REPOSITORY_NAME } from './helpers/constant'; const { setup } = pageHelpers.home; -jest.mock('ui/new_platform'); - -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - // Mocking FormattedDate and FormattedTime due to timezone differences on CI jest.mock('@kbn/i18n/react', () => { const original = jest.requireActual('@kbn/i18n/react'); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts index f480e937f162a..dc568161d4fb4 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts @@ -17,11 +17,6 @@ import { DEFAULT_POLICY_SCHEDULE } from '../../public/application/constants'; const { setup } = pageHelpers.policyAdd; -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - // mock for EuiSelectable's virtualization jest.mock('react-virtualized-auto-sizer', () => { return ({ diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts index 7eec80890ca86..a5b2ec73b85cd 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts @@ -18,11 +18,6 @@ const { setup: setupPolicyAdd } = pageHelpers.policyAdd; const EXPIRE_AFTER_VALUE = '5'; const EXPIRE_AFTER_UNIT = TIME_UNITS.MINUTE; -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - describe('', () => { let testBed: PolicyFormTestBed; let testBedPolicyAdd: PolicyFormTestBed; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts index 2a9e17a1c9c4d..e0c9aeffa09e8 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts @@ -11,16 +11,9 @@ import { RepositoryType } from '../../common/types'; import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { RepositoryAddTestBed } from './helpers/repository_add.helpers'; -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.repositoryAdd; const repositoryTypes = ['fs', 'url', 'source', 'azure', 'gcs', 's3', 'hdfs']; -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - describe('', () => { let testBed: RepositoryAddTestBed; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts index bab276584966b..1606db07b57b4 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts @@ -12,16 +12,9 @@ import { RepositoryEditTestSubjects } from './helpers/repository_edit.helpers'; import { RepositoryAddTestSubjects } from './helpers/repository_add.helpers'; import { REPOSITORY_EDIT } from './helpers/constant'; -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.repositoryEdit; const { setup: setupRepositoryAdd } = pageHelpers.repositoryAdd; -jest.mock('ui/i18n', () => { - const I18nContext = ({ children }: any) => children; - return { I18nContext }; -}); - describe('', () => { let testBed: TestBed; let testBedRepositoryAdd: TestBed; diff --git a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts index 473a3392deb3e..e5d8e804691a8 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts @@ -4,55 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { deserializeSnapshotDetails } from './snapshot_serialization'; +import { deserializeSnapshotDetails, serializeSnapshotConfig } from './snapshot_serialization'; -describe('deserializeSnapshotDetails', () => { - test('deserializes a snapshot', () => { - expect( - deserializeSnapshotDetails( - 'repositoryName', - { - snapshot: 'snapshot name', - uuid: 'UUID', - version_id: 5, - version: 'version', - indices: ['index2', 'index3', 'index1'], - include_global_state: false, - state: 'SUCCESS', - start_time: '0', - start_time_in_millis: 0, - end_time: '1', - end_time_in_millis: 1, - duration_in_millis: 1, - shards: { - total: 3, - failed: 1, - successful: 2, - }, - failures: [ - { - index: 'z', - shard: 1, - }, - { - index: 'a', - shard: 3, - }, - { - index: 'a', - shard: 1, - }, - { - index: 'a', - shard: 2, - }, - ], - }, - 'found-snapshots', - [ +describe('Snapshot serialization and deserialization', () => { + describe('deserializeSnapshotDetails', () => { + test('deserializes a snapshot', () => { + expect( + deserializeSnapshotDetails( + 'repositoryName', { - snapshot: 'last_successful_snapshot', - uuid: 'last_successful_snapshot_UUID', + snapshot: 'snapshot name', + uuid: 'UUID', version_id: 5, version: 'version', indices: ['index2', 'index3', 'index1'], @@ -87,56 +49,109 @@ describe('deserializeSnapshotDetails', () => { }, ], }, - ] - ) - ).toEqual({ - repository: 'repositoryName', - snapshot: 'snapshot name', - uuid: 'UUID', - versionId: 5, - version: 'version', - // Indices are sorted. - indices: ['index1', 'index2', 'index3'], - dataStreams: [], - includeGlobalState: false, - // Failures are grouped and sorted by index, and the failures themselves are sorted by shard. - indexFailures: [ - { - index: 'a', - failures: [ + 'found-snapshots', + [ { - shard: 1, - }, - { - shard: 2, - }, - { - shard: 3, - }, - ], - }, - { - index: 'z', - failures: [ - { - shard: 1, + snapshot: 'last_successful_snapshot', + uuid: 'last_successful_snapshot_UUID', + version_id: 5, + version: 'version', + indices: ['index2', 'index3', 'index1'], + include_global_state: false, + state: 'SUCCESS', + start_time: '0', + start_time_in_millis: 0, + end_time: '1', + end_time_in_millis: 1, + duration_in_millis: 1, + shards: { + total: 3, + failed: 1, + successful: 2, + }, + failures: [ + { + index: 'z', + shard: 1, + }, + { + index: 'a', + shard: 3, + }, + { + index: 'a', + shard: 1, + }, + { + index: 'a', + shard: 2, + }, + ], }, - ], + ] + ) + ).toEqual({ + repository: 'repositoryName', + snapshot: 'snapshot name', + uuid: 'UUID', + versionId: 5, + version: 'version', + // Indices are sorted. + indices: ['index1', 'index2', 'index3'], + dataStreams: [], + includeGlobalState: false, + // Failures are grouped and sorted by index, and the failures themselves are sorted by shard. + indexFailures: [ + { + index: 'a', + failures: [ + { + shard: 1, + }, + { + shard: 2, + }, + { + shard: 3, + }, + ], + }, + { + index: 'z', + failures: [ + { + shard: 1, + }, + ], + }, + ], + state: 'SUCCESS', + startTime: '0', + startTimeInMillis: 0, + endTime: '1', + endTimeInMillis: 1, + durationInMillis: 1, + shards: { + total: 3, + failed: 1, + successful: 2, }, - ], - state: 'SUCCESS', - startTime: '0', - startTimeInMillis: 0, - endTime: '1', - endTimeInMillis: 1, - durationInMillis: 1, - shards: { - total: 3, - failed: 1, - successful: 2, - }, - managedRepository: 'found-snapshots', - isLastSuccessfulSnapshot: false, + managedRepository: 'found-snapshots', + isLastSuccessfulSnapshot: false, + }); + }); + }); + + describe('serializeSnapshotConfig', () => { + test('serializes config as expected', () => { + const metadata = { test: 'what have you' }; + expect(serializeSnapshotConfig({ metadata, indices: '.k*' })).toEqual({ + metadata, + indices: ['.k*'], + }); + }); + test('serializes empty config as expected', () => { + expect(serializeSnapshotConfig({})).toEqual({}); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts index a85b49430eecd..61a57c9288f03 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts @@ -131,10 +131,10 @@ export function deserializeSnapshotConfig(snapshotConfigEs: SnapshotConfigEs): S export function serializeSnapshotConfig(snapshotConfig: SnapshotConfig): SnapshotConfigEs { const { indices, ignoreUnavailable, includeGlobalState, partial, metadata } = snapshotConfig; - const indicesArray = csvToArray(indices); + const maybeIndicesArray = csvToArray(indices); const snapshotConfigEs: SnapshotConfigEs = { - indices: indicesArray, + indices: maybeIndicesArray, ignore_unavailable: ignoreUnavailable, include_global_state: includeGlobalState, partial, diff --git a/x-pack/plugins/snapshot_restore/common/lib/utils.ts b/x-pack/plugins/snapshot_restore/common/lib/utils.ts index 96eb7cb6908d8..3d07aa8de3a55 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/utils.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/utils.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export const csvToArray = (indices?: string | string[]): string[] => { +export const csvToArray = (indices?: string | string[]): string[] | undefined => { return indices && Array.isArray(indices) ? indices : typeof indices === 'string' ? indices.split(',') - : []; + : undefined; }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts b/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts index 275915c5760af..7ec34dd7b3984 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts +++ b/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.ts @@ -24,7 +24,7 @@ const maximumItemPreviewCount = 10; export const useCollapsibleList = ({ items }: Arg): ReturnValue => { const [isShowingFullList, setIsShowingFullList] = useState(false); - const itemsArray = csvToArray(items); + const itemsArray = csvToArray(items) ?? []; const displayItems: ChildItems = items === undefined ? 'all' diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx index ae22464c8a52b..407b9be14e3c1 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx @@ -35,11 +35,17 @@ export const PolicyStepRetention: React.FunctionComponent = ({ }) => { const { retention = {} } = policy; - const updatePolicyRetention = (updatedFields: Partial): void => { + const updatePolicyRetention = ( + updatedFields: Partial, + validationHelperData = {} + ): void => { const newRetention = { ...retention, ...updatedFields }; - updatePolicy({ - retention: newRetention, - }); + updatePolicy( + { + retention: newRetention, + }, + validationHelperData + ); }; // State for touched inputs diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx index 94854905e6686..6f89427516453 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx @@ -25,7 +25,7 @@ import { import { SlmPolicyPayload } from '../../../../../../../../common/types'; import { useServices } from '../../../../../../app_context'; -import { PolicyValidation } from '../../../../../../services/validation'; +import { PolicyValidation, ValidatePolicyHelperData } from '../../../../../../services/validation'; import { orderDataStreamsAndIndices } from '../../../../../lib'; import { DataStreamBadge } from '../../../../../data_stream_badge'; @@ -34,12 +34,16 @@ import { mapSelectionToIndicesOptions, determineListMode } from './helpers'; import { DataStreamsAndIndicesListHelpText } from './data_streams_and_indices_list_help_text'; +interface IndicesConfig { + indices?: string[] | string; +} + interface Props { isManagedPolicy: boolean; policy: SlmPolicyPayload; indices: string[]; dataStreams: string[]; - onUpdate: (arg: { indices?: string[] | string }) => void; + onUpdate: (arg: IndicesConfig, validateHelperData: ValidatePolicyHelperData) => void; errors: PolicyValidation['errors']; } @@ -53,7 +57,7 @@ export const IndicesAndDataStreamsField: FunctionComponent = ({ dataStreams, indices, policy, - onUpdate, + onUpdate: _onUpdate, errors, }) => { const { i18n } = useServices(); @@ -66,6 +70,12 @@ export const IndicesAndDataStreamsField: FunctionComponent = ({ !config.indices || (Array.isArray(config.indices) && config.indices.length === 0) ); + const onUpdate = (data: IndicesConfig) => { + _onUpdate(data, { + validateIndicesCount: !isAllIndices, + }); + }; + const [indicesAndDataStreamsSelection, setIndicesAndDataStreamsSelection] = useState( () => Array.isArray(config.indices) && !isAllIndices diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx index 9d43c45d17ea7..f65156bada278 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx @@ -31,11 +31,17 @@ export const PolicyStepSettings: React.FunctionComponent = ({ }) => { const { config = {}, isManagedPolicy } = policy; - const updatePolicyConfig = (updatedFields: Partial): void => { + const updatePolicyConfig = ( + updatedFields: Partial, + validationHelperData = {} + ): void => { const newConfig = { ...config, ...updatedFields }; - updatePolicy({ - config: newConfig, - }); + updatePolicy( + { + config: newConfig, + }, + validationHelperData + ); }; const renderIgnoreUnavailableField = () => ( diff --git a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx index d9fd4cca0d614..855abb04b7c2b 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx @@ -311,7 +311,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = }); } }} - selectedIndicesAndDataStreams={csvToArray(restoreIndices)} + selectedIndicesAndDataStreams={csvToArray(restoreIndices) ?? []} indices={snapshotIndices} dataStreams={snapshotDataStreams} /> diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/attempt_to_uri_decode.ts b/x-pack/plugins/snapshot_restore/public/application/lib/attempt_to_uri_decode.ts new file mode 100644 index 0000000000000..6f99fae118cd7 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/lib/attempt_to_uri_decode.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const attemptToURIDecode = (value: string) => { + let result: string; + + try { + result = decodeURI(value); + result = decodeURIComponent(result); + } catch (e1) { + try { + result = decodeURIComponent(value); + } catch (e2) { + result = value; + } + } + + return result; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.js b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts similarity index 81% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.js rename to x-pack/plugins/snapshot_restore/public/application/lib/index.ts index c4aa32f1f7dc2..c544df4365606 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/index.js +++ b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { PolicyTable } from './components/policy_table'; +export { useDecodedParams } from './use_decoded_params'; diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/use_decoded_params.ts b/x-pack/plugins/snapshot_restore/public/application/lib/use_decoded_params.ts new file mode 100644 index 0000000000000..d4582de4084ba --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/lib/use_decoded_params.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useParams } from 'react-router-dom'; +import { attemptToURIDecode } from './attempt_to_uri_decode'; + +export const useDecodedParams = < + Params extends { [K in keyof Params]?: string } = {} +>(): Params => { + const params = useParams>(); + const decodedParams = {} as Params; + + for (const [key, value] of Object.entries(params)) { + if (value) { + (decodedParams as any)[key] = attemptToURIDecode(value); + } + } + + return decodedParams; +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx index 962dfa73e95c7..12e7e2de9383d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx @@ -89,7 +89,7 @@ export const SnapshotRestoreHome: React.FunctionComponent { - history.push(`${BASE_PATH}/${newSection}`); + history.push(encodeURI(`${BASE_PATH}/${encodeURIComponent(newSection)}`)); }; // Set breadcrumb and page title diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx index 5959ad6441f5d..f67e8eb586238 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx @@ -24,6 +24,8 @@ import { EuiSpacer, } from '@elastic/eui'; +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; + import { SlmPolicy } from '../../../../../../common/types'; import { useServices } from '../../../../app_context'; import { SectionError, Error } from '../../../../../shared_imports'; @@ -41,8 +43,6 @@ import { } from '../../../../components'; import { TabSummary, TabHistory } from './tabs'; -import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; - interface Props { policyName: SlmPolicy['name']; onClose: () => void; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx index 39ef66eb0658c..655bd0e9d8bb9 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx @@ -9,6 +9,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiEmptyPrompt, EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + import { SectionError, Error, @@ -20,6 +22,7 @@ import { SlmPolicy } from '../../../../../common/types'; import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common'; import { SectionLoading } from '../../../components'; import { BASE_PATH, UIM_POLICY_LIST_LOAD } from '../../../constants'; +import { useDecodedParams } from '../../../lib'; import { useLoadPolicies, useLoadRetentionSettings } from '../../../services/http'; import { linkToAddPolicy, linkToPolicy } from '../../../services/navigation'; import { useServices } from '../../../app_context'; @@ -28,18 +31,14 @@ import { PolicyDetails } from './policy_details'; import { PolicyTable } from './policy_table'; import { PolicyRetentionSchedule } from './policy_retention_schedule'; -import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; - interface MatchParams { policyName?: SlmPolicy['name']; } export const PolicyList: React.FunctionComponent> = ({ - match: { - params: { policyName }, - }, history, }) => { + const { policyName } = useDecodedParams(); const { error, isLoading, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx index f6b9ac08cc0a2..9afdad3806def 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx @@ -7,11 +7,14 @@ import React, { Fragment, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { RouteComponentProps } from 'react-router-dom'; - import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; + +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + import { Repository } from '../../../../../common/types'; import { SectionError, Error } from '../../../../shared_imports'; import { SectionLoading } from '../../../components'; +import { useDecodedParams } from '../../../lib'; import { BASE_PATH, UIM_REPOSITORY_LIST_LOAD } from '../../../constants'; import { useServices } from '../../../app_context'; import { useLoadRepositories } from '../../../services/http'; @@ -20,18 +23,14 @@ import { linkToAddRepository, linkToRepository } from '../../../services/navigat import { RepositoryDetails } from './repository_details'; import { RepositoryTable } from './repository_table'; -import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; - interface MatchParams { repositoryName?: Repository['name']; } export const RepositoryList: React.FunctionComponent> = ({ - match: { - params: { repositoryName }, - }, history, }) => { + const { repositoryName } = useDecodedParams(); const { error, isLoading, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx index 2b8df7294c374..d13188fc44730 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx @@ -24,6 +24,7 @@ import { linkToSnapshot, } from '../../../services/navigation'; import { useServices } from '../../../app_context'; +import { useDecodedParams } from '../../../lib'; import { SnapshotDetails } from './snapshot_details'; import { SnapshotTable } from './snapshot_table'; @@ -35,12 +36,10 @@ interface MatchParams { } export const SnapshotList: React.FunctionComponent> = ({ - match: { - params: { repositoryName, snapshotId }, - }, location: { search }, history, }) => { + const { repositoryName, snapshotId } = useDecodedParams(); const { error, isLoading, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx index 90cd26c821c5e..dead9abb6ef0c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx @@ -43,7 +43,7 @@ export const PolicyAdd: React.FunctionComponent = ({ if (error) { setSaveError(error); } else { - history.push(`${BASE_PATH}/policies/${name}`); + history.push(encodeURI(`${BASE_PATH}/policies/${encodeURIComponent(name)}`)); } }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx index 915b1cbef1233..7af663b29957d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx @@ -10,6 +10,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui'; import { SlmPolicyPayload } from '../../../../common/types'; import { SectionError, Error } from '../../../shared_imports'; +import { useDecodedParams } from '../../lib'; import { TIME_UNITS } from '../../../../common/constants'; import { SectionLoading, PolicyForm } from '../../components'; import { BASE_PATH } from '../../constants'; @@ -22,12 +23,10 @@ interface MatchParams { } export const PolicyEdit: React.FunctionComponent> = ({ - match: { - params: { name }, - }, history, location: { pathname }, }) => { + const { name } = useDecodedParams(); const { i18n } = useServices(); // Set breadcrumb and page title @@ -83,12 +82,12 @@ export const PolicyEdit: React.FunctionComponent { - history.push(`${BASE_PATH}/policies/${name}`); + history.push(encodeURI(`${BASE_PATH}/policies/${encodeURIComponent(name)}`)); }; const renderLoading = () => { diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx index 08bfde833c368..a66d137471eba 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx @@ -44,7 +44,11 @@ export const RepositoryAdd: React.FunctionComponent = ({ } else { const { redirect } = parse(search.replace(/^\?/, ''), { sort: false }); - history.push(redirect ? (redirect as string) : `${BASE_PATH}/${section}/${name}`); + history.push( + redirect + ? (redirect as string) + : encodeURI(`${BASE_PATH}/${encodeURIComponent(section)}/${encodeURIComponent(name)}`) + ); } }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx index 95f8b9b8bde7d..3e8cff5793572 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx @@ -16,18 +16,17 @@ import { BASE_PATH, Section } from '../../constants'; import { useServices } from '../../app_context'; import { breadcrumbService, docTitleService } from '../../services/navigation'; import { editRepository, useLoadRepository } from '../../services/http'; +import { useDecodedParams } from '../../lib'; interface MatchParams { name: string; } export const RepositoryEdit: React.FunctionComponent> = ({ - match: { - params: { name }, - }, history, }) => { const { i18n } = useServices(); + const { name } = useDecodedParams(); const section = 'repositories' as Section; // Set breadcrumb and page title @@ -70,7 +69,9 @@ export const RepositoryEdit: React.FunctionComponent> = ({ - match: { - params: { repositoryName, snapshotId }, - }, history, }) => { const { i18n } = useServices(); + const { repositoryName, snapshotId } = useDecodedParams(); // Set breadcrumb and page title useEffect(() => { diff --git a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts index 503704c6fe820..b498dc280d395 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts @@ -13,33 +13,37 @@ export function linkToRepositories() { } export function linkToRepository(repositoryName: string) { - return `/repositories/${encodeURIComponent(repositoryName)}`; + return encodeURI(`/repositories/${encodeURIComponent(repositoryName)}`); } export function linkToEditRepository(repositoryName: string) { - return `/edit_repository/${encodeURIComponent(repositoryName)}`; + return encodeURI(`/edit_repository/${encodeURIComponent(repositoryName)}`); } export function linkToAddRepository(redirect?: string) { - return `/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`; + return encodeURI(`/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`); } export function linkToSnapshots(repositoryName?: string, policyName?: string) { if (repositoryName) { - return `/snapshots?repository=${encodeURIComponent(repositoryName)}`; + return encodeURI(`/snapshots?repository=${encodeURIComponent(repositoryName)}`); } if (policyName) { - return `/snapshots?policy=${encodeURIComponent(policyName)}`; + return encodeURI(`/snapshots?policy=${encodeURIComponent(policyName)}`); } return `/snapshots`; } export function linkToSnapshot(repositoryName: string, snapshotName: string) { - return `/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; + return encodeURI( + `/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}` + ); } export function linkToRestoreSnapshot(repositoryName: string, snapshotName: string) { - return `/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; + return encodeURI( + `/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}` + ); } export function linkToPolicies() { @@ -47,11 +51,11 @@ export function linkToPolicies() { } export function linkToPolicy(policyName: string) { - return `/policies/${encodeURIComponent(policyName)}`; + return encodeURI(`/policies/${encodeURIComponent(policyName)}`); } export function linkToEditPolicy(policyName: string) { - return `/edit_policy/${encodeURIComponent(policyName)}`; + return encodeURI(`/edit_policy/${encodeURIComponent(policyName)}`); } export function linkToAddPolicy() { diff --git a/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts b/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts index 7fd755497eec6..82bf0ef06c400 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/validation/index.ts @@ -12,4 +12,4 @@ export { export { RestoreValidation, validateRestore } from './validate_restore'; -export { PolicyValidation, validatePolicy } from './validate_policy'; +export { PolicyValidation, validatePolicy, ValidatePolicyHelperData } from './validate_policy'; diff --git a/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts b/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts index 24960b2533230..4314b703722f6 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_policy.ts @@ -25,21 +25,32 @@ const isSnapshotNameNotLowerCase = (str: string): boolean => { return strExcludeDate !== strExcludeDate.toLowerCase() ? true : false; }; +export interface ValidatePolicyHelperData { + managedRepository?: { + name: string; + policy: string; + }; + isEditing?: boolean; + policyName?: string; + /** + * Whether to block on the indices configured for this snapshot. + * + * By default ES will back up all indices and data streams if this is an empty array or left blank. + * However, in the UI, under certain conditions, like when displaying indices to select for backup, + * we want to block users from submitting an empty array, but not block the entire form if they + * are not configuring this value - like when they are on a previous step. + */ + validateIndicesCount?: boolean; +} + export const validatePolicy = ( policy: SlmPolicyPayload, - validationHelperData: { - managedRepository?: { - name: string; - policy: string; - }; - isEditing?: boolean; - policyName?: string; - } + validationHelperData: ValidatePolicyHelperData ): PolicyValidation => { const i18n = textService.i18n; const { name, snapshotName, schedule, repository, config, retention } = policy; - const { managedRepository, isEditing, policyName } = validationHelperData; + const { managedRepository, isEditing, policyName, validateIndicesCount } = validationHelperData; const validation: PolicyValidation = { isValid: true, @@ -96,7 +107,12 @@ export const validatePolicy = ( ); } - if (config && typeof config.indices === 'string' && config.indices.trim().length === 0) { + if ( + validateIndicesCount && + config && + typeof config.indices === 'string' && + config.indices.trim().length === 0 + ) { validation.errors.indices.push( i18n.translate('xpack.snapshotRestore.policyValidation.indexPatternRequiredErrorMessage', { defaultMessage: 'At least one index pattern is required.', @@ -104,7 +120,12 @@ export const validatePolicy = ( ); } - if (config && Array.isArray(config.indices) && config.indices.length === 0) { + if ( + validateIndicesCount && + config && + Array.isArray(config.indices) && + config.indices.length === 0 + ) { validation.errors.indices.push( i18n.translate('xpack.snapshotRestore.policyValidation.indicesRequiredErrorMessage', { defaultMessage: 'You must select at least one data stream or index.', diff --git a/x-pack/plugins/transform/server/routes/api/field_histograms.ts b/x-pack/plugins/transform/server/routes/api/field_histograms.ts index f2fd81368ec17..2642040c4cd0d 100644 --- a/x-pack/plugins/transform/server/routes/api/field_histograms.ts +++ b/x-pack/plugins/transform/server/routes/api/field_histograms.ts @@ -34,7 +34,7 @@ export function registerFieldHistogramsRoutes({ router, license }: RouteDependen try { const resp = await getHistogramsForFields( - ctx.transform!.dataClient, + ctx.core.elasticsearch.client, indexPatternTitle, query, fields, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index dc07044ce8ed7..093ef4d6f1873 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -253,31 +253,6 @@ "charts.controls.rangeErrorMessage": "値は{min}と{max}の間でなければなりません", "charts.controls.vislibBasicOptions.legendPositionLabel": "凡例位置", "charts.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", - "common.ui.flotCharts.aprLabel": "4 月", - "common.ui.flotCharts.augLabel": "8 月", - "common.ui.flotCharts.decLabel": "12 月", - "common.ui.flotCharts.febLabel": "2 月", - "common.ui.flotCharts.friLabel": "金", - "common.ui.flotCharts.janLabel": "1 月", - "common.ui.flotCharts.julLabel": "7 月", - "common.ui.flotCharts.junLabel": "6 月", - "common.ui.flotCharts.marLabel": "3 月", - "common.ui.flotCharts.mayLabel": "5 月", - "common.ui.flotCharts.monLabel": "月", - "common.ui.flotCharts.novLabel": "11 月", - "common.ui.flotCharts.octLabel": "10 月", - "common.ui.flotCharts.pie.unableToDrawLabelsInsideCanvasErrorMessage": "キャンバス内のラベルではパイを作成できません", - "common.ui.flotCharts.satLabel": "土", - "common.ui.flotCharts.sepLabel": "9 月", - "common.ui.flotCharts.sunLabel": "日", - "common.ui.flotCharts.thuLabel": "木", - "common.ui.flotCharts.tueLabel": "火", - "common.ui.flotCharts.wedLabel": "水", - "common.ui.stateManagement.unableToParseUrlErrorMessage": "URL をパースできません", - "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", - "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", - "common.ui.url.replacementFailedErrorMessage": "置換に失敗、未解決の表現式: {expr}", - "common.ui.url.savedObjectIsMissingNotificationMessage": "保存されたオブジェクトがありません", "console.autocomplete.addMethodMetaText": "メソド", "console.consoleDisplayName": "コンソール", "console.consoleMenu.copyAsCurlMessage": "リクエストが URL としてコピーされました", @@ -1115,7 +1090,6 @@ "data.search.aggs.metrics.count.customLabel.help": "このアグリゲーションのカスタムラベルを表します", "data.search.aggs.metrics.count.enabled.help": "このアグリゲーションが有効かどうかを指定します", "data.search.aggs.metrics.count.id.help": "このアグリゲーションのID", - "data.search.aggs.metrics.count.json.help": "アグリゲーションがElasticsearchに送信されるときに含める高度なJSON", "data.search.aggs.metrics.count.schema.help": "このアグリゲーションで使用するスキーマ", "data.search.aggs.metrics.countLabel": "カウント", "data.search.aggs.metrics.countTitle": "カウント", @@ -5068,7 +5042,6 @@ "xpack.apm.transactionActionMenu.viewInUptime": "ステータス", "xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "サンプルドキュメントを表示", "xpack.apm.transactionBreakdown.chartTitle": "スパンタイプ別時間", - "xpack.apm.transactionBreakdown.noData": "この時間範囲のデータがありません。", "xpack.apm.transactionCardinalityWarning.body": "一意のトランザクション名の数が構成された値{bucketSize}を超えています。エージェントを再構成し、類似したトランザクションをグループ化するか、{codeBlock}の値を増やしてください。", "xpack.apm.transactionCardinalityWarning.docsLink": "詳細はドキュメントをご覧ください", "xpack.apm.transactionCardinalityWarning.title": "このビューには、報告されたトランザクションのサブセットが表示されます。", @@ -8127,7 +8100,6 @@ "xpack.indexLifecycleMgmt.editPolicy.learnAboutShardAllocationLink": "シャード割り当ての詳細をご覧ください", "xpack.indexLifecycleMgmt.editPolicy.learnAboutTimingText": "タイミングの詳細をご覧ください", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText": "インデックスへのアクティブな書き込みから削除までの、インデックスライフサイクルの 4 つのフェーズを自動化するには、インデックスポリシーを使用します。", - "xpack.indexLifecycleMgmt.editPolicy.loadPolicyErrorMessage": "ポリシーの読み込み中にエラーが発生しました", "xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError": "最高年齢が必要です。", "xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError": "最高ドキュメント数が必要です。", "xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError": "最大インデックスサイズが必要です。", @@ -8266,7 +8238,6 @@ "xpack.indexLifecycleMgmt.policyTable.addLifecyclePolicyToTemplateConfirmModal.title": "インデックステンプレートにポリシー「{name}」 を追加", "xpack.indexLifecycleMgmt.policyTable.addPolicyToTemplateButtonText": "インデックステンプレートにポリシーを追加", "xpack.indexLifecycleMgmt.policyTable.captionText": "以下は {total} 列中 {count, plural, one {# 列} other {# 列}} を含むインデックスライフサイクルポリシー表です。", - "xpack.indexLifecycleMgmt.policyTable.deletedPoliciesText": "{numSelected} 件の{numSelected, plural, one {ポリシー} other {ポリシー}}が削除されました", "xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonDisabledTooltip": "インデックスが使用中のポリシーは削除できません", "xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonText": "ポリシーを削除", "xpack.indexLifecycleMgmt.policyTable.emptyPrompt.createButtonLabel": "ポリシーを作成", @@ -11140,7 +11111,6 @@ "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "インデックスパターン{destinationIndex}を削除する要求が確認されました。", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "ディスティネーションインデックス{destinationIndex}を削除する要求が確認されました。", "xpack.ml.dataframe.analyticsList.deleteDestinationIndexTitle": "ディスティネーションインデックス{indexName}を削除", - "xpack.ml.dataframe.analyticsList.deleteModalBody": "この分析ジョブを削除してよろしいですか?", "xpack.ml.dataframe.analyticsList.deleteModalCancelButton": "キャンセル", "xpack.ml.dataframe.analyticsList.deleteModalDeleteButton": "削除", "xpack.ml.dataframe.analyticsList.deleteModalTitle": "{analyticsId}の削除", @@ -11635,7 +11605,6 @@ "xpack.ml.jobsList.deleteJobModal.cancelButtonLabel": "キャンセル", "xpack.ml.jobsList.deleteJobModal.closeButtonLabel": "閉じる", "xpack.ml.jobsList.deleteJobModal.deleteButtonLabel": "削除", - "xpack.ml.jobsList.deleteJobModal.deleteJobsDescription": "{jobsCount, plural, one {このジョブ} other {これらのジョブ}}を削除してよろしいですか?", "xpack.ml.jobsList.deleteJobModal.deleteJobsTitle": "{jobsCount, plural, one {{jobId}} other {# 件のジョブ}}を削除", "xpack.ml.jobsList.deleteJobModal.deleteMultipleJobsDescription": "{jobsCount, plural, one {ジョブ} other {複数ジョブ}}の削除には時間がかかる場合があります。{jobsCount, plural, one {} other {}}バックグラウンドで削除され、ジョブリストからすぐに消えない場合があります", "xpack.ml.jobsList.deleteJobModal.deletingJobsStatusLabel": "ジョブを削除中", @@ -12304,7 +12273,6 @@ "xpack.ml.ruleEditor.deleteJobRule.ruleNoLongerExistsErrorMessage": "ジョブ {jobId} の検知器インデックス {detectorIndex} のルールが現在存在しません", "xpack.ml.ruleEditor.deleteRuleModal.cancelButtonLabel": "キャンセル", "xpack.ml.ruleEditor.deleteRuleModal.deleteButtonLabel": "削除", - "xpack.ml.ruleEditor.deleteRuleModal.deleteRuleDescription": "このルールを削除してよろしいですか?", "xpack.ml.ruleEditor.deleteRuleModal.deleteRuleLinkText": "ルールを削除", "xpack.ml.ruleEditor.deleteRuleModal.deleteRuleTitle": "ルールの削除", "xpack.ml.ruleEditor.detectorDescriptionList.detectorTitle": "検知器", @@ -12398,7 +12366,6 @@ "xpack.ml.settings.filterLists.deleteFilterListModal.cancelButtonLabel": "キャンセル", "xpack.ml.settings.filterLists.deleteFilterListModal.confirmButtonLabel": "削除", "xpack.ml.settings.filterLists.deleteFilterListModal.deleteButtonLabel": "削除", - "xpack.ml.settings.filterLists.deleteFilterListModal.deleteWarningMessage": "{selectedFilterListsLength, plural, one {このフィルダー} other {これらのフィルター}}を削除してよろしいですか?", "xpack.ml.settings.filterLists.deleteFilterListModal.modalTitle": "{selectedFilterListsLength, plural, one {{selectedFilterId}} other {# フィルターリスト}}の削除", "xpack.ml.settings.filterLists.deleteFilterLists.deletingErrorMessage": "フィルターリスト {filterListId} の削除中にエラーが発生しました。{respMessage}", "xpack.ml.settings.filterLists.deleteFilterLists.deletingNotificationMessage": "{filterListsToDeleteLength, plural, one {{filterListToDeleteId}} other {# フィルターリスト}}を削除しています", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 144bc1cac1852..ea04b1e14d959 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -253,31 +253,6 @@ "charts.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", "charts.controls.vislibBasicOptions.legendPositionLabel": "图例位置", "charts.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示", - "common.ui.flotCharts.aprLabel": "四月", - "common.ui.flotCharts.augLabel": "八月", - "common.ui.flotCharts.decLabel": "十二月", - "common.ui.flotCharts.febLabel": "二月", - "common.ui.flotCharts.friLabel": "周五", - "common.ui.flotCharts.janLabel": "一月", - "common.ui.flotCharts.julLabel": "七月", - "common.ui.flotCharts.junLabel": "六月", - "common.ui.flotCharts.marLabel": "三月", - "common.ui.flotCharts.mayLabel": "五月", - "common.ui.flotCharts.monLabel": "周一", - "common.ui.flotCharts.novLabel": "十一月", - "common.ui.flotCharts.octLabel": "十月", - "common.ui.flotCharts.pie.unableToDrawLabelsInsideCanvasErrorMessage": "无法用画布内包含的标签绘制饼图", - "common.ui.flotCharts.satLabel": "周六", - "common.ui.flotCharts.sepLabel": "九月", - "common.ui.flotCharts.sunLabel": "周日", - "common.ui.flotCharts.thuLabel": "周四", - "common.ui.flotCharts.tueLabel": "周二", - "common.ui.flotCharts.wedLabel": "周三", - "common.ui.stateManagement.unableToParseUrlErrorMessage": "无法解析 URL", - "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", - "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", - "common.ui.url.replacementFailedErrorMessage": "替换失败,未解析的表达式:{expr}", - "common.ui.url.savedObjectIsMissingNotificationMessage": "已保存对象缺失", "console.autocomplete.addMethodMetaText": "方法", "console.consoleDisplayName": "控制台", "console.consoleMenu.copyAsCurlMessage": "请求已复制为 cURL", @@ -1116,7 +1091,6 @@ "data.search.aggs.metrics.count.customLabel.help": "表示此聚合的定制标签", "data.search.aggs.metrics.count.enabled.help": "指定是否启用此聚合", "data.search.aggs.metrics.count.id.help": "此聚合的 ID", - "data.search.aggs.metrics.count.json.help": "聚合发送至 Elasticsearch 时要包括的高级 json", "data.search.aggs.metrics.count.schema.help": "要用于此聚合的方案", "data.search.aggs.metrics.countLabel": "计数", "data.search.aggs.metrics.countTitle": "计数", @@ -5070,7 +5044,6 @@ "xpack.apm.transactionActionMenu.viewInUptime": "状态", "xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "查看样例文档", "xpack.apm.transactionBreakdown.chartTitle": "跨度类型花费的时间", - "xpack.apm.transactionBreakdown.noData": "此时间范围内没有数据。", "xpack.apm.transactionCardinalityWarning.body": "唯一事务名称的数目超过 {bucketSize} 的已配置值。尝试重新配置您的代理以对类似的事务分组或增大 {codeBlock} 的值", "xpack.apm.transactionCardinalityWarning.docsLink": "在文档中了解详情", "xpack.apm.transactionCardinalityWarning.title": "此视图显示已报告事务的子集。", @@ -8130,7 +8103,6 @@ "xpack.indexLifecycleMgmt.editPolicy.learnAboutShardAllocationLink": "了解分片分配", "xpack.indexLifecycleMgmt.editPolicy.learnAboutTimingText": "了解计时", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText": "使用索引策略自动化索引生命周期的四个阶段,从频繁地写入到索引到删除索引。", - "xpack.indexLifecycleMgmt.editPolicy.loadPolicyErrorMessage": "加载策略时出错", "xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError": "最大存在时间必填。", "xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError": "最大文档数必填。", "xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError": "最大索引大小必填。", @@ -8269,7 +8241,6 @@ "xpack.indexLifecycleMgmt.policyTable.addLifecyclePolicyToTemplateConfirmModal.title": "将策略 “{name}” 添加到索引模板", "xpack.indexLifecycleMgmt.policyTable.addPolicyToTemplateButtonText": "将策略添加到索引模板", "xpack.indexLifecycleMgmt.policyTable.captionText": "下面是包含 {count, plural, one {# 行} other {# 行}}(共 {total} 行)的索引生命周期策略表。", - "xpack.indexLifecycleMgmt.policyTable.deletedPoliciesText": "已删除 {numSelected} 个{numSelected, plural, one {策略} other {策略}}", "xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonDisabledTooltip": "您无法删除索引正在使用的策略", "xpack.indexLifecycleMgmt.policyTable.deletePolicyButtonText": "删除策略", "xpack.indexLifecycleMgmt.policyTable.emptyPrompt.createButtonLabel": "创建策略", @@ -11143,7 +11114,6 @@ "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "删除索引模式 {destinationIndex} 的请求已确认。", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "删除目标索引 {destinationIndex} 的请求已确认。", "xpack.ml.dataframe.analyticsList.deleteDestinationIndexTitle": "删除目标索引 {indexName}", - "xpack.ml.dataframe.analyticsList.deleteModalBody": "是否确定要删除此分析作业?", "xpack.ml.dataframe.analyticsList.deleteModalCancelButton": "取消", "xpack.ml.dataframe.analyticsList.deleteModalDeleteButton": "删除", "xpack.ml.dataframe.analyticsList.deleteModalTitle": "删除 {analyticsId}", @@ -11639,7 +11609,6 @@ "xpack.ml.jobsList.deleteJobModal.cancelButtonLabel": "取消", "xpack.ml.jobsList.deleteJobModal.closeButtonLabel": "关闭", "xpack.ml.jobsList.deleteJobModal.deleteButtonLabel": "删除", - "xpack.ml.jobsList.deleteJobModal.deleteJobsDescription": "是否确定要删除{jobsCount, plural, one {此作业} other {这些作业}}?", "xpack.ml.jobsList.deleteJobModal.deleteJobsTitle": "删除 {jobsCount, plural, one {{jobId}} other {# 个作业}}", "xpack.ml.jobsList.deleteJobModal.deleteMultipleJobsDescription": "删除{jobsCount, plural, one {一个作业} other {多个作业}}会非常耗时。将在后台删除{jobsCount, plural, one {该作业} other {这些作业}},但删除的作业可能不会立即从作业列表中消失", "xpack.ml.jobsList.deleteJobModal.deletingJobsStatusLabel": "正在删除作业", @@ -12308,7 +12277,6 @@ "xpack.ml.ruleEditor.deleteJobRule.ruleNoLongerExistsErrorMessage": "作业 {jobId} 中不再存在检测工具索引 {detectorIndex} 的规则", "xpack.ml.ruleEditor.deleteRuleModal.cancelButtonLabel": "取消", "xpack.ml.ruleEditor.deleteRuleModal.deleteButtonLabel": "删除", - "xpack.ml.ruleEditor.deleteRuleModal.deleteRuleDescription": "是否确定要删除此规则?", "xpack.ml.ruleEditor.deleteRuleModal.deleteRuleLinkText": "删除规则", "xpack.ml.ruleEditor.deleteRuleModal.deleteRuleTitle": "删除规则", "xpack.ml.ruleEditor.detectorDescriptionList.detectorTitle": "检测工具", @@ -12402,7 +12370,6 @@ "xpack.ml.settings.filterLists.deleteFilterListModal.cancelButtonLabel": "取消", "xpack.ml.settings.filterLists.deleteFilterListModal.confirmButtonLabel": "删除", "xpack.ml.settings.filterLists.deleteFilterListModal.deleteButtonLabel": "删除", - "xpack.ml.settings.filterLists.deleteFilterListModal.deleteWarningMessage": "是否确定要删除{selectedFilterListsLength, plural, one {此筛选列表} other {这些筛选列表}}", "xpack.ml.settings.filterLists.deleteFilterListModal.modalTitle": "删除 {selectedFilterListsLength, plural, one {{selectedFilterId}} other {# 个筛选列表}}", "xpack.ml.settings.filterLists.deleteFilterLists.deletingErrorMessage": "删除筛选列表 {filterListId} 时出错。{respMessage}", "xpack.ml.settings.filterLists.deleteFilterLists.deletingNotificationMessage": "正在删除 {filterListsToDeleteLength, plural, one {{filterListToDeleteId}} other {# 个筛选列表}}", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx index 417a9e09086a2..b56ae35df5d06 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.test.tsx @@ -72,17 +72,31 @@ describe('index connector validation with minimal config', () => { describe('action params validation', () => { test('action params validation succeeds when action params is valid', () => { const actionParams = { - documents: ['test'], + documents: [{ test: 1234 }], }; expect(actionTypeModel.validateParams(actionParams)).toEqual({ - errors: {}, + errors: { + documents: [], + }, }); const emptyActionParams = {}; expect(actionTypeModel.validateParams(emptyActionParams)).toEqual({ - errors: {}, + errors: { + documents: ['Document is required and should be a valid JSON object.'], + }, + }); + + const invalidDocumentActionParams = { + documents: [{}], + }; + + expect(actionTypeModel.validateParams(invalidDocumentActionParams)).toEqual({ + errors: { + documents: ['Document is required and should be a valid JSON object.'], + }, }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.tsx index 3ee663a5fc8a0..c0255650e0f37 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index.tsx @@ -44,8 +44,23 @@ export function getActionType(): ActionTypeModel import('./es_index_connector')), actionParamsFields: lazy(() => import('./es_index_params')), - validateParams: (): ValidationResult => { - return { errors: {} }; + validateParams: (actionParams: IndexActionParams): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + documents: new Array(), + }; + validationResult.errors = errors; + if (!actionParams.documents?.length || Object.keys(actionParams.documents[0]).length === 0) { + errors.documents.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredDocumentJson', + { + defaultMessage: 'Document is required and should be a valid JSON object.', + } + ) + ); + } + return validationResult; }, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx index e8e8cc582512e..495707db4975c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx @@ -17,6 +17,7 @@ export const IndexParamsFields = ({ editAction, messageVariables, docLinks, + errors, }: ActionParamsProps) => { const { documents } = actionParams; @@ -24,8 +25,10 @@ export const IndexParamsFields = ({ try { const documentsJSON = JSON.parse(updatedDocuments); editAction('documents', [documentsJSON], index); - // eslint-disable-next-line no-empty - } catch (e) {} + } catch (e) { + // set document as empty to turn on the validation for non empty valid JSON object + editAction('documents', [{}], index); + } }; return ( @@ -34,7 +37,7 @@ export const IndexParamsFields = ({ messageVariables={messageVariables} paramsProperty={'documents'} inputTargetValue={ - documents && documents.length > 0 ? ((documents[0] as unknown) as string) : '' + documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined } label={i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel', @@ -48,6 +51,7 @@ export const IndexParamsFields = ({ defaultMessage: 'Code editor', } )} + errors={errors.documents as string[]} onDocumentsChange={onDocumentsChange} helpText={ } + onBlur={() => { + if ( + !(documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined) + ) { + // set document as empty to turn on the validation for non empty valid JSON object + onDocumentsChange('{}'); + } + }} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.tsx index 1dfd9e3edc2c5..ff9ad936f224f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.tsx @@ -21,7 +21,7 @@ const WebhookParamsFields: React.FunctionComponent { editAction('body', json, index); }} + onBlur={() => { + if (!body) { + editAction('body', '', index); + } + }} /> ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx index 0b8184fc441fd..5ea15deb53161 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx @@ -14,12 +14,13 @@ import { ActionVariable } from '../../types'; interface Props { messageVariables?: ActionVariable[]; paramsProperty: string; - inputTargetValue: string; + inputTargetValue?: string; label: string; errors?: string[]; areaLabel?: string; onDocumentsChange: (data: string) => void; helpText?: JSX.Element; + onBlur?: () => void; } export const JsonEditorWithMessageVariables: React.FunctionComponent = ({ @@ -31,6 +32,7 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent = ({ areaLabel, onDocumentsChange, helpText, + onBlur, }) => { const [cursorPosition, setCursorPosition] = useState(null); @@ -84,6 +86,7 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent = ({ onDocumentsChange(convertToJson(xjson)); }} onCursorChange={(_value: any) => onClickWithMessageVariable(_value)} + onBlur={onBlur} /> ); diff --git a/x-pack/plugins/ui_actions_enhanced/public/can_inherit_time_range.test.ts b/x-pack/plugins/ui_actions_enhanced/public/can_inherit_time_range.test.ts index 5f2e9d07c4bcb..9fb3a54d5a182 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/can_inherit_time_range.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/can_inherit_time_range.test.ts @@ -10,8 +10,6 @@ import { HelloWorldEmbeddable } from '../../../../examples/embeddable_examples/p /** eslint-enable */ import { TimeRangeEmbeddable, TimeRangeContainer } from './test_helpers'; -jest.mock('ui/new_platform'); - test('canInheritTimeRange returns false if embeddable is inside container without a time range', () => { const embeddable = new TimeRangeEmbeddable( { id: '1234', timeRange: { from: 'noxw-15m', to: 'now' } }, diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx index 78252dccd20d2..9cc64defc1795 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx @@ -15,7 +15,7 @@ import { urlDrilldownActionFactory, } from './test_data'; import { ActionFactory } from '../../dynamic_actions'; -import { licenseMock } from '../../../../licensing/common/licensing.mock'; +import { licensingMock } from '../../../../licensing/public/mocks'; // TODO: afterEach is not available for it globally during setup // https://github.com/elastic/kibana/issues/59469 @@ -68,8 +68,12 @@ test('If not enough license, button is disabled', () => { { ...urlDrilldownActionFactory, minimalLicense: 'gold', + licenseFeatureName: 'Url Drilldown', }, - () => licenseMock.createLicense() + { + getLicense: () => licensingMock.createLicense(), + getFeatureUsageStart: () => licensingMock.createStart().featureUsage, + } ); const screen = render(); diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.tsx index 7e4fe1de8be8d..a49251811239f 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.tsx @@ -93,7 +93,7 @@ export const ActionWizard: React.FC = ({ if ( !currentActionFactory && actionFactories.length === 1 && - actionFactories[0].isCompatibleLicence() + actionFactories[0].isCompatibleLicense() ) { onActionFactoryChange(actionFactories[0]); } @@ -314,8 +314,8 @@ const ActionFactorySelector: React.FC = ({ * make sure not compatible factories are in the end */ const ensureOrder = (factories: ActionFactory[]) => { - const compatibleLicense = factories.filter((f) => f.isCompatibleLicence()); - const notCompatibleLicense = factories.filter((f) => !f.isCompatibleLicence()); + const compatibleLicense = factories.filter((f) => f.isCompatibleLicense()); + const notCompatibleLicense = factories.filter((f) => !f.isCompatibleLicense()); return [ ...compatibleLicense.sort((f1, f2) => f2.order - f1.order), ...notCompatibleLicense.sort((f1, f2) => f2.order - f1.order), @@ -328,7 +328,7 @@ const ActionFactorySelector: React.FC = ({ = ({ label={actionFactory.getDisplayName(context)} data-test-subj={`${TEST_SUBJ_ACTION_FACTORY_ITEM}-${actionFactory.id}`} onClick={() => onActionFactorySelected(actionFactory)} - disabled={!actionFactory.isCompatibleLicence()} + disabled={!actionFactory.isCompatibleLicense()} > {actionFactory.getIconType(context) && ( diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx index d48cb13b1a470..71286e9a59c06 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx @@ -10,7 +10,7 @@ import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/p import { ActionWizard } from './action_wizard'; import { ActionFactory, ActionFactoryDefinition } from '../../dynamic_actions'; import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; -import { licenseMock } from '../../../../licensing/common/licensing.mock'; +import { licensingMock } from '../../../../licensing/public/mocks'; import { APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER, @@ -116,9 +116,10 @@ export const dashboardDrilldownActionFactory: ActionFactoryDefinition< }, }; -export const dashboardFactory = new ActionFactory(dashboardDrilldownActionFactory, () => - licenseMock.createLicense() -); +export const dashboardFactory = new ActionFactory(dashboardDrilldownActionFactory, { + getLicense: () => licensingMock.createLicense(), + getFeatureUsageStart: () => licensingMock.createStart().featureUsage, +}); interface UrlDrilldownConfig { url: string; @@ -176,9 +177,10 @@ export const urlDrilldownActionFactory: ActionFactoryDefinition - licenseMock.createLicense() -); +export const urlFactory = new ActionFactory(urlDrilldownActionFactory, { + getLicense: () => licensingMock.createLicense(), + getFeatureUsageStart: () => licensingMock.createStart().featureUsage, +}); export const mockSupportedTriggers: TriggerId[] = [ VALUE_CLICK_TRIGGER, diff --git a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts index 7919d2d2482ae..9af79c9ac5259 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts @@ -21,8 +21,6 @@ import { import { nextTick } from 'test_utils/enzyme_helpers'; import { ReactElement } from 'react'; -jest.mock('ui/new_platform'); - const createOpenModalMock = () => { const mock = jest.fn(); mock.mockReturnValue({ close: jest.fn() }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 9fca785ec9072..b708bbc57375d 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -148,7 +148,7 @@ export function createFlyoutManageDrilldowns({ icon: actionFactory?.getIconType(drilldownFactoryContext), error: !actionFactory ? invalidDrilldownType(drilldown.action.factoryId) // this shouldn't happen for the end user, but useful during development - : !actionFactory.isCompatibleLicence() + : !actionFactory.isCompatibleLicense() ? insufficientLicenseLevel : undefined, triggers: drilldown.triggers.map((trigger) => getTrigger(trigger as TriggerId)), diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/form_drilldown_wizard/form_drilldown_wizard.tsx index bb3eb89d8f199..d7f94a52088b7 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -75,7 +75,7 @@ export const FormDrilldownWizard: React.FC = ({ ); const hasNotCompatibleLicenseFactory = () => - actionFactories?.some((f) => !f.isCompatibleLicence()); + actionFactories?.some((f) => !f.isCompatibleLicense()); const renderGetMoreActionsLink = () => ( diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts index ff455c6ae45b6..8faccc088a327 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts @@ -37,11 +37,18 @@ export interface DrilldownDefinition< id: string; /** - * Minimal licence level + * Minimal license level * Empty means no restrictions */ minimalLicense?: LicenseType; + /** + * Required when `minimalLicense` is used. + * Is a user-facing string. Has to be unique. Doesn't need i18n. + * The feature's name will be displayed to Cloud end-users when they're billed based on their feature usage. + */ + licenseFeatureName?: string; + /** * Determines the display order of the drilldowns in the flyout picker. * Higher numbers are displayed first. diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts index a07fed8486438..032a4a63fe2e9 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts @@ -7,6 +7,7 @@ import { ActionFactory } from './action_factory'; import { ActionFactoryDefinition } from './action_factory_definition'; import { licensingMock } from '../../../licensing/public/mocks'; +import { PublicLicense } from '../../../licensing/public'; const def: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', @@ -22,34 +23,94 @@ const def: ActionFactoryDefinition = { supportedTriggers: () => [], }; +const featureUsage = licensingMock.createStart().featureUsage; + +const createActionFactory = ( + defOverride: Partial = {}, + license?: Partial +) => { + return new ActionFactory( + { ...def, ...defOverride }, + { + getLicense: () => licensingMock.createLicense({ license }), + getFeatureUsageStart: () => featureUsage, + } + ); +}; + describe('License & ActionFactory', () => { test('no license requirements', async () => { - const factory = new ActionFactory(def, () => licensingMock.createLicense()); + const factory = createActionFactory(); expect(await factory.isCompatible({ triggers: [] })).toBe(true); - expect(factory.isCompatibleLicence()).toBe(true); + expect(factory.isCompatibleLicense()).toBe(true); }); test('not enough license level', async () => { - const factory = new ActionFactory({ ...def, minimalLicense: 'gold' }, () => - licensingMock.createLicense() - ); + const factory = createActionFactory({ minimalLicense: 'gold', licenseFeatureName: 'Feature' }); expect(await factory.isCompatible({ triggers: [] })).toBe(true); - expect(factory.isCompatibleLicence()).toBe(false); + expect(factory.isCompatibleLicense()).toBe(false); }); - test('licence has expired', async () => { - const factory = new ActionFactory({ ...def, minimalLicense: 'gold' }, () => - licensingMock.createLicense({ license: { type: 'gold', status: 'expired' } }) + test('license has expired', async () => { + const factory = createActionFactory( + { minimalLicense: 'gold', licenseFeatureName: 'Feature' }, + { type: 'gold', status: 'expired' } ); expect(await factory.isCompatible({ triggers: [] })).toBe(true); - expect(factory.isCompatibleLicence()).toBe(false); + expect(factory.isCompatibleLicense()).toBe(false); }); test('enough license level', async () => { - const factory = new ActionFactory({ ...def, minimalLicense: 'gold' }, () => - licensingMock.createLicense({ license: { type: 'gold' } }) + const factory = createActionFactory( + { minimalLicense: 'gold', licenseFeatureName: 'Feature' }, + { type: 'gold' } ); + expect(await factory.isCompatible({ triggers: [] })).toBe(true); - expect(factory.isCompatibleLicence()).toBe(true); + expect(factory.isCompatibleLicense()).toBe(true); + }); + + describe('licenseFeatureName', () => { + test('licenseFeatureName is required, if minimalLicense is provided', () => { + expect(() => { + createActionFactory(); + }).not.toThrow(); + + expect(() => { + createActionFactory({ minimalLicense: 'gold', licenseFeatureName: 'feature' }); + }).not.toThrow(); + + expect(() => { + createActionFactory({ minimalLicense: 'gold' }); + }).toThrow(); + }); + + test('"licenseFeatureName"', () => { + expect( + createActionFactory({ minimalLicense: 'gold', licenseFeatureName: 'feature' }) + .licenseFeatureName + ).toBe('feature'); + expect(createActionFactory().licenseFeatureName).toBeUndefined(); + }); + }); + + describe('notifyFeatureUsage', () => { + const spy = jest.spyOn(featureUsage, 'notifyUsage'); + beforeEach(() => { + spy.mockClear(); + }); + test('is not called if no license requirements', async () => { + const action = createActionFactory().create({ name: 'fake', config: {} }); + await action.execute({}); + expect(spy).not.toBeCalled(); + }); + test('is called if has license requirements', async () => { + const action = createActionFactory({ + minimalLicense: 'gold', + licenseFeatureName: 'feature', + }).create({ name: 'fake', config: {} }); + await action.execute({}); + expect(spy).toBeCalledWith('feature'); + }); }); }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts index 35e06ab036fc9..35a82adf9896d 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts @@ -13,9 +13,14 @@ import { import { ActionFactoryDefinition } from './action_factory_definition'; import { Configurable } from '../../../../../src/plugins/kibana_utils/public'; import { BaseActionFactoryContext, SerializedAction } from './types'; -import { ILicense } from '../../../licensing/public'; +import { ILicense, LicensingPluginStart } from '../../../licensing/public'; import { UiActionsActionDefinition as ActionDefinition } from '../../../../../src/plugins/ui_actions/public'; +export interface ActionFactoryDeps { + readonly getLicense: () => ILicense; + readonly getFeatureUsageStart: () => LicensingPluginStart['featureUsage']; +} + export class ActionFactory< Config extends object = object, SupportedTriggers extends TriggerId = TriggerId, @@ -31,11 +36,18 @@ export class ActionFactory< FactoryContext, ActionContext >, - protected readonly getLicence: () => ILicense - ) {} + protected readonly deps: ActionFactoryDeps + ) { + if (def.minimalLicense && !def.licenseFeatureName) { + throw new Error( + `ActionFactory [actionFactory.id = ${def.id}] "licenseFeatureName" is required, if "minimalLicense" is provided` + ); + } + } public readonly id = this.def.id; public readonly minimalLicense = this.def.minimalLicense; + public readonly licenseFeatureName = this.def.licenseFeatureName; public readonly order = this.def.order || 0; public readonly MenuItem? = this.def.MenuItem; public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; @@ -65,13 +77,13 @@ export class ActionFactory< } /** - * Does this action factory licence requirements + * Does this action factory license requirements * compatible with current license? */ - public isCompatibleLicence() { + public isCompatibleLicense() { if (!this.minimalLicense) return true; - const licence = this.getLicence(); - return licence.isAvailable && licence.isActive && licence.hasAtLeast(this.minimalLicense); + const license = this.deps.getLicense(); + return license.isAvailable && license.isActive && license.hasAtLeast(this.minimalLicense); } public create( @@ -81,14 +93,31 @@ export class ActionFactory< return { ...action, isCompatible: async (context: ActionContext): Promise => { - if (!this.isCompatibleLicence()) return false; + if (!this.isCompatibleLicense()) return false; if (!action.isCompatible) return true; return action.isCompatible(context); }, + execute: async (context: ActionContext): Promise => { + this.notifyFeatureUsage(); + return action.execute(context); + }, }; } public supportedTriggers(): SupportedTriggers[] { return this.def.supportedTriggers(); } + + private notifyFeatureUsage(): void { + if (!this.minimalLicense || !this.licenseFeatureName) return; + this.deps + .getFeatureUsageStart() + .notifyUsage(this.licenseFeatureName) + .catch(() => { + // eslint-disable-next-line no-console + console.warn( + `ActionFactory [actionFactory.id = ${this.def.id}] fail notify feature usage.` + ); + }); + } } diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts index d79614e47ccd4..91b8c8ec1e5ef 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts @@ -34,11 +34,18 @@ export interface ActionFactoryDefinition< id: string; /** - * Minimal licence level - * Empty means no licence restrictions + * Minimal license level + * Empty means no license restrictions */ readonly minimalLicense?: LicenseType; + /** + * Required when `minimalLicense` is used. + * Is a user-facing string. Has to be unique. Doesn't need i18n. + * The feature's name will be displayed to Cloud end-users when they're billed based on their feature usage. + */ + licenseFeatureName?: string; + /** * This method should return a definition of a new action, normally used to * register it in `ui_actions` registry. diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts index 0b0cd39e35e25..39d9dfeca2fd6 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts @@ -87,7 +87,9 @@ const setup = ( actions, }); const uiActionsEnhancements = new UiActionsServiceEnhancements({ - getLicenseInfo, + getLicense: getLicenseInfo, + featureUsageSetup: licensingMock.createSetup().featureUsage, + getFeatureUsageStart: () => licensingMock.createStart().featureUsage, }); const manager = new DynamicActionManager({ isCompatible, @@ -671,11 +673,13 @@ describe('DynamicActionManager', () => { const basicActionFactory: ActionFactoryDefinition = { ...actionFactoryDefinition1, minimalLicense: 'basic', + licenseFeatureName: 'Feature 1', }; const goldActionFactory: ActionFactoryDefinition = { ...actionFactoryDefinition2, minimalLicense: 'gold', + licenseFeatureName: 'Feature 2', }; uiActions.registerActionFactory(basicActionFactory); diff --git a/x-pack/plugins/ui_actions_enhanced/public/mocks.ts b/x-pack/plugins/ui_actions_enhanced/public/mocks.ts index ff07d6e74a9c0..17a6fc1b955df 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/mocks.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/mocks.ts @@ -11,6 +11,7 @@ import { embeddablePluginMock } from '../../../../src/plugins/embeddable/public/ import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '.'; import { plugin as pluginInitializer } from '.'; import { licensingMock } from '../../licensing/public/mocks'; +import { StartDependencies } from './plugin'; export type Setup = jest.Mocked; export type Start = jest.Mocked; @@ -35,7 +36,7 @@ const createStartContract = (): Start => { }; const createPlugin = ( - coreSetup: CoreSetup = coreMock.createSetup(), + coreSetup: CoreSetup = coreMock.createSetup(), coreStart: CoreStart = coreMock.createStart() ) => { const pluginInitializerContext = coreMock.createPluginInitializerContext(); @@ -47,6 +48,7 @@ const createPlugin = ( const setup = plugin.setup(coreSetup, { uiActions: uiActions.setup, embeddable: embeddable.setup, + licensing: licensingMock.createSetup(), }); return { diff --git a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts index 5069b485b198d..015531aab9743 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts @@ -36,16 +36,17 @@ import { } from './custom_time_range_badge'; import { CommonlyUsedRange } from './types'; import { UiActionsServiceEnhancements } from './services'; -import { ILicense, LicensingPluginStart } from '../../licensing/public'; +import { ILicense, LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public'; import { createFlyoutManageDrilldowns } from './drilldowns'; -import { Storage } from '../../../../src/plugins/kibana_utils/public'; +import { createStartServicesGetter, Storage } from '../../../../src/plugins/kibana_utils/public'; interface SetupDependencies { embeddable: EmbeddableSetup; // Embeddable are needed because they register basic triggers/actions. uiActions: UiActionsSetup; + licensing: LicensingPluginSetup; } -interface StartDependencies { +export interface StartDependencies { embeddable: EmbeddableStart; uiActions: UiActionsStart; licensing: LicensingPluginStart; @@ -70,23 +71,30 @@ declare module '../../../../src/plugins/ui_actions/public' { export class AdvancedUiActionsPublicPlugin implements Plugin { - readonly licenceInfo = new BehaviorSubject(undefined); + readonly licenseInfo = new BehaviorSubject(undefined); private getLicenseInfo(): ILicense { - if (!this.licenceInfo.getValue()) { + if (!this.licenseInfo.getValue()) { throw new Error( - 'AdvancedUiActionsPublicPlugin: Licence is not ready! Licence becomes available only after setup.' + 'AdvancedUiActionsPublicPlugin: License is not ready! License becomes available only after setup.' ); } - return this.licenceInfo.getValue()!; + return this.licenseInfo.getValue()!; } - private readonly enhancements = new UiActionsServiceEnhancements({ - getLicenseInfo: () => this.getLicenseInfo(), - }); + private enhancements?: UiActionsServiceEnhancements; private subs: Subscription[] = []; constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { uiActions }: SetupDependencies): SetupContract { + public setup( + core: CoreSetup, + { uiActions, licensing }: SetupDependencies + ): SetupContract { + const startServices = createStartServicesGetter(core.getStartServices); + this.enhancements = new UiActionsServiceEnhancements({ + getLicense: () => this.getLicenseInfo(), + featureUsageSetup: licensing.featureUsage, + getFeatureUsageStart: () => startServices().plugins.licensing.featureUsage, + }); return { ...uiActions, ...this.enhancements, @@ -94,7 +102,7 @@ export class AdvancedUiActionsPublicPlugin } public start(core: CoreStart, { uiActions, licensing }: StartDependencies): StartContract { - this.subs.push(licensing.license$.subscribe(this.licenceInfo)); + this.subs.push(licensing.license$.subscribe(this.licenseInfo)); const dateFormat = core.uiSettings.get('dateFormat') as string; const commonlyUsedRanges = core.uiSettings.get( @@ -117,9 +125,9 @@ export class AdvancedUiActionsPublicPlugin return { ...uiActions, - ...this.enhancements, + ...this.enhancements!, FlyoutManageDrilldowns: createFlyoutManageDrilldowns({ - actionFactories: this.enhancements.getActionFactories(), + actionFactories: this.enhancements!.getActionFactories(), getTrigger: (triggerId: TriggerId) => uiActions.getTrigger(triggerId), storage: new Storage(window?.localStorage), toastService: core.notifications.toasts, diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts index 08823833b9af2..3a0b65d2ed844 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts @@ -4,11 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UiActionsServiceEnhancements } from './ui_actions_service_enhancements'; +import { + UiActionsServiceEnhancements, + UiActionsServiceEnhancementsParams, +} from './ui_actions_service_enhancements'; import { ActionFactoryDefinition, ActionFactory } from '../dynamic_actions'; import { licensingMock } from '../../../licensing/public/mocks'; -const getLicenseInfo = () => licensingMock.createLicense(); +const deps: UiActionsServiceEnhancementsParams = { + getLicense: () => licensingMock.createLicense(), + featureUsageSetup: licensingMock.createSetup().featureUsage, + getFeatureUsageStart: () => licensingMock.createStart().featureUsage, +}; describe('UiActionsService', () => { describe('action factories', () => { @@ -34,7 +41,7 @@ describe('UiActionsService', () => { }; test('.getActionFactories() returns empty array if no action factories registered', () => { - const service = new UiActionsServiceEnhancements({ getLicenseInfo }); + const service = new UiActionsServiceEnhancements(deps); const factories = service.getActionFactories(); @@ -42,7 +49,7 @@ describe('UiActionsService', () => { }); test('can register and retrieve an action factory', () => { - const service = new UiActionsServiceEnhancements({ getLicenseInfo }); + const service = new UiActionsServiceEnhancements(deps); service.registerActionFactory(factoryDefinition1); @@ -53,7 +60,7 @@ describe('UiActionsService', () => { }); test('can retrieve all action factories', () => { - const service = new UiActionsServiceEnhancements({ getLicenseInfo }); + const service = new UiActionsServiceEnhancements(deps); service.registerActionFactory(factoryDefinition1); service.registerActionFactory(factoryDefinition2); @@ -67,7 +74,7 @@ describe('UiActionsService', () => { }); test('throws when retrieving action factory that does not exist', () => { - const service = new UiActionsServiceEnhancements({ getLicenseInfo }); + const service = new UiActionsServiceEnhancements(deps); service.registerActionFactory(factoryDefinition1); @@ -77,7 +84,7 @@ describe('UiActionsService', () => { }); test('isCompatible from definition is used on registered factory', async () => { - const service = new UiActionsServiceEnhancements({ getLicenseInfo }); + const service = new UiActionsServiceEnhancements(deps); service.registerActionFactory({ ...factoryDefinition1, @@ -88,5 +95,27 @@ describe('UiActionsService', () => { service.getActionFactory(factoryDefinition1.id).isCompatible({ triggers: [] }) ).resolves.toBe(false); }); + + describe('registerFeature for licensing', () => { + const spy = jest.spyOn(deps.featureUsageSetup, 'register'); + beforeEach(() => { + spy.mockClear(); + }); + test('registerFeature is not called if no license requirements', () => { + const service = new UiActionsServiceEnhancements(deps); + service.registerActionFactory(factoryDefinition1); + expect(spy).not.toBeCalled(); + }); + + test('registerFeature is called if has license requirements', () => { + const service = new UiActionsServiceEnhancements(deps); + service.registerActionFactory({ + ...factoryDefinition1, + minimalLicense: 'gold', + licenseFeatureName: 'a name', + }); + expect(spy).toBeCalledWith('a name', 'gold'); + }); + }); }); }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 9575329514835..b8086c16f5e71 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -13,19 +13,22 @@ import { import { DrilldownDefinition } from '../drilldowns'; import { ILicense } from '../../../licensing/common/types'; import { TriggerContextMapping, TriggerId } from '../../../../../src/plugins/ui_actions/public'; +import { LicensingPluginSetup, LicensingPluginStart } from '../../../licensing/public'; export interface UiActionsServiceEnhancementsParams { readonly actionFactories?: ActionFactoryRegistry; - readonly getLicenseInfo: () => ILicense; + readonly getLicense: () => ILicense; + readonly featureUsageSetup: LicensingPluginSetup['featureUsage']; + readonly getFeatureUsageStart: () => LicensingPluginStart['featureUsage']; } export class UiActionsServiceEnhancements { protected readonly actionFactories: ActionFactoryRegistry; - protected readonly getLicenseInfo: () => ILicense; + protected readonly deps: Omit; - constructor({ actionFactories = new Map(), getLicenseInfo }: UiActionsServiceEnhancementsParams) { + constructor({ actionFactories = new Map(), ...deps }: UiActionsServiceEnhancementsParams) { this.actionFactories = actionFactories; - this.getLicenseInfo = getLicenseInfo; + this.deps = deps; } /** @@ -51,9 +54,10 @@ export class UiActionsServiceEnhancements { SupportedTriggers, FactoryContext, ActionContext - >(definition, this.getLicenseInfo); + >(definition, this.deps); this.actionFactories.set(actionFactory.id, actionFactory as ActionFactory); + this.registerFeatureUsage(definition); }; public readonly getActionFactory = (actionFactoryId: string): ActionFactory => { @@ -94,6 +98,7 @@ export class UiActionsServiceEnhancements { execute, getHref, minimalLicense, + licenseFeatureName, supportedTriggers, isCompatible, }: DrilldownDefinition): void => { @@ -105,6 +110,7 @@ export class UiActionsServiceEnhancements { > = { id: factoryId, minimalLicense, + licenseFeatureName, order, CollectConfig, createConfig, @@ -128,4 +134,19 @@ export class UiActionsServiceEnhancements { this.registerActionFactory(actionFactory); }; + + private registerFeatureUsage = (definition: ActionFactoryDefinition): void => { + if (!definition.minimalLicense || !definition.licenseFeatureName) return; + + // Intentionally don't wait for response because + // happens in setup phase and has to be sync + this.deps.featureUsageSetup + .register(definition.licenseFeatureName, definition.minimalLicense) + .catch(() => { + // eslint-disable-next-line no-console + console.warn( + `ActionFactory [actionFactory.id = ${definition.id}] fail to register feature for featureUsage.` + ); + }); + }; } diff --git a/x-pack/plugins/uptime/public/breadcrumbs.ts b/x-pack/plugins/uptime/public/breadcrumbs.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/uptime/public/breadcrumbs.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap index cf80d2de38b3f..261f3a016d4db 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap @@ -44,18 +44,23 @@ exports[`DonutChart component passes correct props without errors for valid prop }, }, "axes": Object { - "axisLineStyle": Object { + "axisLine": Object { "stroke": "#eaeaea", "strokeWidth": 1, + "visible": true, }, - "axisTitleStyle": Object { + "axisTitle": Object { "fill": "#333", "fontFamily": "sans-serif", "fontSize": 12, "fontStyle": "bold", - "padding": 8, + "padding": Object { + "inner": 8, + "outer": 0, + }, + "visible": true, }, - "gridLineStyle": Object { + "gridLine": Object { "horizontal": Object { "dash": Array [ 0, @@ -64,7 +69,7 @@ exports[`DonutChart component passes correct props without errors for valid prop "opacity": 1, "stroke": "#D3DAE6", "strokeWidth": 1, - "visible": true, + "visible": false, }, "vertical": Object { "dash": Array [ @@ -74,17 +79,30 @@ exports[`DonutChart component passes correct props without errors for valid prop "opacity": 1, "stroke": "#D3DAE6", "strokeWidth": 1, - "visible": true, + "visible": false, }, }, - "tickLabelStyle": Object { + "tickLabel": Object { + "alignment": Object { + "horizontal": "near", + "vertical": "near", + }, "fill": "#777", "fontFamily": "sans-serif", "fontSize": 10, "fontStyle": "normal", - "padding": 4, + "offset": Object { + "reference": "local", + "x": 0, + "y": 0, + }, + "padding": 0, + "rotation": 0, + "visible": true, }, - "tickLineStyle": Object { + "tickLine": Object { + "padding": 10, + "size": 10, "stroke": "#eaeaea", "strokeWidth": 1, "visible": true, @@ -164,6 +182,7 @@ exports[`DonutChart component passes correct props without errors for valid prop }, "legend": Object { "horizontalHeight": 64, + "margin": 0, "spacingBuffer": 10, "verticalWidth": 200, }, @@ -217,17 +236,20 @@ exports[`DonutChart component passes correct props without errors for valid prop }, }, "axes": Object { - "axisLineStyle": Object { + "axisLine": Object { "stroke": "rgba(238, 240, 243, 1)", }, - "axisTitleStyle": Object { + "axisTitle": Object { "fill": "rgba(52, 55, 65, 1)", "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", "fontSize": 12, - "padding": 10, + "padding": Object { + "inner": 10, + "outer": 0, + }, }, - "gridLineStyle": Object { + "gridLine": Object { "horizontal": Object { "dash": Array [ 0, @@ -249,14 +271,17 @@ exports[`DonutChart component passes correct props without errors for valid prop "visible": true, }, }, - "tickLabelStyle": Object { + "tickLabel": Object { "fill": "rgba(105, 112, 125, 1)", "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", "fontSize": 10, - "padding": 8, + "padding": Object { + "inner": 10, + "outer": 8, + }, }, - "tickLineStyle": Object { + "tickLine": Object { "stroke": "rgba(238, 240, 243, 1)", "strokeWidth": 1, "visible": false, diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts index 9ed453d286285..568b0873b8dbb 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { KibanaRequest } from 'kibana/server'; import moment from 'moment'; import { schema } from '@kbn/config-schema'; -import { ILegacyScopedClusterClient } from 'kibana/server'; import { updateState } from './common'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { commonStateTranslations, durationAnomalyTranslations } from './translations'; @@ -36,13 +36,11 @@ export const getAnomalySummary = (anomaly: AnomaliesTableRecord, monitorInfo: Pi const getAnomalies = async ( plugins: UptimeCorePlugins, - mlClusterClient: ILegacyScopedClusterClient, params: Record, lastCheckedAt: string ) => { - const { getAnomaliesTableData } = plugins.ml.resultsServiceProvider(mlClusterClient, { - params: 'DummyKibanaRequest', - } as any); + const fakeRequest = {} as KibanaRequest; + const { getAnomaliesTableData } = plugins.ml.resultsServiceProvider(fakeRequest); return await getAnomaliesTableData( [getMLJobId(params.monitorId)], @@ -82,23 +80,12 @@ export const durationAnomalyAlertFactory: UptimeAlertTypeFactory = (_server, _li producer: 'uptime', async executor(options) { const { - services: { - alertInstanceFactory, - callCluster, - savedObjectsClient, - getLegacyScopedClusterClient, - }, + services: { alertInstanceFactory, callCluster, savedObjectsClient }, state, params, } = options; - const { anomalies } = - (await getAnomalies( - plugins, - getLegacyScopedClusterClient(plugins.ml.mlClient), - params, - state.lastCheckedAt - )) ?? {}; + const { anomalies } = (await getAnomalies(plugins, params, state.lastCheckedAt)) ?? {}; const foundAnomalies = anomalies?.length > 0; diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts index f0222de02697d..015d9a4925f3e 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts @@ -203,28 +203,32 @@ describe('monitor availability', () => { }, }, }, - ], - "minimum_should_match": 1, - "should": Array [ Object { "bool": Object { "minimum_should_match": 1, "should": Array [ Object { - "match_phrase": Object { - "monitor.id": "apm-dev", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "apm-dev", + }, + }, + ], }, }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ Object { - "match_phrase": Object { - "monitor.id": "auto-http-0X8D6082B94BBE3B8A", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "auto-http-0X8D6082B94BBE3B8A", + }, + }, + ], }, }, ], @@ -797,6 +801,133 @@ describe('monitor availability', () => { ] `); }); + + it('does not overwrite filters', async () => { + const [callES, esMock] = setupMockEsCompositeQuery< + AvailabilityKey, + GetMonitorAvailabilityResult, + AvailabilityDoc + >( + [ + { + bucketCriteria: [], + }, + ], + genBucketItem + ); + await getMonitorAvailability({ + callES, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, + range: 3, + rangeUnit: 's', + threshold: '99', + filters: JSON.stringify({ bool: { filter: [{ term: { 'monitor.id': 'foo' } }] } }), + }); + const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + expect(params).toMatchInlineSnapshot(` + Object { + "body": Object { + "aggs": Object { + "monitors": Object { + "aggs": Object { + "down_sum": Object { + "sum": Object { + "field": "summary.down", + "missing": 0, + }, + }, + "fields": Object { + "top_hits": Object { + "size": 1, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + }, + }, + "filtered": Object { + "bucket_selector": Object { + "buckets_path": Object { + "threshold": "ratio.value", + }, + "script": "params.threshold < 0.99", + }, + }, + "ratio": Object { + "bucket_script": Object { + "buckets_path": Object { + "downTotal": "down_sum", + "upTotal": "up_sum", + }, + "script": " + if (params.upTotal + params.downTotal > 0) { + return params.upTotal / (params.upTotal + params.downTotal); + } return null;", + }, + }, + "up_sum": Object { + "sum": Object { + "field": "summary.up", + "missing": 0, + }, + }, + }, + "composite": Object { + "size": 2000, + "sources": Array [ + Object { + "monitorId": Object { + "terms": Object { + "field": "monitor.id", + }, + }, + }, + Object { + "location": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + }, + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-3s", + "lte": "now", + }, + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "monitor.id": "foo", + }, + }, + ], + }, + }, + ], + }, + }, + "size": 0, + }, + "index": "heartbeat-8*", + } + `); + }); }); describe('formatBuckets', () => { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts index 7dba71a8126e2..e61d736e37106 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts @@ -148,28 +148,32 @@ describe('getMonitorStatus', () => { }, }, }, - ], - "minimum_should_match": 1, - "should": Array [ Object { "bool": Object { "minimum_should_match": 1, "should": Array [ Object { - "match_phrase": Object { - "monitor.id": "apm-dev", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "apm-dev", + }, + }, + ], }, }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ Object { - "match_phrase": Object { - "monitor.id": "auto-http-0X8D6082B94BBE3B8A", + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "auto-http-0X8D6082B94BBE3B8A", + }, + }, + ], }, }, ], @@ -286,6 +290,296 @@ describe('getMonitorStatus', () => { `); }); + it('properly assigns filters for complex kuery filters', async () => { + const [callES, esMock] = setupMockEsCompositeQuery( + [{ bucketCriteria: [] }], + genBucketItem + ); + const clientParameters = { + timerange: { + from: 'now-15m', + to: 'now', + }, + numTimes: 5, + locations: [], + filters: { + bool: { + filter: [ + { + bool: { + should: [ + { + match_phrase: { + tags: 'org:google', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [ + { + match_phrase: { + 'monitor.type': 'http', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + match_phrase: { + 'monitor.type': 'tcp', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + }; + await getMonitorStatus({ + callES, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, + ...clientParameters, + }); + expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); + const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + expect(params).toMatchInlineSnapshot(` + Object { + "body": Object { + "aggs": Object { + "monitors": Object { + "aggs": Object { + "fields": Object { + "top_hits": Object { + "size": 1, + }, + }, + }, + "composite": Object { + "size": 2000, + "sources": Array [ + Object { + "monitorId": Object { + "terms": Object { + "field": "monitor.id", + }, + }, + }, + Object { + "status": Object { + "terms": Object { + "field": "monitor.status", + }, + }, + }, + Object { + "location": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + }, + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "monitor.status": "down", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-15m", + "lte": "now", + }, + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "tags": "org:google", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.type": "http", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.type": "tcp", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + "size": 0, + }, + "index": "heartbeat-8*", + } + `); + }); + + it('properly assigns filters for complex kuery filters object', async () => { + const [callES, esMock] = setupMockEsCompositeQuery( + [{ bucketCriteria: [] }], + genBucketItem + ); + const clientParameters = { + timerange: { + from: 'now-15m', + to: 'now', + }, + numTimes: 5, + locations: [], + filters: { + bool: { + filter: { + exists: { + field: 'monitor.status', + }, + }, + }, + }, + }; + await getMonitorStatus({ + callES, + dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, + ...clientParameters, + }); + expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); + const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + expect(params).toMatchInlineSnapshot(` + Object { + "body": Object { + "aggs": Object { + "monitors": Object { + "aggs": Object { + "fields": Object { + "top_hits": Object { + "size": 1, + }, + }, + }, + "composite": Object { + "size": 2000, + "sources": Array [ + Object { + "monitorId": Object { + "terms": Object { + "field": "monitor.id", + }, + }, + }, + Object { + "status": Object { + "terms": Object { + "field": "monitor.status", + }, + }, + }, + Object { + "location": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + }, + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "monitor.status": "down", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-15m", + "lte": "now", + }, + }, + }, + Object { + "bool": Object { + "filter": Object { + "exists": Object { + "field": "monitor.status", + }, + }, + }, + }, + ], + }, + }, + "size": 0, + }, + "index": "heartbeat-8*", + } + `); + }); + it('fetches single page of results', async () => { const [callES, esMock] = setupMockEsCompositeQuery( [ diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts index 0801fc5769363..f78048100b998 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_availability.ts @@ -47,6 +47,11 @@ export const getMonitorAvailability: UMElasticsearchQueryFn< const gte = `now-${range}${rangeUnit}`; + let parsedFilters: any; + if (filters) { + parsedFilters = JSON.parse(filters); + } + do { const esParams: any = { index: dynamicSettings.heartbeatIndices, @@ -62,6 +67,8 @@ export const getMonitorAvailability: UMElasticsearchQueryFn< }, }, }, + // append user filters, if defined + ...(parsedFilters?.bool ? [parsedFilters] : []), ], }, }, @@ -139,11 +146,6 @@ export const getMonitorAvailability: UMElasticsearchQueryFn< }, }; - if (filters) { - const parsedFilter = JSON.parse(filters); - esParams.body.query.bool = { ...esParams.body.query.bool, ...parsedFilter.bool }; - } - if (afterKey) { esParams.body.aggs.monitors.composite.after = afterKey; } diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts index beed8e8335314..caf505610e991 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts @@ -71,6 +71,8 @@ export const getMonitorStatus: UMElasticsearchQueryFn< }, }, }, + // append user filters, if defined + ...(filters?.bool ? [filters] : []), ], }, }, @@ -116,10 +118,6 @@ export const getMonitorStatus: UMElasticsearchQueryFn< }, }; - if (filters?.bool) { - esParams.body.query.bool = Object.assign({}, esParams.body.query.bool, filters.bool); - } - /** * Perform a logical `and` against the selected location filters. */ diff --git a/x-pack/plugins/watcher/public/application/lib/api.ts b/x-pack/plugins/watcher/public/application/lib/api.ts index 82ec2925ba6dc..4f42b26d51c46 100644 --- a/x-pack/plugins/watcher/public/application/lib/api.ts +++ b/x-pack/plugins/watcher/public/application/lib/api.ts @@ -35,44 +35,53 @@ export const getSavedObjectsClient = () => savedObjectsClient; const basePath = ROUTES.API_ROOT; +const loadWatchesDeserializer = ({ watches = [] }: { watches: any[] }) => { + return watches.map((watch: any) => Watch.fromUpstreamJson(watch)); +}; + export const useLoadWatches = (pollIntervalMs: number) => { return useRequest({ path: `${basePath}/watches`, method: 'get', pollIntervalMs, - deserializer: ({ watches = [] }: { watches: any[] }) => { - return watches.map((watch: any) => Watch.fromUpstreamJson(watch)); - }, + deserializer: loadWatchesDeserializer, }); }; +const loadWatchDetailDeserializer = ({ watch = {} }: { watch: any }) => + Watch.fromUpstreamJson(watch); + export const useLoadWatchDetail = (id: string) => { return useRequest({ path: `${basePath}/watch/${id}`, method: 'get', - deserializer: ({ watch = {} }: { watch: any }) => Watch.fromUpstreamJson(watch), + deserializer: loadWatchDetailDeserializer, }); }; +const loadWatchHistoryDeserializer = ({ watchHistoryItems = [] }: { watchHistoryItems: any }) => { + return watchHistoryItems.map((historyItem: any) => + WatchHistoryItem.fromUpstreamJson(historyItem) + ); +}; + export const useLoadWatchHistory = (id: string, startTime: string) => { return useRequest({ query: startTime ? { startTime } : undefined, path: `${basePath}/watch/${id}/history`, method: 'get', - deserializer: ({ watchHistoryItems = [] }: { watchHistoryItems: any }) => { - return watchHistoryItems.map((historyItem: any) => - WatchHistoryItem.fromUpstreamJson(historyItem) - ); - }, + deserializer: loadWatchHistoryDeserializer, }); }; +const loadWatchHistoryDetailDeserializer = ({ watchHistoryItem }: { watchHistoryItem: any }) => + WatchHistoryItem.fromUpstreamJson(watchHistoryItem); + export const useLoadWatchHistoryDetail = (id: string | undefined) => { return useRequest({ path: !id ? '' : `${basePath}/history/${id}`, method: 'get', - deserializer: ({ watchHistoryItem }: { watchHistoryItem: any }) => - WatchHistoryItem.fromUpstreamJson(watchHistoryItem), + deserializer: loadWatchHistoryDetailDeserializer, }); }; @@ -148,6 +157,8 @@ export const loadIndexPatterns = async () => { return savedObjects; }; +const getWatchVisualizationDataDeserializer = (data: { visualizeData: any }) => data?.visualizeData; + export const useGetWatchVisualizationData = (watchModel: BaseWatch, visualizeOptions: any) => { return useRequest({ path: `${basePath}/watch/visualize`, @@ -156,21 +167,23 @@ export const useGetWatchVisualizationData = (watchModel: BaseWatch, visualizeOpt watch: watchModel.upstreamJson, options: visualizeOptions.upstreamJson, }), - deserializer: (data: { visualizeData: any }) => data?.visualizeData, + deserializer: getWatchVisualizationDataDeserializer, }); }; +const loadSettingsDeserializer = (data: { + action_types: { + [key: string]: { + enabled: boolean; + }; + }; +}) => Settings.fromUpstreamJson(data); + export const useLoadSettings = () => { return useRequest({ path: `${basePath}/settings`, method: 'get', - deserializer: (data: { - action_types: { - [key: string]: { - enabled: boolean; - }; - }; - }) => Settings.fromUpstreamJson(data), + deserializer: loadSettingsDeserializer, }); }; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx index c0d906114277e..2ff0f53d07e91 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useContext, useEffect } from 'react'; +import React, { Fragment, useContext, useEffect, useMemo } from 'react'; import { AnnotationDomainTypes, Axis, @@ -105,7 +105,9 @@ export const WatchVisualization = () => { threshold, } = watch; - const domain = getDomain(watch); + // Only recalculate the domain if the watch configuration changes. This prevents the visualization + // request's resolution from re-triggering itself in an infinite loop. + const domain = useMemo(() => getDomain(watch), [watch]); const timeBuckets = createTimeBuckets(); timeBuckets.setBounds(domain); const interval = timeBuckets.getInterval().expression; diff --git a/x-pack/plugins/watcher/public/application/shared_imports.ts b/x-pack/plugins/watcher/public/application/shared_imports.ts index a9e07b80a9b22..766e8e659c8ae 100644 --- a/x-pack/plugins/watcher/public/application/shared_imports.ts +++ b/x-pack/plugins/watcher/public/application/shared_imports.ts @@ -10,6 +10,6 @@ export { UseRequestConfig, sendRequest, useRequest, -} from '../../../../../src/plugins/es_ui_shared/public/'; +} from '../../../../../src/plugins/es_ui_shared/public'; export { useXJsonMode } from '../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks'; diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 421e709444d0f..6271c4b601307 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -56,7 +56,6 @@ const onlyNotInCoverageTests = [ require.resolve('../test/upgrade_assistant_integration/config.js'), require.resolve('../test/licensing_plugin/config.ts'), require.resolve('../test/licensing_plugin/config.public.ts'), - require.resolve('../test/licensing_plugin/config.legacy.ts'), require.resolve('../test/endpoint_api_integration_no_ingest/config.ts'), require.resolve('../test/reporting_api_integration/reporting_and_security.config.ts'), require.resolve('../test/reporting_api_integration/reporting_without_security.config.ts'), diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 67dd8c877e378..f9fdfaed1c79b 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -63,7 +63,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) const actionsProxyUrl = options.enableActionsProxy ? [ `--xpack.actions.proxyUrl=http://localhost:${proxyPort}`, - '--xpack.actions.rejectUnauthorizedCertificates=false', + '--xpack.actions.proxyRejectUnauthorizedCertificates=false', ] : []; diff --git a/x-pack/test/api_integration/apis/maps/get_tile.js b/x-pack/test/api_integration/apis/maps/get_tile.js new file mode 100644 index 0000000000000..7219fc858e059 --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/get_tile.js @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export default function ({ getService }) { + const supertest = getService('supertest'); + + describe('getTile', () => { + it('should validate params', async () => { + await supertest + .get( + `/api/maps/mvt/getTile?x=15&y=11&z=5&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(includes:!(coordinates)),docvalue_fields:!(),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(coordinates))` + ) + .set('kbn-xsrf', 'kibana') + .expect(200); + }); + + it('should not validate when required params are missing', async () => { + await supertest + .get( + `/api/maps/mvt/getTile?&index=logstash*&requestBody=(_source:(includes:!(coordinates)),docvalue_fields:!(),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(coordinates))` + ) + .set('kbn-xsrf', 'kibana') + .expect(400); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index f9dff19229645..6c213380dd64e 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -16,6 +16,7 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./fonts_api')); loadTestFile(require.resolve('./index_settings')); loadTestFile(require.resolve('./migrations')); + loadTestFile(require.resolve('./get_tile')); }); }); } diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts index f4964308cd8c9..7aa5c180c5a02 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts @@ -258,7 +258,7 @@ export default ({ getService }: FtrProviderContext) => { description: 'Not found', }; const id = `${jobId}_invalid`; - const message = `[resource_not_found_exception] No known data frame analytics with id [${id}]`; + const message = 'resource_not_found_exception'; const { body } = await supertest .post(`/api/ml/data_frame/analytics/${id}/_update`) diff --git a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts index 299f5f93fd281..a5969bdffbf8a 100644 --- a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts +++ b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts @@ -60,8 +60,7 @@ export default ({ getService }: FtrProviderContext) => { responseBody: { statusCode: 404, error: 'Not Found', - message: - '[index_not_found_exception] no such index [ft_farequote_not_exists], with { resource.type="index_or_alias" & resource.id="ft_farequote_not_exists" & index_uuid="_na_" & index="ft_farequote_not_exists" }', + message: 'index_not_found_exception', }, }, }; diff --git a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts index 5795eac9637b1..4ec8ae0429a6b 100644 --- a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts +++ b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts @@ -152,8 +152,7 @@ export default ({ getService }: FtrProviderContext) => { responseBody: { statusCode: 404, error: 'Not Found', - message: - '[index_not_found_exception] no such index [ft_farequote_not_exists], with { resource.type="index_or_alias" & resource.id="ft_farequote_not_exists" & index_uuid="_na_" & index="ft_farequote_not_exists" }', + message: 'index_not_found_exception', }, }, }; diff --git a/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts b/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts index fa83807be161a..af2c53c577253 100644 --- a/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts +++ b/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts @@ -116,8 +116,7 @@ export default ({ getService }: FtrProviderContext) => { responseBody: { statusCode: 404, error: 'Not Found', - message: - '[index_not_found_exception] no such index [ft_farequote_not_exist], with { resource.type="index_or_alias" & resource.id="ft_farequote_not_exist" & index_uuid="_na_" & index="ft_farequote_not_exist" }', + message: 'index_not_found_exception', }, }, }, diff --git a/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts b/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts index 627d9454beeb6..7dbb4a9d03e4f 100644 --- a/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts +++ b/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts @@ -78,8 +78,7 @@ export default ({ getService }: FtrProviderContext) => { responseBody: { statusCode: 404, error: 'Not Found', - message: - '[index_not_found_exception] no such index [ft_ecommerce_not_exist], with { resource.type="index_or_alias" & resource.id="ft_ecommerce_not_exist" & index_uuid="_na_" & index="ft_ecommerce_not_exist" }', + message: 'index_not_found_exception', }, }, }, diff --git a/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts b/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts index b1c086ddbb456..6588d4df570b7 100644 --- a/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts +++ b/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts @@ -81,8 +81,7 @@ export default ({ getService }: FtrProviderContext) => { responseBody: { statusCode: 404, error: 'Not Found', - message: - '[index_not_found_exception] no such index [ft_ecommerce_not_exist], with { resource.type="index_or_alias" & resource.id="ft_ecommerce_not_exist" & index_uuid="_na_" & index="ft_ecommerce_not_exist" }', + message: 'index_not_found_exception', }, }, }, diff --git a/x-pack/test/api_integration/apis/ml/filters/create_filters.ts b/x-pack/test/api_integration/apis/ml/filters/create_filters.ts index dfec7798ffc0c..598fab2cb7fa6 100644 --- a/x-pack/test/api_integration/apis/ml/filters/create_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/create_filters.ts @@ -73,7 +73,7 @@ export default ({ getService }: FtrProviderContext) => { responseCode: 400, responseBody: { error: 'Bad Request', - message: 'Invalid filter_id', + message: 'status_exception', }, }, }, diff --git a/x-pack/test/api_integration/apis/ml/filters/get_filters.ts b/x-pack/test/api_integration/apis/ml/filters/get_filters.ts index 5d7900ea5e9d9..4a3589634ded9 100644 --- a/x-pack/test/api_integration/apis/ml/filters/get_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/get_filters.ts @@ -91,7 +91,7 @@ export default ({ getService }: FtrProviderContext) => { .set(COMMON_REQUEST_HEADERS) .expect(400); expect(body.error).to.eql('Bad Request'); - expect(body.message).to.contain('Unable to find filter'); + expect(body.message).to.contain('resource_not_found_exception'); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/filters/update_filters.ts b/x-pack/test/api_integration/apis/ml/filters/update_filters.ts index fbbb94d54c035..6f421ad120b51 100644 --- a/x-pack/test/api_integration/apis/ml/filters/update_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/update_filters.ts @@ -111,7 +111,7 @@ export default ({ getService }: FtrProviderContext) => { .send(updateFilterRequestBody) .expect(400); - expect(body.message).to.contain('No filter with id'); + expect(body.message).to.contain('resource_not_found_exception'); }); }); }; diff --git a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts index 5b9c5393e81d9..0b7f9cf927d26 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts @@ -47,8 +47,8 @@ export default ({ getService }: FtrProviderContext) => { responseCode: 200, responseBody: { - [SINGLE_METRIC_JOB_CONFIG.job_id]: { closed: false, error: { statusCode: 409 } }, - [MULTI_METRIC_JOB_CONFIG.job_id]: { closed: false, error: { statusCode: 409 } }, + [SINGLE_METRIC_JOB_CONFIG.job_id]: { closed: false, error: { status: 409 } }, + [MULTI_METRIC_JOB_CONFIG.job_id]: { closed: false, error: { status: 409 } }, }, }, }, @@ -162,9 +162,7 @@ export default ({ getService }: FtrProviderContext) => { expectedRspJobIds.forEach((id) => { expect(body[id].closed).to.eql(testData.expected.responseBody[id].closed); - expect(body[id].error.statusCode).to.eql( - testData.expected.responseBody[id].error.statusCode - ); + expect(body[id].error.status).to.eql(testData.expected.responseBody[id].error.status); }); // ensure jobs are still open diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts index 5b61112a374c1..0b94abaa15890 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import expectedBreakdown from './expectation/breakdown.json'; -import expectedBreakdownWithTransactionName from './expectation/breakdown_transaction_name.json'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -25,7 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { `/api/apm/services/opbeans-node/transaction_groups/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` ); expect(response.status).to.be(200); - expect(response.body).to.eql({ kpis: [], timeseries: [] }); + expect(response.body).to.eql({ timeseries: [] }); }); }); @@ -47,15 +46,32 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expect(response.status).to.be(200); - expect(response.body).to.eql(expectedBreakdownWithTransactionName); + const { timeseries } = response.body; + const { title, color, type, data, hideLegend, legendValue } = timeseries[0]; + expect(data).to.eql([ + { x: 1593413100000, y: null }, + { x: 1593413130000, y: null }, + { x: 1593413160000, y: null }, + { x: 1593413190000, y: null }, + { x: 1593413220000, y: null }, + { x: 1593413250000, y: null }, + { x: 1593413280000, y: null }, + { x: 1593413310000, y: 1 }, + { x: 1593413340000, y: null }, + ]); + expect(title).to.be('app'); + expect(color).to.be('#54b399'); + expect(type).to.be('areaStacked'); + expect(hideLegend).to.be(false); + expect(legendValue).to.be('100%'); }); - it('returns the top 4 by percentage and sorts them by name', async () => { + it('returns the transaction breakdown sorted by name', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transaction_groups/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` ); expect(response.status).to.be(200); - expect(response.body.kpis.map((kpi: { name: string }) => kpi.name)).to.eql([ + expect(response.body.timeseries.map((serie: { title: string }) => serie.title)).to.eql([ 'app', 'http', 'postgresql', diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/breakdown.json b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/breakdown.json index 3b884a9eb7907..8ffbba64ec7ab 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/breakdown.json +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/breakdown.json @@ -1,26 +1,4 @@ { - "kpis":[ - { - "name":"app", - "percentage":0.16700861715223636, - "color":"#54b399" - }, - { - "name":"http", - "percentage":0.7702092736971686, - "color":"#6092c0" - }, - { - "name":"postgresql", - "percentage":0.0508822322527698, - "color":"#d36086" - }, - { - "name":"redis", - "percentage":0.011899876897825195, - "color":"#9170b8" - } - ], "timeseries":[ { "title":"app", @@ -64,7 +42,8 @@ "y":null } ], - "hideLegend":true + "hideLegend":false, + "legendValue": "17%" }, { "title":"http", @@ -108,7 +87,8 @@ "y":null } ], - "hideLegend":true + "hideLegend":false, + "legendValue": "77%" }, { "title":"postgresql", @@ -152,7 +132,8 @@ "y":null } ], - "hideLegend":true + "hideLegend":false, + "legendValue": "5.1%" }, { "title":"redis", @@ -196,7 +177,8 @@ "y":null } ], - "hideLegend":true + "hideLegend":false, + "legendValue": "1.2%" } ] } \ No newline at end of file diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/breakdown_transaction_name.json b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/breakdown_transaction_name.json deleted file mode 100644 index b4f8e376d3609..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/expectation/breakdown_transaction_name.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "kpis":[ - { - "name":"app", - "percentage":1, - "color":"#54b399" - } - ], - "timeseries":[ - { - "title":"app", - "color":"#54b399", - "type":"areaStacked", - "data":[ - { - "x":1593413100000, - "y":null - }, - { - "x":1593413130000, - "y":null - }, - { - "x":1593413160000, - "y":null - }, - { - "x":1593413190000, - "y":null - }, - { - "x":1593413220000, - "y":null - }, - { - "x":1593413250000, - "y":null - }, - { - "x":1593413280000, - "y":null - }, - { - "x":1593413310000, - "y":1 - }, - { - "x":1593413340000, - "y":null - } - ], - "hideLegend":true - } - ] -} \ No newline at end of file diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts index a022b7c79c079..c682c1f1f4640 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts @@ -49,7 +49,7 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllTimelines(es); }); - it('should contain two output keys of rules_installed and rules_updated', async () => { + it('should contain rules_installed, rules_updated, timelines_installed, and timelines_updated', async () => { const { body } = await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) .set('kbn-xsrf', 'true') @@ -74,6 +74,16 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.rules_installed).to.be.greaterThan(0); }); + it('should create the prepackaged timelines and return a count greater than zero', async () => { + const { body } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.be.greaterThan(0); + }); + it('should create the prepackaged rules that the rules_updated is of size zero', async () => { const { body } = await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) @@ -84,6 +94,16 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.rules_updated).to.eql(0); }); + it('should create the prepackaged timelines and the timelines_updated is of size zero', async () => { + const { body } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_updated).to.eql(0); + }); + it('should be possible to call the API twice and the second time the number of rules installed should be zero', async () => { await supertest .put(DETECTION_ENGINE_PREPACKAGED_URL) @@ -109,6 +129,30 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.rules_installed).to.eql(0); }); + + it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { + await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + await waitFor(async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.timelines_not_installed === 0; + }); + + const { body } = await supertest + .put(DETECTION_ENGINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.eql(0); + }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts b/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts new file mode 100644 index 0000000000000..209c7974a7c2a --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/basic/tests/import_timelines.ts @@ -0,0 +1,403 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { TIMELINE_IMPORT_URL } from '../../../../plugins/security_solution/common/constants'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteAllTimelines } from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('import timelines', () => { + describe('creating a timeline', () => { + const getTimeline = () => { + return Buffer.from( + JSON.stringify({ + savedObjectId: '67664480-d191-11ea-ae67-4f4be8c1847b', + version: 'WzU1NSwxXQ==', + columns: [ + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: '@timestamp', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'message', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.category', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.action', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'host.name', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'source.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'destination.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'user.name', + searchable: null, + }, + ], + dataProviders: [], + description: '', + eventType: 'all', + filters: [], + kqlMode: 'filter', + timelineType: 'default', + kqlQuery: { + filterQuery: { + serializedQuery: + '{"bool":{"should":[{"exists":{"field":"@timestamp"}}],"minimum_should_match":1}}', + kuery: { + expression: '@timestamp : * ', + kind: 'kuery', + }, + }, + }, + title: 'x2', + sort: { + columnId: '@timestamp', + sortDirection: 'desc', + }, + created: 1596036895488, + createdBy: 'angela', + updated: 1596491470411, + updatedBy: 'elastic', + templateTimelineId: null, + templateTimelineVersion: null, + dateRange: { + start: '2020-04-10T14:10:58.373Z', + end: '2020-05-30T14:16:58.373Z', + }, + savedQueryId: null, + eventNotes: [ + { + noteId: '7d875ba0-d5d3-11ea-9899-ebec3d084fe0', + version: 'WzU1NiwxXQ==', + eventId: '8KtMKnIBOS_moQ_K9fAe', + note: 'hi Xavier', + timelineId: '67664480-d191-11ea-ae67-4f4be8c1847b', + created: 1596491490806, + createdBy: 'elastic', + updated: 1596491490806, + updatedBy: 'elastic', + }, + ], + globalNotes: [], + pinnedEventIds: [ + 'K99zy3EBDTDlbwBfpf6x', + 'GKpFKnIBOS_moQ_Ke5AO', + '8KtMKnIBOS_moQ_K9fAe', + ], + }) + ); + }; + beforeEach(async () => {}); + + afterEach(async () => { + await deleteAllTimelines(es); + }); + + it("if it doesn't exists", async () => { + const { body } = await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimeline(), 'timelines.ndjson') + .expect(200); + expect(body).to.eql({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 1, + timelines_updated: 0, + }); + }); + }); + + describe('creating a timeline template', () => { + const getTimelineTemplate = () => { + return Buffer.from( + JSON.stringify({ + savedObjectId: 'cab434d0-d26c-11ea-b887-3b103296472a', + version: 'WzQ0NSwxXQ==', + columns: [ + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: '@timestamp', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'message', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.category', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.action', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'host.name', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'source.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'destination.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'user.name', + searchable: null, + }, + ], + dataProviders: [], + description: 'desc', + eventType: 'all', + filters: [], + kqlMode: 'filter', + timelineType: 'template', + kqlQuery: { filterQuery: null }, + title: 'my t template', + sort: { columnId: '@timestamp', sortDirection: 'desc' }, + templateTimelineId: '46a50505-0a48-49cb-9ab2-d15d683efa3b', + templateTimelineVersion: 1, + created: 1596473742379, + createdBy: 'elastic', + updated: 1596473909169, + updatedBy: 'elastic', + dateRange: { start: '2020-08-02T16:55:22.160Z', end: '2020-08-03T16:55:22.161Z' }, + savedQueryId: null, + eventNotes: [], + globalNotes: [ + { + noteId: '358f45c0-d5aa-11ea-9b6d-53d136d390dc', + version: 'WzQzOCwxXQ==', + note: 'I have a global note', + timelineId: 'cab434d0-d26c-11ea-b887-3b103296472a', + created: 1596473760688, + createdBy: 'elastic', + updated: 1596473760688, + updatedBy: 'elastic', + }, + ], + pinnedEventIds: [], + }) + ); + }; + + afterEach(async () => { + await deleteAllTimelines(es); + }); + + it("if it doesn't exists", async () => { + const { body } = await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimelineTemplate(), 'timelines.ndjson') + .expect(200); + expect(body).to.eql({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 1, + timelines_updated: 0, + }); + }); + }); + + describe('Updating a timeline template', () => { + const getTimelineTemplate = (templateTimelineVersion: number) => { + return Buffer.from( + JSON.stringify({ + savedObjectId: 'cab434d0-d26c-11ea-b887-3b103296472a', + version: 'WzQ0NSwxXQ==', + columns: [ + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: '@timestamp', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'message', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.category', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'event.action', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'host.name', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'source.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'destination.ip', + searchable: null, + }, + { + indexes: null, + name: null, + columnHeaderType: 'not-filtered', + id: 'user.name', + searchable: null, + }, + ], + dataProviders: [], + description: 'desc', + eventType: 'all', + filters: [], + kqlMode: 'filter', + timelineType: 'template', + kqlQuery: { filterQuery: null }, + title: 'my t template', + sort: { columnId: '@timestamp', sortDirection: 'desc' }, + templateTimelineId: '46a50505-0a48-49cb-9ab2-d15d683efa3b', + templateTimelineVersion, + created: 1596473742379, + createdBy: 'elastic', + updated: 1596473909169, + updatedBy: 'elastic', + dateRange: { start: '2020-08-02T16:55:22.160Z', end: '2020-08-03T16:55:22.161Z' }, + savedQueryId: null, + eventNotes: [], + globalNotes: [ + { + noteId: '358f45c0-d5aa-11ea-9b6d-53d136d390dc', + version: 'WzQzOCwxXQ==', + note: 'I have a global note', + timelineId: 'cab434d0-d26c-11ea-b887-3b103296472a', + created: 1596473760688, + createdBy: 'elastic', + updated: 1596473760688, + updatedBy: 'elastic', + }, + ], + pinnedEventIds: [], + }) + ); + }; + + afterEach(async () => { + await deleteAllTimelines(es); + }); + + it("if it doesn't exists", async () => { + await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimelineTemplate(1), 'timelines.ndjson') + .expect(200); + const { body } = await supertest + .post(`${TIMELINE_IMPORT_URL}`) + .set('kbn-xsrf', 'true') + .attach('file', getTimelineTemplate(2), 'timelines.ndjson') + .expect(200); + expect(body).to.eql({ + errors: [], + success: true, + success_count: 1, + timelines_installed: 0, + timelines_updated: 1, + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts index a480e63ff4a92..09feb7ac5e0ec 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts @@ -28,5 +28,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./patch_rules')); loadTestFile(require.resolve('./query_signals')); loadTestFile(require.resolve('./open_close_signals')); + loadTestFile(require.resolve('./import_timelines')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts new file mode 100644 index 0000000000000..556217877968b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/basic/tests/install_prepackaged_timelines.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { TIMELINE_PREPACKAGED_URL } from '../../../../plugins/security_solution/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createSignalsIndex, + deleteAllAlerts, + deleteAllTimelines, + deleteSignalsIndex, + waitFor, +} from '../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('install_prepackaged_timelines', () => { + describe('creating prepackaged rules', () => { + beforeEach(async () => { + await createSignalsIndex(supertest); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(es); + await deleteAllTimelines(es); + }); + + it('should contain timelines_installed, and timelines_updated', async () => { + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(Object.keys(body)).to.eql(['timelines_installed', 'timelines_updated']); + }); + + it('should create the prepackaged timelines and return a count greater than zero', async () => { + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.be.greaterThan(0); + }); + + it('should create the prepackaged timelines and the timelines_updated is of size zero', async () => { + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_updated).to.eql(0); + }); + + it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => { + await supertest.put(TIMELINE_PREPACKAGED_URL).set('kbn-xsrf', 'true').send().expect(200); + + await waitFor(async () => { + const { body } = await supertest + .get(`${TIMELINE_PREPACKAGED_URL}/_status`) + .set('kbn-xsrf', 'true') + .expect(200); + return body.timelines_not_installed === 0; + }); + + const { body } = await supertest + .put(TIMELINE_PREPACKAGED_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.timelines_installed).to.eql(0); + }); + }); + }); +}; diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 04f289b69bb71..82aba0503fb9d 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -21,7 +21,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'infraHome']); const supertest = getService('supertest'); - describe('Home page', function () { + // FLAKY: https://github.com/elastic/kibana/issues/75724 + describe.skip('Home page', function () { this.tags('includeFirefox'); before(async () => { await esArchiver.load('empty_kibana'); diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js index 4bbe38367d0a2..ef8b4ad4c0f19 100644 --- a/x-pack/test/functional/apps/maps/index.js +++ b/x-pack/test/functional/apps/maps/index.js @@ -46,6 +46,7 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./es_geo_grid_source')); loadTestFile(require.resolve('./es_pew_pew_source')); loadTestFile(require.resolve('./joins')); + loadTestFile(require.resolve('./mvt_scaling')); loadTestFile(require.resolve('./add_layer_panel')); loadTestFile(require.resolve('./import_geojson')); loadTestFile(require.resolve('./layer_errors')); diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index e447996a08dfe..1139ae204aefd 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { set } from '@elastic/safer-lodash-set'; import { MAPBOX_STYLES } from './mapbox_styles'; @@ -21,6 +20,7 @@ const VECTOR_SOURCE_ID = 'n1t6f'; const CIRCLE_STYLE_LAYER_INDEX = 0; const FILL_STYLE_LAYER_INDEX = 2; const LINE_STYLE_LAYER_INDEX = 3; +const TOO_MANY_FEATURES_LAYER_INDEX = 4; export default function ({ getPageObjects, getService }) { const PageObjects = getPageObjects(['maps']); @@ -87,28 +87,32 @@ export default function ({ getPageObjects, getService }) { }); }); - it('should style fills, points and lines independently', async () => { + it('should style fills, points, lines, and bounding-boxes independently', async () => { const mapboxStyle = await PageObjects.maps.getMapboxStyle(); const layersForVectorSource = mapboxStyle.layers.filter((mbLayer) => { return mbLayer.id.startsWith(VECTOR_SOURCE_ID); }); - // Color is dynamically obtained from eui source lib - const dynamicColor = - layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX].paint['circle-stroke-color']; - //circle layer for points - expect(layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX]).to.eql( - set(MAPBOX_STYLES.POINT_LAYER, 'paint.circle-stroke-color', dynamicColor) - ); + expect(layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.POINT_LAYER); //fill layer expect(layersForVectorSource[FILL_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.FILL_LAYER); //line layer for borders - expect(layersForVectorSource[LINE_STYLE_LAYER_INDEX]).to.eql( - set(MAPBOX_STYLES.LINE_LAYER, 'paint.line-color', dynamicColor) - ); + expect(layersForVectorSource[LINE_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.LINE_LAYER); + + //Too many features layer (this is a static style config) + expect(layersForVectorSource[TOO_MANY_FEATURES_LAYER_INDEX]).to.eql({ + id: 'n1t6f_toomanyfeatures', + type: 'fill', + source: 'n1t6f', + minzoom: 0, + maxzoom: 24, + filter: ['==', ['get', '__kbn_too_many_features__'], true], + layout: { visibility: 'visible' }, + paint: { 'fill-pattern': '__kbn_too_many_features_image_id__', 'fill-opacity': 0.75 }, + }); }); it('should flag only the joined features as visible', async () => { diff --git a/x-pack/test/functional/apps/maps/mapbox_styles.js b/x-pack/test/functional/apps/maps/mapbox_styles.js index 744eb4ac74bf6..78720fa1689ec 100644 --- a/x-pack/test/functional/apps/maps/mapbox_styles.js +++ b/x-pack/test/functional/apps/maps/mapbox_styles.js @@ -14,7 +14,11 @@ export const MAPBOX_STYLES = { filter: [ 'all', ['==', ['get', '__kbn_isvisibleduetojoin__'], true], - ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']], + [ + 'all', + ['!=', ['get', '__kbn_too_many_features__'], true], + ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']], + ], ], layout: { visibility: 'visible' }, paint: { @@ -84,7 +88,11 @@ export const MAPBOX_STYLES = { filter: [ 'all', ['==', ['get', '__kbn_isvisibleduetojoin__'], true], - ['any', ['==', ['geometry-type'], 'Polygon'], ['==', ['geometry-type'], 'MultiPolygon']], + [ + 'all', + ['!=', ['get', '__kbn_too_many_features__'], true], + ['any', ['==', ['geometry-type'], 'Polygon'], ['==', ['geometry-type'], 'MultiPolygon']], + ], ], layout: { visibility: 'visible' }, paint: { @@ -151,20 +159,18 @@ export const MAPBOX_STYLES = { 'all', ['==', ['get', '__kbn_isvisibleduetojoin__'], true], [ - 'any', - ['==', ['geometry-type'], 'Polygon'], - ['==', ['geometry-type'], 'MultiPolygon'], - ['==', ['geometry-type'], 'LineString'], - ['==', ['geometry-type'], 'MultiLineString'], + 'all', + ['!=', ['get', '__kbn_too_many_features__'], true], + [ + 'any', + ['==', ['geometry-type'], 'Polygon'], + ['==', ['geometry-type'], 'MultiPolygon'], + ['==', ['geometry-type'], 'LineString'], + ['==', ['geometry-type'], 'MultiLineString'], + ], ], ], - layout: { - visibility: 'visible', - }, - paint: { - /* 'line-color': '' */ // Obtained dynamically - 'line-opacity': 0.75, - 'line-width': 1, - }, + layout: { visibility: 'visible' }, + paint: { 'line-color': '#41937c', 'line-opacity': 0.75, 'line-width': 1 }, }, }; diff --git a/x-pack/test/functional/apps/maps/mvt_scaling.js b/x-pack/test/functional/apps/maps/mvt_scaling.js new file mode 100644 index 0000000000000..e50b72658fb43 --- /dev/null +++ b/x-pack/test/functional/apps/maps/mvt_scaling.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +const VECTOR_SOURCE_ID = 'caffa63a-ebfb-466d-8ff6-d797975b88ab'; + +export default function ({ getPageObjects, getService }) { + const PageObjects = getPageObjects(['maps']); + const inspector = getService('inspector'); + + describe('mvt geoshape layer', () => { + before(async () => { + await PageObjects.maps.loadSavedMap('geo_shape_mvt'); + }); + + after(async () => { + await inspector.close(); + }); + + it('should render with mvt-source', async () => { + const mapboxStyle = await PageObjects.maps.getMapboxStyle(); + + //Source should be correct + expect(mapboxStyle.sources[VECTOR_SOURCE_ID].tiles[0]).to.equal( + '/api/maps/mvt/getTile?x={x}&y={y}&z={z}&geometryFieldName=geometry&index=geo_shapes*&requestBody=(_source:(includes:!(geometry,prop1)),docvalue_fields:!(prop1),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(geometry,prop1))' + ); + + //Should correctly load meta for style-rule (sigma is set to 1, opacity to 1) + const fillLayer = mapboxStyle.layers.find((layer) => layer.id === VECTOR_SOURCE_ID + '_fill'); + expect(fillLayer.paint).to.eql({ + 'fill-color': [ + 'interpolate', + ['linear'], + [ + 'coalesce', + [ + 'case', + ['==', ['get', 'prop1'], null], + 0.3819660112501051, + [ + 'max', + ['min', ['to-number', ['get', 'prop1']], 3.618033988749895], + 1.381966011250105, + ], + ], + 0.3819660112501051, + ], + 0.3819660112501051, + 'rgba(0,0,0,0)', + 1.381966011250105, + '#ecf1f7', + 1.6614745084375788, + '#d9e3ef', + 1.9409830056250525, + '#c5d5e7', + 2.2204915028125263, + '#b2c7df', + 2.5, + '#9eb9d8', + 2.7795084971874737, + '#8bacd0', + 3.0590169943749475, + '#769fc8', + 3.338525491562421, + '#6092c0', + ], + 'fill-opacity': 1, + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js index fefad214736e6..70f7e0559d034 100644 --- a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js @@ -10,6 +10,7 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['monitoring', 'common', 'header']); const esSupertest = getService('esSupertest'); const noData = getService('monitoringNoData'); + const testSubjects = getService('testSubjects'); const clusterOverview = getService('monitoringClusterOverview'); const retry = getService('retry'); @@ -46,8 +47,10 @@ export default function ({ getService, getPageObjects }) { }); // Here we are checking that once Monitoring is enabled, - //it moves on to the cluster overview page. - await retry.try(async () => { + // it moves on to the cluster overview page. + await retry.tryForTime(10000, async () => { + // Click the refresh button + await testSubjects.click('querySubmitButton'); expect(await clusterOverview.isOnClusterOverview()).to.be(true); }); }); diff --git a/x-pack/test/functional/apps/monitoring/time_filter.js b/x-pack/test/functional/apps/monitoring/time_filter.js index 11557d995218e..127c7d8889bc4 100644 --- a/x-pack/test/functional/apps/monitoring/time_filter.js +++ b/x-pack/test/functional/apps/monitoring/time_filter.js @@ -7,8 +7,6 @@ import expect from '@kbn/expect'; import { getLifecycleMethods } from './_get_lifecycle_methods'; -const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['header', 'timePicker']); const testSubjects = getService('testSubjects'); @@ -36,12 +34,9 @@ export default function ({ getService, getPageObjects }) { expect(isLoading).to.be(true); }); - it('should send another request when changing the time picker', async () => { - /** - * TODO: The value should either be removed or lowered after: - * https://github.com/elastic/kibana/issues/72997 is resolved - */ - await delay(3000); + // TODO: [cr] I'm not sure this test is any better than the above one, we might need to rely solely on unit tests + // for this functionality + it.skip('should send another request when changing the time picker', async () => { await PageObjects.timePicker.setAbsoluteRange( 'Aug 15, 2016 @ 21:00:00.000', 'Aug 16, 2016 @ 00:00:00.000' diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index 0f1fd3c09d706..f756d73484198 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -1008,6 +1008,40 @@ } } +{ + "type": "doc", + "value": { + "id": "map:bff99716-e3dc-11ea-87d0-0242ac130003", + "index": ".kibana", + "source": { + "map" : { + "description":"shapes with mvt scaling", + "layerListJSON":"[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true},\"id\":\"76b9fc1d-1e8a-4d2f-9f9e-6ba2b19f24bb\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\"},\"type\":\"VECTOR_TILE\"},{\"sourceDescriptor\":{\"geoField\":\"geometry\",\"filterByMapBounds\":true,\"scalingType\":\"MVT\",\"topHitsSize\":1,\"id\":\"97f8555e-8db0-4bd8-8b18-22e32f468667\",\"type\":\"ES_SEARCH\",\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"caffa63a-ebfb-466d-8ff6-d797975b88ab\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"prop1\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":1},\"type\":\"ORDINAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true},\"type\":\"TILED_VECTOR\",\"joins\":[]}]", + "mapStateJSON":"{\"zoom\":3.75,\"center\":{\"lon\":80.01106,\"lat\":3.65009},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "title":"geo_shape_mvt", + "uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + }, + "type" : "map", + "references" : [ + { + "id":"561253e0-f731-11e8-8487-11b9dd924f96", + "name":"layer_1_source_index_pattern", + "type":"index-pattern" + } + ], + "migrationVersion" : { + "map" : "7.9.0" + }, + "updated_at" : "2020-08-10T18:27:39.805Z" + } + } +} + + + + + + { "type": "doc", "value": { diff --git a/x-pack/test/functional/es_archives/monitoring/apm/mappings.json b/x-pack/test/functional/es_archives/monitoring/apm/mappings.json index d5f68e6642a2d..df00072edd669 100644 --- a/x-pack/test/functional/es_archives/monitoring/apm/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/apm/mappings.json @@ -1,719 +1,311 @@ { - "type": "index", - "value": { - "aliases": {}, - "index": ".monitoring-beats-6-2018.08.31", - "mappings": { - "dynamic": false, - "properties": { - "beats_state": { - "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" + "type": "index", + "value": { + "aliases": { + }, + "index": ".monitoring-beats-6-2018.08.31", + "mappings": { + "dynamic": false, + "properties": { + "beats_state": { + "properties": { + "timestamp": { + "format": "date_time", + "type": "date" + } + } + }, + "beats_stats": { + "properties": { + "beat": { + "properties": { + "type": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "metrics": { + "properties": { + "apm-server": { + "properties": { + "processor": { + "properties": { + "error": { + "properties": { + "transformations": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "transformations": { + "type": "long" + } + } + }, + "span": { + "properties": { + "transformations": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "transformations": { + "type": "long" + } + } + } + } + }, + "server": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + } + } + }, + "response": { + "properties": { + "count": { + "type": "long" + }, + "errors": { + "properties": { + "closed": { + "type": "long" }, - "name": { - "type": "keyword" + "concurrency": { + "type": "long" }, - "type": { - "type": "keyword" + "decode": { + "type": "long" }, - "uuid": { - "type": "keyword" + "forbidden": { + "type": "long" }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } + "internal": { + "type": "long" + }, + "method": { + "type": "long" }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } + "queue": { + "type": "long" }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } + "ratelimit": { + "type": "long" }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } + "toolarge": { + "type": "long" }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } + "unauthorized": { + "type": "long" }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } + "validate": { + "type": "long" } + } + }, + "valid": { + "properties": { + "accepted": { + "type": "long" + }, + "ok": { + "type": "long" + } + } } - }, - "timestamp": { - "format": "date_time", - "type": "date" + } } + } } + } }, - "beats_stats": { - "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } + "beat": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "value": { + "type": "long" } + } + } + } + }, + "handles": { + "properties": { + "open": { + "type": "long" + } + } + }, + "info": { + "properties": { + "ephemeral_id": { + "type": "keyword" + } + } + }, + "memstats": { + "properties": { + "gc_next": { + "type": "long" }, - "metrics": { - "properties": { - "beat": { - "properties": { - "cpu": { - "properties": { - "system": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "value": { - "type": "long" - }, - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "user": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - } - } - }, - "info": { - "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "uptime": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "memstats": { - "properties": { - "gc_next": { - "type": "long" - }, - "memory_alloc": { - "type": "long" - }, - "memory_total": { - "type": "long" - }, - "rss": { - "type": "long" - } - } - }, - "handles": { - "properties": { - "open": { - "type": "long" - }, - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - } - } - } - } - }, - "apm-server": { - "properties": { - "server": { - "properties": { - "request": { - "properties": { - "count": { - "type": "long" - } - } - }, - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "response": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "properties": { - "count": { - "type": "long" - }, - "toolarge": { - "type": "long" - }, - "validate": { - "type": "long" - }, - "ratelimit": { - "type": "long" - }, - "queue": { - "type": "long" - }, - "closed": { - "type": "long" - }, - "forbidden": { - "type": "long" - }, - "concurrency": { - "type": "long" - }, - "unauthorized": { - "type": "long" - }, - "internal": { - "type": "long" - }, - "decode": { - "type": "long" - }, - "method": { - "type": "long" - } - } - }, - "valid": { - "properties": { - "ok": { - "type": "long" - }, - "accepted": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - } - } - }, - "decoder": { - "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "reader": { - "properties": { - "size": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "missing-content-length": { - "properties": { - "count": { - "type": "long" - } - } - } - } - }, - "processor": { - "properties": { - "metric": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - }, - "transaction": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "transactions": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "error": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "errors": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "span": { - "properties": { - "transformations": { - "type": "long" - } - } - } - } - } - } - }, - "libbeat": { - "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, - "output": { - "properties": { - "events": { - "properties": { - "acked": { - "type": "long" - }, - "active": { - "type": "long" - }, - "batches": { - "type": "long" - }, - "dropped": { - "type": "long" - }, - "duplicates": { - "type": "long" - }, - "failed": { - "type": "long" - }, - "total": { - "type": "long" - }, - "toomany": { - "type": "long" - } - } - }, - "read": { - "properties": { - "bytes": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "write": { - "properties": { - "bytes": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - } - } - }, - "pipeline": { - "properties": { - "clients": { - "type": "long" - }, - "events": { - "properties": { - "active": { - "type": "long" - }, - "dropped": { - "type": "long" - }, - "failed": { - "type": "long" - }, - "filtered": { - "type": "long" - }, - "published": { - "type": "long" - }, - "retry": { - "type": "long" - }, - "total": { - "type": "long" - } - } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } - } - } - } - } - }, - "system": { - "properties": { - "load": { - "properties": { - "1": { - "type": "double" - }, - "5": { - "type": "double" - }, - "15": { - "type": "double" - }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "5": { - "type": "double" - }, - "15": { - "type": "double" - } - } - } - } - } - } - } - } + "memory_alloc": { + "type": "long" }, - "tags": { - "type": "keyword" + "memory_total": { + "type": "long" }, - "timestamp": { - "format": "date_time", - "type": "date" + "rss": { + "type": "long" } + } } + } }, - "cluster_uuid": { - "type": "keyword" - }, - "interval_ms": { - "type": "long" - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" + "libbeat": { + "properties": { + "output": { + "properties": { + "events": { + "properties": { + "acked": { + "type": "long" + }, + "active": { + "type": "long" + }, + "dropped": { + "type": "long" + }, + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } }, - "ip": { - "type": "keyword" + "read": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } }, - "name": { - "type": "keyword" + "write": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + } + } + }, + "pipeline": { + "properties": { + "events": { + "properties": { + "dropped": { + "type": "long" + }, + "failed": { + "type": "long" + }, + "published": { + "type": "long" + }, + "retry": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + } + } + }, + "system": { + "properties": { + "load": { + "properties": { + "1": { + "type": "double" }, - "transport_address": { - "type": "keyword" + "15": { + "type": "double" }, - "uuid": { - "type": "keyword" + "5": { + "type": "double" } + } } - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "type": { - "type": "keyword" + } } + } + }, + "timestamp": { + "format": "date_time", + "type": "date" } + } + }, + "cluster_uuid": { + "type": "keyword" }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "codec": "best_compression", - "format": "6", - "number_of_replicas": "0", - "number_of_shards": "1" + "metricset": { + "properties": { + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } + } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "codec": "best_compression", + "format": "6", + "number_of_replicas": "0", + "number_of_shards": "1" + } } + } } \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/mappings.json b/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/mappings.json index 9abc5c6381e45..4909b73909186 100644 --- a/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/basic_6.3.x/mappings.json @@ -1,7 +1,8 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-kibana-6-2018.07.23", "mappings": { "dynamic": false, @@ -9,95 +10,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -105,47 +32,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -155,36 +63,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -192,9 +73,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -208,10 +86,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -230,121 +122,54 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-es-6-2018.07.23", "mappings": { "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -359,125 +184,43 @@ } } }, - "fielddata": { + "indexing": { "properties": { - "memory_size_in_bytes": { + "index_time_in_millis": { "type": "long" }, - "evictions": { + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { "type": "long" } } }, - "store": { + "merges": { "properties": { - "size_in_bytes": { + "total_size_in_bytes": { "type": "long" } } }, - "indexing": { + "refresh": { "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - }, - "throttle_time_in_millis": { + "total_time_in_millis": { "type": "long" } } }, - "merges": { + "segments": { "properties": { - "total_size_in_bytes": { - "type": "long" + "count": { + "type": "integer" } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - }, - "segments": { - "properties": { - "count": { - "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - } - } - }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -486,36 +229,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -534,14 +260,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -550,24 +274,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -577,41 +292,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -620,68 +335,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -702,15 +441,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -718,15 +448,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -745,116 +466,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -870,16 +572,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -891,12 +587,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -906,12 +596,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -921,71 +605,10 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, "thread_pool": { "properties": { "bulk": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -996,9 +619,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1009,22 +629,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1035,22 +639,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1073,246 +661,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/beats-with-restarted-instance/mappings.json b/x-pack/test/functional/es_archives/monitoring/beats-with-restarted-instance/mappings.json index f616ffc1d3aec..a8017ee6631b0 100644 --- a/x-pack/test/functional/es_archives/monitoring/beats-with-restarted-instance/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/beats-with-restarted-instance/mappings.json @@ -7,105 +7,6 @@ "properties": { "beats_state": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, "timestamp": { "format": "date_time", "type": "date" @@ -116,12 +17,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -135,108 +30,40 @@ }, "metrics": { "properties": { - "beat": { + "apm-server": { "properties": { - "cpu": { + "processor": { "properties": { - "system": { + "error": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "total": { + "metric": { "properties": { - "value": { - "type": "long" - }, - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "user": { + "span": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } - } - } - }, - "info": { - "properties": { - "ephemeral_id": { - "type": "keyword" }, - "uptime": { + "transaction": { "properties": { - "ms": { + "transformations": { "type": "long" } } } } }, - "memstats": { - "properties": { - "gc_next": { - "type": "long" - }, - "memory_alloc": { - "type": "long" - }, - "memory_total": { - "type": "long" - }, - "rss": { - "type": "long" - } - } - }, - "handles": { - "properties": { - "open": { - "type": "long" - }, - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - } - } - } - } - }, - "apm-server": { - "properties": { "server": { "properties": { "request": { @@ -246,17 +73,6 @@ } } }, - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "response": { "properties": { "count": { @@ -264,53 +80,47 @@ }, "errors": { "properties": { - "count": { - "type": "long" - }, - "toolarge": { + "closed": { "type": "long" }, - "validate": { + "concurrency": { "type": "long" }, - "ratelimit": { + "decode": { "type": "long" }, - "queue": { + "forbidden": { "type": "long" }, - "closed": { + "internal": { "type": "long" }, - "forbidden": { + "method": { "type": "long" }, - "concurrency": { + "queue": { "type": "long" }, - "unauthorized": { + "ratelimit": { "type": "long" }, - "internal": { + "toolarge": { "type": "long" }, - "decode": { + "unauthorized": { "type": "long" }, - "method": { + "validate": { "type": "long" } } }, "valid": { "properties": { - "ok": { - "type": "long" - }, "accepted": { "type": "long" }, - "count": { + "ok": { "type": "long" } } @@ -318,296 +128,109 @@ } } } - }, - "decoder": { + } + } + }, + "beat": { + "properties": { + "cpu": { "properties": { - "deflate": { + "total": { "properties": { - "content-length": { - "type": "long" - }, - "count": { + "value": { "type": "long" } } + } + } + }, + "handles": { + "properties": { + "open": { + "type": "long" + } + } + }, + "info": { + "properties": { + "ephemeral_id": { + "type": "keyword" + } + } + }, + "memstats": { + "properties": { + "gc_next": { + "type": "long" + }, + "memory_alloc": { + "type": "long" + }, + "memory_total": { + "type": "long" }, - "gzip": { + "rss": { + "type": "long" + } + } + } + } + }, + "libbeat": { + "properties": { + "output": { + "properties": { + "events": { "properties": { - "content-length": { + "acked": { "type": "long" }, - "count": { + "active": { "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { + }, + "dropped": { "type": "long" }, - "count": { + "failed": { + "type": "long" + }, + "total": { "type": "long" } } }, - "reader": { + "read": { "properties": { - "size": { + "bytes": { "type": "long" }, - "count": { + "errors": { "type": "long" } } }, - "missing-content-length": { + "write": { "properties": { - "count": { + "bytes": { + "type": "long" + }, + "errors": { "type": "long" } } } } }, - "processor": { + "pipeline": { "properties": { - "metric": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - }, - "transaction": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "transactions": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "error": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "errors": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "span": { - "properties": { - "transformations": { - "type": "long" - } - } - } - } - } - } - }, - "libbeat": { - "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, - "output": { - "properties": { - "events": { - "properties": { - "acked": { - "type": "long" - }, - "active": { - "type": "long" - }, - "batches": { - "type": "long" - }, - "dropped": { - "type": "long" - }, - "duplicates": { - "type": "long" - }, - "failed": { - "type": "long" - }, - "total": { - "type": "long" - }, - "toomany": { - "type": "long" - } - } - }, - "read": { - "properties": { - "bytes": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "write": { - "properties": { - "bytes": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - } - } - }, - "pipeline": { - "properties": { - "clients": { - "type": "long" - }, "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -618,13 +241,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -637,24 +253,11 @@ "1": { "type": "double" }, - "5": { - "type": "double" - }, "15": { "type": "double" }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "5": { - "type": "double" - }, - "15": { - "type": "double" - } - } + "5": { + "type": "double" } } } @@ -662,9 +265,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -674,25 +274,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -725,115 +316,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -848,29 +371,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -885,44 +391,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -931,42 +402,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -975,36 +416,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -1023,14 +447,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -1039,24 +461,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -1066,41 +479,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -1109,68 +522,92 @@ } } }, - "cluster_stats": { - "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" - } - } - }, - "cluster_state": { - "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" - } - } - }, - "node_stats": { + "indices_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "_all": { "properties": { - "docs": { + "primaries": { "properties": { - "count": { - "type": "long" + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "job_stats": { + "properties": { + "job_id": { + "type": "keyword" + } + } + }, + "node_stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -1191,15 +628,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1207,15 +635,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1234,116 +653,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1359,16 +759,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -1380,12 +774,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -1395,12 +783,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -1410,398 +792,97 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "index": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "search": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "write": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - } - } - } - } - }, - "index_recovery": { - "type": "object" - }, - "shard": { - "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, - "index": { - "type": "keyword" - }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, - "node": { - "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" - }, - "state": { - "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - } - } - }, - "ccr_stats": { - "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { - "type": "keyword" - }, - "follower_index": { - "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", + "thread_pool": { "properties": { - "from_seq_no": { - "type": "long" + "bulk": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "retries": { - "type": "integer" + "get": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "exception": { - "type": "object", + "index": { "properties": { - "type": { - "type": "keyword" + "queue": { + "type": "integer" }, - "reason": { - "type": "text" + "rejected": { + "type": "long" + } + } + }, + "search": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" } } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" }, - "reason": { - "type": "text" + "write": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } } } } } }, - "ccr_auto_follow_stats": { + "shard": { "properties": { - "number_of_failed_follow_indices": { - "type": "long" + "index": { + "type": "keyword" }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" + "node": { + "type": "keyword" }, - "number_of_successful_follow_indices": { - "type": "long" + "primary": { + "type": "boolean" }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } + "state": { + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "type": "keyword" }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } + "uuid": { + "type": "keyword" } } + }, + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1824,24 +905,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -1863,6 +929,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, @@ -1888,95 +969,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -1984,47 +991,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -2034,36 +1022,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -2071,9 +1032,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -2087,10 +1045,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/beats/mappings.json b/x-pack/test/functional/es_archives/monitoring/beats/mappings.json index e9e03d6ff2a16..2ea26547e767b 100644 --- a/x-pack/test/functional/es_archives/monitoring/beats/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/beats/mappings.json @@ -6,115 +6,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -129,29 +61,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -166,44 +81,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -212,42 +92,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -256,36 +106,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -304,14 +137,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -320,24 +151,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -347,41 +169,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -390,68 +212,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -472,15 +318,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -488,15 +325,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -515,116 +343,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -640,16 +449,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -661,12 +464,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -676,12 +473,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -691,110 +482,30 @@ } } }, - "jvm": { + "thread_pool": { "properties": { - "mem": { + "bulk": { "properties": { - "heap_used_in_bytes": { - "type": "long" + "queue": { + "type": "integer" }, - "heap_used_percent": { - "type": "half_float" + "rejected": { + "type": "long" + } + } + }, + "get": { + "properties": { + "queue": { + "type": "integer" }, - "heap_max_in_bytes": { + "rejected": { "type": "long" } } }, - "gc": { + "index": { "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "index": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -805,22 +516,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -843,368 +538,64 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { - "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { - "type": "keyword" - }, - "follower_index": { - "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } - } - } - }, - "settings": { - "index": { - "codec": "best_compression", - "format": "6", - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "index": ".monitoring-beats-6-2017.12.19", - "mappings": { - "dynamic": false, - "properties": { - "beats_state": { + "source_node": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } + "name": { + "type": "keyword" }, + "uuid": { + "type": "keyword" + } + } + }, + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "format": "6", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "index": ".monitoring-beats-6-2017.12.19", + "mappings": { + "dynamic": false, + "properties": { + "beats_state": { + "properties": { "timestamp": { "format": "date_time", "type": "date" @@ -1215,12 +606,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -1234,108 +619,40 @@ }, "metrics": { "properties": { - "beat": { + "apm-server": { "properties": { - "cpu": { + "processor": { "properties": { - "system": { + "error": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "total": { + "metric": { "properties": { - "value": { - "type": "long" - }, - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "user": { + "span": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } - } - } - }, - "info": { - "properties": { - "ephemeral_id": { - "type": "keyword" }, - "uptime": { + "transaction": { "properties": { - "ms": { + "transformations": { "type": "long" } } } } }, - "memstats": { - "properties": { - "gc_next": { - "type": "long" - }, - "memory_alloc": { - "type": "long" - }, - "memory_total": { - "type": "long" - }, - "rss": { - "type": "long" - } - } - }, - "handles": { - "properties": { - "open": { - "type": "long" - }, - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - } - } - } - } - }, - "apm-server": { - "properties": { "server": { "properties": { "request": { @@ -1345,17 +662,6 @@ } } }, - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "response": { "properties": { "count": { @@ -1363,247 +669,50 @@ }, "errors": { "properties": { - "count": { - "type": "long" - }, - "toolarge": { - "type": "long" - }, - "validate": { - "type": "long" - }, - "ratelimit": { - "type": "long" - }, - "queue": { - "type": "long" - }, "closed": { "type": "long" }, - "forbidden": { - "type": "long" - }, "concurrency": { "type": "long" }, - "unauthorized": { - "type": "long" - }, - "internal": { - "type": "long" - }, "decode": { "type": "long" }, - "method": { - "type": "long" - } - } - }, - "valid": { - "properties": { - "ok": { - "type": "long" - }, - "accepted": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - } - } - }, - "decoder": { - "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "reader": { - "properties": { - "size": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "missing-content-length": { - "properties": { - "count": { - "type": "long" - } - } - } - } - }, - "processor": { - "properties": { - "metric": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { + "forbidden": { "type": "long" }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "errors": { + "internal": { "type": "long" }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { + "method": { "type": "long" }, - "count": { - "type": "long" - } - } - } - } - }, - "transaction": { - "properties": { - "decoding": { - "properties": { - "errors": { + "queue": { "type": "long" }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { + "ratelimit": { "type": "long" }, - "count": { + "toolarge": { "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "transactions": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "error": { - "properties": { - "decoding": { - "properties": { - "errors": { + }, + "unauthorized": { "type": "long" }, - "count": { + "validate": { "type": "long" } } }, - "validation": { + "valid": { "properties": { - "errors": { + "accepted": { "type": "long" }, - "count": { + "ok": { "type": "long" } } - }, - "transformations": { - "type": "long" - }, - "errors": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "span": { - "properties": { - "transformations": { - "type": "long" } } } @@ -1611,28 +720,53 @@ } } }, - "libbeat": { + "beat": { "properties": { - "config": { + "cpu": { "properties": { - "module": { + "total": { "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { + "value": { "type": "long" } } - }, - "reloads": { + } + } + }, + "handles": { + "properties": { + "open": { "type": "long" } } }, + "info": { + "properties": { + "ephemeral_id": { + "type": "keyword" + } + } + }, + "memstats": { + "properties": { + "gc_next": { + "type": "long" + }, + "memory_alloc": { + "type": "long" + }, + "memory_total": { + "type": "long" + }, + "rss": { + "type": "long" + } + } + } + } + }, + "libbeat": { + "properties": { "output": { "properties": { "events": { @@ -1643,23 +777,14 @@ "active": { "type": "long" }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, "total": { "type": "long" - }, - "toomany": { - "type": "long" } } }, @@ -1673,9 +798,6 @@ } } }, - "type": { - "type": "keyword" - }, "write": { "properties": { "bytes": { @@ -1690,23 +812,14 @@ }, "pipeline": { "properties": { - "clients": { - "type": "long" - }, "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -1717,13 +830,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -1736,24 +842,11 @@ "1": { "type": "double" }, - "5": { - "type": "double" - }, "15": { "type": "double" }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "5": { - "type": "double" - }, - "15": { - "type": "double" - } - } + "5": { + "type": "double" } } } @@ -1761,9 +854,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -1773,25 +863,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -1823,95 +904,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -1919,47 +926,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -1969,36 +957,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -2006,9 +967,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -2022,10 +980,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/ccr/mappings.json b/x-pack/test/functional/es_archives/monitoring/ccr/mappings.json index c034852554a44..4a941d9bd0d77 100644 --- a/x-pack/test/functional/es_archives/monitoring/ccr/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/ccr/mappings.json @@ -1,121 +1,54 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-es-6-2018.09.19", "mappings": { "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -130,29 +63,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -167,44 +83,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -213,42 +94,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -257,36 +108,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -305,14 +139,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -321,24 +153,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -348,41 +171,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -391,68 +214,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -473,15 +320,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -489,15 +327,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -516,116 +345,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -641,16 +451,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -662,12 +466,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -677,12 +475,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -692,71 +484,10 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, "thread_pool": { "properties": { "bulk": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -767,9 +498,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -780,22 +508,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -806,22 +518,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -844,246 +540,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/logs/data.json.gz b/x-pack/test/functional/es_archives/monitoring/logs/data.json.gz index 7f48fca2ea8a8..851db319e9ecf 100644 Binary files a/x-pack/test/functional/es_archives/monitoring/logs/data.json.gz and b/x-pack/test/functional/es_archives/monitoring/logs/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/monitoring/logs/mappings.json b/x-pack/test/functional/es_archives/monitoring/logs/mappings.json index 6dbecb8d503fd..b392c18e4e49b 100644 --- a/x-pack/test/functional/es_archives/monitoring/logs/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/logs/mappings.json @@ -8,93 +8,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -104,112 +25,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -224,16 +63,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -254,22 +83,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -277,66 +90,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -351,18 +108,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -390,17 +137,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -413,17 +151,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -491,13 +220,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -507,33 +229,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -555,58 +257,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -614,13 +268,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -628,15 +275,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -648,12 +289,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -661,18 +296,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -693,33 +318,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -769,13 +376,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -824,15 +424,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -842,9 +436,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -862,26 +453,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -889,14 +464,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -912,12 +481,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -930,22 +493,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -956,9 +503,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -969,22 +513,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -995,22 +523,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1039,12 +551,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1052,22 +558,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } diff --git a/x-pack/test/functional/es_archives/monitoring/logs_multiple_clusters/mappings.json b/x-pack/test/functional/es_archives/monitoring/logs_multiple_clusters/mappings.json index ccea50f747800..2d6c2f885f831 100644 --- a/x-pack/test/functional/es_archives/monitoring/logs_multiple_clusters/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/logs_multiple_clusters/mappings.json @@ -8,96 +8,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "follower_aliases_version": { - "type": "long" - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -107,112 +25,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -227,16 +63,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -257,22 +83,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -280,66 +90,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -354,18 +108,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -393,17 +137,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -416,17 +151,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -494,13 +220,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -510,33 +229,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -558,58 +257,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -617,13 +268,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -631,15 +275,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -651,12 +289,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -664,18 +296,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -696,33 +318,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -772,13 +376,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -827,15 +424,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -845,9 +436,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -865,26 +453,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -892,14 +464,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -915,12 +481,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -933,22 +493,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -959,9 +503,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -972,22 +513,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -998,22 +523,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1042,12 +551,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1055,22 +558,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } diff --git a/x-pack/test/functional/es_archives/monitoring/logstash-pipelines/mappings.json b/x-pack/test/functional/es_archives/monitoring/logstash-pipelines/mappings.json index aa3aff8b5c41a..98303a4b29d67 100644 --- a/x-pack/test/functional/es_archives/monitoring/logstash-pipelines/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/logstash-pipelines/mappings.json @@ -8,83 +8,25 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, - "logstash_stats": { - "type": "object", + "logstash_state": { "properties": { - "logstash": { + "pipeline": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "version": { + "hash": { "type": "keyword" }, - "snapshot": { - "type": "boolean" - }, - "status": { + "id": { "type": "keyword" - }, - "pipeline": { - "properties": { - "workers": { - "type": "short" - }, - "batch_size": { - "type": "long" - } - } } } - }, + } + } + }, + "logstash_stats": { + "properties": { "events": { "properties": { - "filtered": { + "duration_in_millis": { "type": "long" }, "in": { @@ -92,48 +34,11 @@ }, "out": { "type": "long" - }, - "duration_in_millis": { - "type": "long" } } }, - "timestamp": { - "type": "date" - }, "jvm": { "properties": { - "uptime_in_millis": { - "type": "long" - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -141,50 +46,30 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } + }, + "uptime_in_millis": { + "type": "long" } } }, - "os": { + "logstash": { "properties": { - "cpu": { - "properties": { - "load_average": { - "properties": { - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" - } - } - } - } + "uuid": { + "type": "keyword" }, + "version": { + "type": "keyword" + } + } + }, + "os": { + "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -199,103 +84,70 @@ } } } + }, + "cpuacct": { + "properties": { + "usage_nanos": { + "type": "long" + } + } } } - } - } - }, - "process": { - "properties": { + }, "cpu": { "properties": { - "percent": { - "type": "long" + "load_average": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, - "queue": { - "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" } } }, "pipelines": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { - "in": { - "type": "long" - }, - "filtered": { - "type": "long" - }, - "out": { - "type": "long" - }, "duration_in_millis": { "type": "long" }, - "queue_push_duration_in_millis": { + "out": { "type": "long" } } }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, "queue": { "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" - }, "max_queue_size_in_bytes": { "type": "long" }, "queue_size_in_bytes": { "type": "long" + }, + "type": { + "type": "keyword" } } }, "vertices": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "pipeline_ephemeral_id": { - "type": "keyword" + "duration_in_millis": { + "type": "long" }, "events_in": { "type": "long" @@ -303,111 +155,63 @@ "events_out": { "type": "long" }, - "duration_in_millis": { - "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" + "id": { + "type": "keyword" }, - "long_counters": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - } + "pipeline_ephemeral_id": { + "type": "keyword" }, - "double_gauges": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - } + "queue_push_duration_in_millis": { + "type": "float" } - } - }, - "reloads": { + }, + "type": "nested" + } + }, + "type": "nested" + }, + "process": { + "properties": { + "cpu": { "properties": { - "failures": { - "type": "long" - }, - "successes": { + "percent": { "type": "long" } } } } }, - "workers": { - "type": "short" + "queue": { + "properties": { + "events_count": { + "type": "long" + } + } }, - "batch_size": { - "type": "integer" + "timestamp": { + "type": "date" } } }, - "logstash_state": { + "metricset": { "properties": { - "uuid": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "workers": { - "type": "short" - }, - "batch_size": { - "type": "integer" - }, - "format": { + "fields": { + "keyword": { + "ignore_above": 256, "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "representation": { - "enabled": false } - } + }, + "type": "text" } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -431,115 +235,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -554,29 +290,64 @@ } } }, - "fielddata": { + "indexing": { "properties": { - "memory_size_in_bytes": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { "type": "long" }, - "evictions": { + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } }, + "segments": { + "properties": { + "count": { + "type": "integer" + } + } + }, "store": { "properties": { "size_in_bytes": { "type": "long" } } + } + } + }, + "total": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + } + } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -595,14 +366,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -611,24 +380,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -638,176 +398,85 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } } } - }, - "total": { + } + } + }, + "indices_stats": { + "properties": { + "_all": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "fielddata": { + "primaries": { "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - }, - "throttle_time_in_millis": { - "type": "long" - } - } - }, - "merges": { - "properties": { - "total_size_in_bytes": { - "type": "long" - } - } - }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } } } }, - "segments": { + "total": { "properties": { - "count": { - "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - } - } - }, - "refresh": { - "properties": { - "total_time_in_millis": { - "type": "long" + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } } } } @@ -815,68 +484,49 @@ } } }, - "cluster_stats": { - "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" - } - } - }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -897,15 +547,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -913,15 +554,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -940,116 +572,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1065,16 +678,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -1086,12 +693,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -1101,12 +702,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -1116,71 +711,10 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, "thread_pool": { "properties": { "bulk": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1191,9 +725,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1204,22 +735,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1230,22 +745,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1268,246 +767,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/logstash/changing_pipelines/mappings.json b/x-pack/test/functional/es_archives/monitoring/logstash/changing_pipelines/mappings.json index 28c3c0e1b9e87..3c7511f426e2c 100644 --- a/x-pack/test/functional/es_archives/monitoring/logstash/changing_pipelines/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/logstash/changing_pipelines/mappings.json @@ -71,96 +71,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "follower_aliases_version": { - "type": "long" - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -170,112 +88,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -290,16 +126,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -320,22 +146,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -343,66 +153,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -417,18 +171,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -456,17 +200,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -479,17 +214,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -557,13 +283,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -573,33 +292,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -621,58 +320,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -680,13 +331,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -694,15 +338,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -714,12 +352,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -727,18 +359,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -759,33 +381,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -835,13 +439,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -890,15 +487,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -908,9 +499,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -928,26 +516,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -955,14 +527,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -978,12 +544,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -996,22 +556,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1022,9 +566,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1035,22 +576,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1061,22 +586,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1105,12 +614,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1118,22 +621,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -1175,79 +665,27 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "logstash_state": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "pipeline": { "properties": { - "batch_size": { - "type": "integer" - }, - "ephemeral_id": { - "type": "keyword" - }, - "format": { - "type": "keyword" - }, "hash": { "type": "keyword" }, "id": { "type": "keyword" - }, - "representation": { - "enabled": false, - "type": "object" - }, - "version": { - "type": "keyword" - }, - "workers": { - "type": "short" } } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "logstash_stats": { "properties": { - "batch_size": { - "type": "integer" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, "in": { "type": "long" }, @@ -1258,34 +696,6 @@ }, "jvm": { "properties": { - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -1293,9 +703,6 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } }, @@ -1306,34 +713,6 @@ }, "logstash": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "batch_size": { - "type": "long" - }, - "workers": { - "type": "short" - } - } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -1348,9 +727,6 @@ "properties": { "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1368,9 +744,6 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } @@ -1399,25 +772,13 @@ }, "pipelines": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, - "in": { - "type": "long" - }, "out": { "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" } } }, @@ -1429,9 +790,6 @@ }, "queue": { "properties": { - "events_count": { - "type": "long" - }, "max_queue_size_in_bytes": { "type": "long" }, @@ -1443,29 +801,8 @@ } } }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, "vertices": { "properties": { - "double_gauges": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - }, - "type": "nested" - }, "duration_in_millis": { "type": "long" }, @@ -1478,22 +815,11 @@ "id": { "type": "keyword" }, - "long_counters": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - }, - "type": "nested" - }, "pipeline_ephemeral_id": { "type": "keyword" }, "queue_push_duration_in_millis": { - "type": "long" + "type": "float" } }, "type": "nested" @@ -1509,12 +835,6 @@ "type": "long" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -1522,50 +842,24 @@ "properties": { "events_count": { "type": "long" - }, - "type": { - "type": "keyword" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" } } }, "timestamp": { "type": "date" - }, - "workers": { - "type": "short" } } }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/logstash_pipelines_multicluster/mappings.json b/x-pack/test/functional/es_archives/monitoring/logstash_pipelines_multicluster/mappings.json index f2a7cca4efd8b..de13ddf620d77 100644 --- a/x-pack/test/functional/es_archives/monitoring/logstash_pipelines_multicluster/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/logstash_pipelines_multicluster/mappings.json @@ -71,96 +71,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "follower_aliases_version": { - "type": "long" - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -170,112 +88,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -290,16 +126,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -320,22 +146,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -343,66 +153,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -417,18 +171,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -456,17 +200,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -479,17 +214,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -557,13 +283,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -573,33 +292,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -621,58 +320,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -680,13 +331,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -694,15 +338,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -714,12 +352,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -727,18 +359,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -759,33 +381,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -835,13 +439,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -890,15 +487,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -908,9 +499,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -928,26 +516,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -955,14 +527,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -978,12 +544,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -996,22 +556,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1022,9 +566,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1035,22 +576,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1061,22 +586,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1105,12 +614,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1118,22 +621,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -1175,79 +665,27 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "logstash_state": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "pipeline": { "properties": { - "batch_size": { - "type": "integer" - }, - "ephemeral_id": { - "type": "keyword" - }, - "format": { - "type": "keyword" - }, "hash": { "type": "keyword" }, "id": { "type": "keyword" - }, - "representation": { - "enabled": false, - "type": "object" - }, - "version": { - "type": "keyword" - }, - "workers": { - "type": "short" } } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "logstash_stats": { "properties": { - "batch_size": { - "type": "integer" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, "in": { "type": "long" }, @@ -1258,34 +696,6 @@ }, "jvm": { "properties": { - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -1293,9 +703,6 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } }, @@ -1306,34 +713,6 @@ }, "logstash": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "batch_size": { - "type": "long" - }, - "workers": { - "type": "short" - } - } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -1348,9 +727,6 @@ "properties": { "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1368,9 +744,6 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } @@ -1399,25 +772,13 @@ }, "pipelines": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, - "in": { - "type": "long" - }, "out": { "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" } } }, @@ -1429,9 +790,6 @@ }, "queue": { "properties": { - "events_count": { - "type": "long" - }, "max_queue_size_in_bytes": { "type": "long" }, @@ -1443,29 +801,8 @@ } } }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, "vertices": { "properties": { - "double_gauges": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - }, - "type": "nested" - }, "duration_in_millis": { "type": "long" }, @@ -1478,22 +815,11 @@ "id": { "type": "keyword" }, - "long_counters": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - }, - "type": "nested" - }, "pipeline_ephemeral_id": { "type": "keyword" }, "queue_push_duration_in_millis": { - "type": "long" + "type": "float" } }, "type": "nested" @@ -1509,12 +835,6 @@ "type": "long" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -1522,50 +842,24 @@ "properties": { "events_count": { "type": "long" - }, - "type": { - "type": "keyword" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" } } }, "timestamp": { "type": "date" - }, - "workers": { - "type": "short" } } }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/multi-basic/mappings.json b/x-pack/test/functional/es_archives/monitoring/multi-basic/mappings.json index 75bb2602f1a15..76717c0a61bba 100644 --- a/x-pack/test/functional/es_archives/monitoring/multi-basic/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/multi-basic/mappings.json @@ -5,24 +5,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -44,6 +29,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, @@ -68,95 +68,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -164,47 +90,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -214,36 +121,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -251,9 +131,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -267,10 +144,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -293,136 +184,92 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { + "nodes_hash": { + "type": "integer" + } + } + }, + "cluster_uuid": { + "type": "keyword" + }, + "index_stats": { + "properties": { + "index": { + "type": "keyword" + }, + "primaries": { "properties": { - "primaries": { + "docs": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } + "count": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } + "index_total": { + "type": "long" }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } + "throttle_time_in_millis": { + "type": "long" } } }, - "total": { + "merges": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } + "total_size_in_bytes": { + "type": "long" } } - } - } - } - } - }, - "index_stats": { - "properties": { - "index": { - "type": "keyword" - }, - "primaries": { - "properties": { - "docs": { + }, + "refresh": { "properties": { - "count": { + "total_time_in_millis": { "type": "long" } } }, - "fielddata": { + "segments": { "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" + "count": { + "type": "integer" } } }, @@ -432,13 +279,24 @@ "type": "long" } } + } + } + }, + "total": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + } + } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -457,14 +315,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -473,24 +329,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -500,79 +347,144 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } } } - }, - "total": { + } + } + }, + "indices_stats": { + "properties": { + "_all": { "properties": { - "docs": { + "primaries": { "properties": { - "count": { - "type": "long" + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } } } }, - "fielddata": { + "total": { "properties": { - "memory_size_in_bytes": { - "type": "long" + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } }, - "evictions": { - "type": "long" + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "job_stats": { + "properties": { + "job_id": { + "type": "keyword" + } + } + }, + "node_stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, - "store": { + "total": { "properties": { - "size_in_bytes": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -580,26 +492,10 @@ } } }, - "merges": { - "properties": { - "total_size_in_bytes": { - "type": "long" - } - } - }, "query_cache": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -607,24 +503,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -634,284 +521,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "refresh": { - "properties": { - "total_time_in_millis": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } - } - } - }, - "cluster_stats": { - "properties": { - "nodes": { - "type": "object" }, - "indices": { - "type": "object" - } - } - }, - "cluster_state": { - "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" - } - } - }, - "node_stats": { - "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "jvm": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_time_in_millis": { - "type": "long" - }, - "index_total": { - "type": "long" - }, - "throttle_time_in_millis": { - "type": "long" - } - } - }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "gc": { "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + }, + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + } + } } } }, - "segments": { + "mem": { "properties": { - "count": { - "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { + "heap_max_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "heap_used_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" + "heap_used_percent": { + "type": "half_float" } } } } }, - "fs": { - "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { - "properties": { - "total": { - "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" - }, - "write_kilobytes": { - "type": "long" - } - } - } - } - } - } + "node_id": { + "type": "keyword" }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -927,16 +627,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -948,127 +642,28 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" - } - } - } - } - } - } - }, - "process": { - "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, - "cpu": { - "properties": { - "percent": { - "type": "half_float" - } - } - } - } - }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } } } } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { + } + } + } + }, + "process": { + "properties": { + "cpu": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" + "percent": { + "type": "half_float" } } - }, - "index": { + } + } + }, + "thread_pool": { + "properties": { + "bulk": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1077,11 +672,8 @@ } } }, - "management": { + "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1090,11 +682,8 @@ } } }, - "search": { + "index": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1103,11 +692,8 @@ } } }, - "watcher": { + "search": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1130,246 +716,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1394,83 +775,25 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, - "logstash_stats": { - "type": "object", + "logstash_state": { "properties": { - "logstash": { + "pipeline": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, - "status": { + "hash": { + "type": "keyword" + }, + "id": { "type": "keyword" - }, - "pipeline": { - "properties": { - "workers": { - "type": "short" - }, - "batch_size": { - "type": "long" - } - } } } - }, + } + } + }, + "logstash_stats": { + "properties": { "events": { "properties": { - "filtered": { + "duration_in_millis": { "type": "long" }, "in": { @@ -1478,48 +801,11 @@ }, "out": { "type": "long" - }, - "duration_in_millis": { - "type": "long" } } }, - "timestamp": { - "type": "date" - }, "jvm": { "properties": { - "uptime_in_millis": { - "type": "long" - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -1527,50 +813,30 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } + }, + "uptime_in_millis": { + "type": "long" } } }, - "os": { + "logstash": { "properties": { - "cpu": { - "properties": { - "load_average": { - "properties": { - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" - } - } - } - } + "uuid": { + "type": "keyword" }, + "version": { + "type": "keyword" + } + } + }, + "os": { + "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1585,103 +851,70 @@ } } } + }, + "cpuacct": { + "properties": { + "usage_nanos": { + "type": "long" + } + } } } - } - } - }, - "process": { - "properties": { + }, "cpu": { "properties": { - "percent": { - "type": "long" + "load_average": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, - "queue": { - "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" } } }, "pipelines": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { - "in": { - "type": "long" - }, - "filtered": { - "type": "long" - }, - "out": { - "type": "long" - }, "duration_in_millis": { "type": "long" }, - "queue_push_duration_in_millis": { + "out": { "type": "long" } } }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, "queue": { "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" - }, "max_queue_size_in_bytes": { "type": "long" }, "queue_size_in_bytes": { "type": "long" + }, + "type": { + "type": "keyword" } } }, "vertices": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "pipeline_ephemeral_id": { - "type": "keyword" + "duration_in_millis": { + "type": "long" }, "events_in": { "type": "long" @@ -1689,111 +922,63 @@ "events_out": { "type": "long" }, - "duration_in_millis": { - "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" + "id": { + "type": "keyword" }, - "long_counters": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - } + "pipeline_ephemeral_id": { + "type": "keyword" }, - "double_gauges": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - } + "queue_push_duration_in_millis": { + "type": "float" } - } - }, - "reloads": { + }, + "type": "nested" + } + }, + "type": "nested" + }, + "process": { + "properties": { + "cpu": { "properties": { - "failures": { - "type": "long" - }, - "successes": { + "percent": { "type": "long" } } } } }, - "workers": { - "type": "short" + "queue": { + "properties": { + "events_count": { + "type": "long" + } + } }, - "batch_size": { - "type": "integer" + "timestamp": { + "type": "date" } } }, - "logstash_state": { + "metricset": { "properties": { - "uuid": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "workers": { - "type": "short" - }, - "batch_size": { - "type": "integer" - }, - "format": { - "type": "keyword" - }, - "version": { + "fields": { + "keyword": { + "ignore_above": 256, "type": "keyword" - }, - "representation": { - "enabled": false } - } + }, + "type": "text" } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json b/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json index 7ba6fd939afa8..7e7084698b788 100644 --- a/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json @@ -1,121 +1,54 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-es-6-2017.08.15", "mappings": { "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -130,29 +63,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -167,44 +83,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -213,42 +94,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -257,36 +108,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -305,14 +139,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -321,24 +153,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -348,41 +171,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -391,68 +214,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -473,15 +320,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -489,15 +327,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -516,116 +345,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -641,16 +451,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -662,12 +466,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -677,12 +475,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -692,136 +484,40 @@ } } }, - "jvm": { + "thread_pool": { "properties": { - "mem": { + "bulk": { "properties": { - "heap_used_in_bytes": { + "queue": { + "type": "integer" + }, + "rejected": { "type": "long" + } + } + }, + "get": { + "properties": { + "queue": { + "type": "integer" }, - "heap_used_percent": { - "type": "half_float" + "rejected": { + "type": "long" + } + } + }, + "index": { + "properties": { + "queue": { + "type": "integer" }, - "heap_max_in_bytes": { + "rejected": { "type": "long" } } }, - "gc": { + "search": { "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "index": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "search": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -844,246 +540,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1102,7 +593,8 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-logstash-6-2017.08.15", "mappings": { "dynamic": false, @@ -1110,83 +602,25 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, - "logstash_stats": { - "type": "object", + "logstash_state": { "properties": { - "logstash": { + "pipeline": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "version": { + "hash": { "type": "keyword" }, - "snapshot": { - "type": "boolean" - }, - "status": { + "id": { "type": "keyword" - }, - "pipeline": { - "properties": { - "workers": { - "type": "short" - }, - "batch_size": { - "type": "long" - } - } } } - }, + } + } + }, + "logstash_stats": { + "properties": { "events": { "properties": { - "filtered": { + "duration_in_millis": { "type": "long" }, "in": { @@ -1194,48 +628,11 @@ }, "out": { "type": "long" - }, - "duration_in_millis": { - "type": "long" } } }, - "timestamp": { - "type": "date" - }, "jvm": { "properties": { - "uptime_in_millis": { - "type": "long" - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -1243,50 +640,30 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } + }, + "uptime_in_millis": { + "type": "long" } } }, - "os": { + "logstash": { "properties": { - "cpu": { - "properties": { - "load_average": { - "properties": { - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" - } - } - } - } + "uuid": { + "type": "keyword" }, + "version": { + "type": "keyword" + } + } + }, + "os": { + "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1301,103 +678,70 @@ } } } + }, + "cpuacct": { + "properties": { + "usage_nanos": { + "type": "long" + } + } } } - } - } - }, - "process": { - "properties": { + }, "cpu": { "properties": { - "percent": { - "type": "long" + "load_average": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, - "queue": { - "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" } } }, "pipelines": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { - "in": { - "type": "long" - }, - "filtered": { - "type": "long" - }, - "out": { - "type": "long" - }, "duration_in_millis": { "type": "long" }, - "queue_push_duration_in_millis": { + "out": { "type": "long" } } }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, "queue": { "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" - }, "max_queue_size_in_bytes": { "type": "long" }, "queue_size_in_bytes": { "type": "long" + }, + "type": { + "type": "keyword" } } }, "vertices": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "pipeline_ephemeral_id": { - "type": "keyword" + "duration_in_millis": { + "type": "long" }, "events_in": { "type": "long" @@ -1405,111 +749,63 @@ "events_out": { "type": "long" }, - "duration_in_millis": { - "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" + "id": { + "type": "keyword" }, - "long_counters": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - } + "pipeline_ephemeral_id": { + "type": "keyword" }, - "double_gauges": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - } + "queue_push_duration_in_millis": { + "type": "float" } - } - }, - "reloads": { + }, + "type": "nested" + } + }, + "type": "nested" + }, + "process": { + "properties": { + "cpu": { "properties": { - "failures": { - "type": "long" - }, - "successes": { + "percent": { "type": "long" } } } } }, - "workers": { - "type": "short" + "queue": { + "properties": { + "events_count": { + "type": "long" + } + } }, - "batch_size": { - "type": "integer" + "timestamp": { + "type": "date" } } }, - "logstash_state": { + "metricset": { "properties": { - "uuid": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "workers": { - "type": "short" - }, - "batch_size": { - "type": "integer" - }, - "format": { + "fields": { + "keyword": { + "ignore_above": 256, "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "representation": { - "enabled": false } - } + }, + "type": "text" } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1528,7 +824,8 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-kibana-6-2017.08.15", "mappings": { "dynamic": false, @@ -1536,102 +833,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { - "usage": { - "properties": { - "index": { - "type": "keyword" - } - } + "concurrent_connections": { + "type": "long" }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -1639,47 +855,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -1689,36 +886,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -1726,9 +896,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -1742,10 +909,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1764,29 +945,15 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-alerts-6", "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -1808,6 +975,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/data.json.gz b/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/data.json.gz index 0548d40cd4f82..e4cffebf210ad 100644 Binary files a/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/data.json.gz and b/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/mappings.json b/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/mappings.json index 2567ef3949ef9..c8c423463e477 100644 --- a/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/setup/collection/detect_beats/mappings.json @@ -8,93 +8,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -104,112 +25,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -224,16 +63,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -254,22 +83,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -277,66 +90,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -351,18 +108,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -390,17 +137,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -413,17 +151,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -491,13 +220,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -507,33 +229,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -555,58 +257,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -614,13 +268,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -628,15 +275,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -648,12 +289,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -661,18 +296,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -693,33 +318,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -769,13 +376,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -824,15 +424,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -842,9 +436,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -862,26 +453,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -889,14 +464,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -912,12 +481,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -930,22 +493,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -956,9 +503,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -969,22 +513,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -995,22 +523,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1039,12 +551,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1052,22 +558,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -1107,93 +600,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -1203,112 +617,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -1323,16 +655,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -1353,22 +675,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -1376,66 +682,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -1450,18 +700,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1489,17 +729,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1512,17 +743,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1590,13 +812,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -1606,33 +821,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -1643,69 +838,21 @@ "query_time_in_millis": { "type": "long" }, - "query_total": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "interval_ms": { - "type": "long" - }, - "job_stats": { - "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" + "query_total": { + "type": "long" + } + } + } + } } } - }, + } + } + }, + "job_stats": { + "properties": { "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -1713,13 +860,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -1727,15 +867,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -1747,12 +881,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -1760,18 +888,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1792,33 +910,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1868,13 +968,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -1923,15 +1016,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -1941,9 +1028,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1961,26 +1045,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -1988,14 +1056,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -2011,12 +1073,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -2029,22 +1085,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2055,9 +1095,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2068,22 +1105,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2094,22 +1115,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2138,12 +1143,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -2151,22 +1150,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -2208,63 +1194,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "kibana_stats": { "properties": { - "cloud": { - "properties": { - "id": { - "type": "keyword" - }, - "metadata": { - "type": "object" - }, - "name": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "zone": { - "type": "keyword" - } - } - }, "concurrent_connections": { "type": "long" }, "kibana": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -2287,22 +1226,6 @@ "type": "half_float" } } - }, - "memory": { - "properties": { - "free_in_bytes": { - "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -2317,12 +1240,6 @@ "properties": { "size_limit": { "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" } } }, @@ -2330,9 +1247,6 @@ "type": "float" } } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -2341,9 +1255,6 @@ "disconnects": { "type": "long" }, - "status_codes": { - "type": "object" - }, "total": { "type": "long" } @@ -2359,49 +1270,15 @@ } } }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - } - } - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "timestamp": { - "format": "date_time", "type": "date" }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } }, @@ -15291,4 +14168,4 @@ } } } -} +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_exclusive_mb/mappings.json b/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_exclusive_mb/mappings.json index ea61a4b7f325f..211e253ef83d4 100644 --- a/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_exclusive_mb/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_exclusive_mb/mappings.json @@ -9,105 +9,6 @@ "properties": { "beats_state": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, "timestamp": { "format": "date_time", "type": "date" @@ -118,12 +19,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -139,146 +34,19 @@ "properties": { "apm-server": { "properties": { - "decoder": { - "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "missing-content-length": { - "properties": { - "count": { - "type": "long" - } - } - }, - "reader": { - "properties": { - "count": { - "type": "long" - }, - "size": { - "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - }, "processor": { "properties": { "error": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "errors": { - "type": "long" - }, - "frames": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, "metric": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, @@ -291,40 +59,8 @@ }, "transaction": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "frames": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "transactions": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } } @@ -332,17 +68,6 @@ }, "server": { "properties": { - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "request": { "properties": { "count": { @@ -363,9 +88,6 @@ "concurrency": { "type": "long" }, - "count": { - "type": "long" - }, "decode": { "type": "long" }, @@ -400,9 +122,6 @@ "accepted": { "type": "long" }, - "count": { - "type": "long" - }, "ok": { "type": "long" } @@ -418,65 +137,17 @@ "properties": { "cpu": { "properties": { - "system": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "total": { "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - }, "value": { "type": "long" } } - }, - "user": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } } } }, "handles": { "properties": { - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - }, "open": { "type": "long" } @@ -486,13 +157,6 @@ "properties": { "ephemeral_id": { "type": "keyword" - }, - "uptime": { - "properties": { - "ms": { - "type": "long" - } - } } } }, @@ -516,26 +180,6 @@ }, "libbeat": { "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, "output": { "properties": { "events": { @@ -546,21 +190,12 @@ "active": { "type": "long" }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, - "toomany": { - "type": "long" - }, "total": { "type": "long" } @@ -576,9 +211,6 @@ } } }, - "type": { - "type": "keyword" - }, "write": { "properties": { "bytes": { @@ -593,23 +225,14 @@ }, "pipeline": { "properties": { - "clients": { - "type": "long" - }, "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -620,13 +243,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -644,19 +260,6 @@ }, "5": { "type": "double" - }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "15": { - "type": "double" - }, - "5": { - "type": "double" - } - } } } } @@ -664,9 +267,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -676,25 +276,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -729,93 +320,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -825,112 +337,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -940,17 +370,7 @@ "properties": { "docs": { "properties": { - "count": { - "type": "long" - } - } - }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { + "count": { "type": "long" } } @@ -975,22 +395,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -998,66 +402,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -1072,18 +420,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1111,17 +449,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1134,17 +463,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1212,13 +532,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -1228,33 +541,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -1276,58 +569,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -1335,13 +580,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -1349,15 +587,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -1369,12 +601,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -1382,18 +608,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1414,33 +630,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1490,13 +688,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -1545,15 +736,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -1563,9 +748,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1583,26 +765,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -1610,14 +776,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -1633,12 +793,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -1651,22 +805,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1677,9 +815,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1690,22 +825,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1715,23 +834,7 @@ "type": "integer" }, "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" + "type": "long" } } }, @@ -1760,12 +863,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1773,22 +870,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -1830,63 +914,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "kibana_stats": { "properties": { - "cloud": { - "properties": { - "id": { - "type": "keyword" - }, - "metadata": { - "type": "object" - }, - "name": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "zone": { - "type": "keyword" - } - } - }, "concurrent_connections": { "type": "long" }, "kibana": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -1909,22 +946,6 @@ "type": "half_float" } } - }, - "memory": { - "properties": { - "free_in_bytes": { - "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -1939,12 +960,6 @@ "properties": { "size_limit": { "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" } } }, @@ -1952,9 +967,6 @@ "type": "float" } } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -1963,9 +975,6 @@ "disconnects": { "type": "long" }, - "status_codes": { - "type": "object" - }, "total": { "type": "long" } @@ -1981,49 +990,15 @@ } } }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - } - } - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "timestamp": { - "format": "date_time", "type": "date" }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } }, @@ -2060,79 +1035,27 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "logstash_state": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "pipeline": { "properties": { - "batch_size": { - "type": "integer" - }, - "ephemeral_id": { - "type": "keyword" - }, - "format": { - "type": "keyword" - }, "hash": { "type": "keyword" }, "id": { "type": "keyword" - }, - "representation": { - "enabled": false, - "type": "object" - }, - "version": { - "type": "keyword" - }, - "workers": { - "type": "short" } } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "logstash_stats": { "properties": { - "batch_size": { - "type": "integer" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, "in": { "type": "long" }, @@ -2143,34 +1066,6 @@ }, "jvm": { "properties": { - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -2178,9 +1073,6 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } }, @@ -2191,34 +1083,6 @@ }, "logstash": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "batch_size": { - "type": "long" - }, - "workers": { - "type": "short" - } - } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -2233,9 +1097,6 @@ "properties": { "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -2253,9 +1114,6 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } @@ -2284,25 +1142,13 @@ }, "pipelines": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, - "in": { - "type": "long" - }, "out": { "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" } } }, @@ -2314,9 +1160,6 @@ }, "queue": { "properties": { - "events_count": { - "type": "long" - }, "max_queue_size_in_bytes": { "type": "long" }, @@ -2328,29 +1171,8 @@ } } }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, "vertices": { "properties": { - "double_gauges": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - }, - "type": "nested" - }, "duration_in_millis": { "type": "long" }, @@ -2363,22 +1185,11 @@ "id": { "type": "keyword" }, - "long_counters": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - }, - "type": "nested" - }, "pipeline_ephemeral_id": { "type": "keyword" }, "queue_push_duration_in_millis": { - "type": "long" + "type": "float" } }, "type": "nested" @@ -2394,12 +1205,6 @@ "type": "long" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -2407,50 +1212,24 @@ "properties": { "events_count": { "type": "long" - }, - "type": { - "type": "keyword" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" } } }, "timestamp": { "type": "date" - }, - "workers": { - "type": "short" } } }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_mb/mappings.json b/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_mb/mappings.json index ea9313fb87b6e..f8c55c60c4984 100644 --- a/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_mb/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/setup/collection/es_and_kibana_mb/mappings.json @@ -9,105 +9,6 @@ "properties": { "beats_state": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, "timestamp": { "format": "date_time", "type": "date" @@ -118,12 +19,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -139,146 +34,19 @@ "properties": { "apm-server": { "properties": { - "decoder": { - "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "missing-content-length": { - "properties": { - "count": { - "type": "long" - } - } - }, - "reader": { - "properties": { - "count": { - "type": "long" - }, - "size": { - "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - }, "processor": { "properties": { "error": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "errors": { - "type": "long" - }, - "frames": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, "metric": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, @@ -291,40 +59,8 @@ }, "transaction": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "frames": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "transactions": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } } @@ -332,17 +68,6 @@ }, "server": { "properties": { - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "request": { "properties": { "count": { @@ -363,9 +88,6 @@ "concurrency": { "type": "long" }, - "count": { - "type": "long" - }, "decode": { "type": "long" }, @@ -400,9 +122,6 @@ "accepted": { "type": "long" }, - "count": { - "type": "long" - }, "ok": { "type": "long" } @@ -418,65 +137,17 @@ "properties": { "cpu": { "properties": { - "system": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "total": { "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - }, "value": { "type": "long" } } - }, - "user": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } } } }, "handles": { "properties": { - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - }, "open": { "type": "long" } @@ -486,13 +157,6 @@ "properties": { "ephemeral_id": { "type": "keyword" - }, - "uptime": { - "properties": { - "ms": { - "type": "long" - } - } } } }, @@ -516,26 +180,6 @@ }, "libbeat": { "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, "output": { "properties": { "events": { @@ -546,21 +190,12 @@ "active": { "type": "long" }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, - "toomany": { - "type": "long" - }, "total": { "type": "long" } @@ -576,9 +211,6 @@ } } }, - "type": { - "type": "keyword" - }, "write": { "properties": { "bytes": { @@ -593,23 +225,14 @@ }, "pipeline": { "properties": { - "clients": { - "type": "long" - }, "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -620,13 +243,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -644,19 +260,6 @@ }, "5": { "type": "double" - }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "15": { - "type": "double" - }, - "5": { - "type": "double" - } - } } } } @@ -664,9 +267,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -676,25 +276,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -729,93 +320,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -825,132 +337,40 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { "type": "keyword" }, "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "fielddata": { + "properties": { + "docs": { "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { + "count": { "type": "long" } } @@ -975,22 +395,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -998,66 +402,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -1072,18 +420,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1111,17 +449,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1134,17 +463,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1212,13 +532,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -1228,33 +541,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -1276,58 +569,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -1335,13 +580,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -1349,15 +587,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -1369,12 +601,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -1382,18 +608,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1414,33 +630,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1490,13 +688,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -1545,15 +736,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -1563,9 +748,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1583,26 +765,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -1610,14 +776,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -1630,108 +790,51 @@ "cpu": { "properties": { "percent": { - "type": "half_float" - } - } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "get": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" + "type": "half_float" } } - }, - "index": { + } + } + }, + "thread_pool": { + "properties": { + "bulk": { "properties": { "queue": { "type": "integer" }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, - "management": { + "get": { "properties": { "queue": { "type": "integer" }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, - "search": { + "index": { "properties": { "queue": { "type": "integer" }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, - "watcher": { + "search": { "properties": { "queue": { "type": "integer" }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1760,12 +863,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1773,22 +870,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -1828,93 +912,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -1924,112 +929,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -2044,16 +967,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -2074,22 +987,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -2097,66 +994,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -2171,18 +1012,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -2210,17 +1041,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -2233,17 +1055,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -2308,32 +1121,15 @@ "indices_stats": { "properties": { "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_time_in_millis": { - "type": "long" - }, - "index_total": { - "type": "long" - } - } - }, - "search": { + "properties": { + "primaries": { + "properties": { + "indexing": { "properties": { - "query_time_in_millis": { + "index_time_in_millis": { "type": "long" }, - "query_total": { + "index_total": { "type": "long" } } @@ -2342,18 +1138,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -2375,58 +1161,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -2434,13 +1172,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -2448,15 +1179,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -2468,12 +1193,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -2481,18 +1200,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -2513,33 +1222,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -2589,13 +1280,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -2644,15 +1328,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -2662,9 +1340,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -2682,26 +1357,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -2709,14 +1368,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -2732,12 +1385,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -2750,22 +1397,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2776,9 +1407,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2789,22 +1417,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2815,22 +1427,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2859,12 +1455,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -2872,22 +1462,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -2929,63 +1506,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "kibana_stats": { "properties": { - "cloud": { - "properties": { - "id": { - "type": "keyword" - }, - "metadata": { - "type": "object" - }, - "name": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "zone": { - "type": "keyword" - } - } - }, "concurrent_connections": { "type": "long" }, "kibana": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -3008,22 +1538,6 @@ "type": "half_float" } } - }, - "memory": { - "properties": { - "free_in_bytes": { - "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -3038,12 +1552,6 @@ "properties": { "size_limit": { "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" } } }, @@ -3051,9 +1559,6 @@ "type": "float" } } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -3062,9 +1567,6 @@ "disconnects": { "type": "long" }, - "status_codes": { - "type": "object" - }, "total": { "type": "long" } @@ -3074,55 +1576,21 @@ "properties": { "average": { "type": "float" - }, - "max": { - "type": "float" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - } - } - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" + }, + "max": { + "type": "float" + } + } }, "timestamp": { - "format": "date_time", "type": "date" }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } }, @@ -3159,63 +1627,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "kibana_stats": { "properties": { - "cloud": { - "properties": { - "id": { - "type": "keyword" - }, - "metadata": { - "type": "object" - }, - "name": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "zone": { - "type": "keyword" - } - } - }, "concurrent_connections": { "type": "long" }, "kibana": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -3238,22 +1659,6 @@ "type": "half_float" } } - }, - "memory": { - "properties": { - "free_in_bytes": { - "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -3268,12 +1673,6 @@ "properties": { "size_limit": { "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" } } }, @@ -3281,9 +1680,6 @@ "type": "float" } } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -3292,9 +1688,6 @@ "disconnects": { "type": "long" }, - "status_codes": { - "type": "object" - }, "total": { "type": "long" } @@ -3310,49 +1703,15 @@ } } }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - } - } - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "timestamp": { - "format": "date_time", "type": "date" }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } }, @@ -3389,79 +1748,27 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "logstash_state": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "pipeline": { "properties": { - "batch_size": { - "type": "integer" - }, - "ephemeral_id": { - "type": "keyword" - }, - "format": { - "type": "keyword" - }, "hash": { "type": "keyword" }, "id": { "type": "keyword" - }, - "representation": { - "enabled": false, - "type": "object" - }, - "version": { - "type": "keyword" - }, - "workers": { - "type": "short" } } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "logstash_stats": { "properties": { - "batch_size": { - "type": "integer" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, "in": { "type": "long" }, @@ -3472,34 +1779,6 @@ }, "jvm": { "properties": { - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -3507,9 +1786,6 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } }, @@ -3520,34 +1796,6 @@ }, "logstash": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "batch_size": { - "type": "long" - }, - "workers": { - "type": "short" - } - } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -3562,9 +1810,6 @@ "properties": { "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -3582,9 +1827,6 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } @@ -3613,25 +1855,13 @@ }, "pipelines": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, - "in": { - "type": "long" - }, "out": { "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" } } }, @@ -3643,9 +1873,6 @@ }, "queue": { "properties": { - "events_count": { - "type": "long" - }, "max_queue_size_in_bytes": { "type": "long" }, @@ -3657,29 +1884,8 @@ } } }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, "vertices": { "properties": { - "double_gauges": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - }, - "type": "nested" - }, "duration_in_millis": { "type": "long" }, @@ -3692,22 +1898,11 @@ "id": { "type": "keyword" }, - "long_counters": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - }, - "type": "nested" - }, "pipeline_ephemeral_id": { "type": "keyword" }, "queue_push_duration_in_millis": { - "type": "long" + "type": "float" } }, "type": "nested" @@ -3723,12 +1918,6 @@ "type": "long" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -3736,50 +1925,24 @@ "properties": { "events_count": { "type": "long" - }, - "type": { - "type": "keyword" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" } } }, "timestamp": { "type": "date" - }, - "workers": { - "type": "short" } } }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_exclusive_mb/mappings.json b/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_exclusive_mb/mappings.json index a3ddceca893c0..4f388b8834b7b 100644 --- a/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_exclusive_mb/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_exclusive_mb/mappings.json @@ -9,105 +9,6 @@ "properties": { "beats_state": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, "timestamp": { "format": "date_time", "type": "date" @@ -118,12 +19,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -139,146 +34,19 @@ "properties": { "apm-server": { "properties": { - "decoder": { - "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "missing-content-length": { - "properties": { - "count": { - "type": "long" - } - } - }, - "reader": { - "properties": { - "count": { - "type": "long" - }, - "size": { - "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - }, "processor": { "properties": { "error": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "errors": { - "type": "long" - }, - "frames": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, "metric": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, @@ -291,40 +59,8 @@ }, "transaction": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "frames": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "transactions": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } } @@ -332,17 +68,6 @@ }, "server": { "properties": { - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "request": { "properties": { "count": { @@ -363,9 +88,6 @@ "concurrency": { "type": "long" }, - "count": { - "type": "long" - }, "decode": { "type": "long" }, @@ -400,9 +122,6 @@ "accepted": { "type": "long" }, - "count": { - "type": "long" - }, "ok": { "type": "long" } @@ -418,65 +137,17 @@ "properties": { "cpu": { "properties": { - "system": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "total": { "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - }, "value": { "type": "long" } } - }, - "user": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } } } }, "handles": { "properties": { - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - }, "open": { "type": "long" } @@ -486,13 +157,6 @@ "properties": { "ephemeral_id": { "type": "keyword" - }, - "uptime": { - "properties": { - "ms": { - "type": "long" - } - } } } }, @@ -516,26 +180,6 @@ }, "libbeat": { "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, "output": { "properties": { "events": { @@ -546,21 +190,12 @@ "active": { "type": "long" }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, - "toomany": { - "type": "long" - }, "total": { "type": "long" } @@ -576,9 +211,6 @@ } } }, - "type": { - "type": "keyword" - }, "write": { "properties": { "bytes": { @@ -593,23 +225,14 @@ }, "pipeline": { "properties": { - "clients": { - "type": "long" - }, "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -620,13 +243,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -644,19 +260,6 @@ }, "5": { "type": "double" - }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "15": { - "type": "double" - }, - "5": { - "type": "double" - } - } } } } @@ -664,9 +267,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -676,25 +276,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -714,108 +305,29 @@ "format": "7", "number_of_replicas": "0", "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - }, - "index": ".monitoring-es-7-2019.04.09", - "mappings": { - "date_detection": false, - "dynamic": "false", - "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": ".monitoring-es-7-2019.04.09", + "mappings": { + "date_detection": false, + "dynamic": "false", + "properties": { "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -825,112 +337,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -945,16 +375,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -975,22 +395,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -998,66 +402,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -1072,18 +420,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1111,17 +449,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1134,17 +463,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1212,13 +532,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -1228,105 +541,37 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "interval_ms": { - "type": "long" - }, - "job_stats": { - "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, - "job_id": { - "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } } } - }, - "state": { + } + } + }, + "job_stats": { + "properties": { + "job_id": { "type": "keyword" } } @@ -1335,13 +580,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -1349,15 +587,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -1369,12 +601,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -1382,18 +608,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1414,33 +630,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1490,13 +688,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -1545,15 +736,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -1563,9 +748,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1583,26 +765,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -1610,14 +776,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -1633,12 +793,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -1651,22 +805,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1677,9 +815,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1690,22 +825,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1716,22 +835,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1760,12 +863,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1773,22 +870,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -1828,93 +912,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -1924,112 +929,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -2039,17 +962,7 @@ "properties": { "docs": { "properties": { - "count": { - "type": "long" - } - } - }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { + "count": { "type": "long" } } @@ -2074,22 +987,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -2097,66 +994,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -2171,18 +1012,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -2210,17 +1041,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -2233,17 +1055,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -2311,13 +1124,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -2327,33 +1133,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -2375,58 +1161,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -2434,13 +1172,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -2448,15 +1179,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -2468,12 +1193,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -2481,18 +1200,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -2513,33 +1222,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -2589,13 +1280,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -2644,15 +1328,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -2662,9 +1340,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -2682,26 +1357,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -2709,14 +1368,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -2732,12 +1385,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -2750,22 +1397,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2776,9 +1407,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2789,22 +1417,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -2814,23 +1426,7 @@ "type": "integer" }, "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" + "type": "long" } } }, @@ -2859,12 +1455,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -2872,22 +1462,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -2929,63 +1506,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "kibana_stats": { "properties": { - "cloud": { - "properties": { - "id": { - "type": "keyword" - }, - "metadata": { - "type": "object" - }, - "name": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "zone": { - "type": "keyword" - } - } - }, "concurrent_connections": { "type": "long" }, "kibana": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -3008,22 +1538,6 @@ "type": "half_float" } } - }, - "memory": { - "properties": { - "free_in_bytes": { - "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -3038,12 +1552,6 @@ "properties": { "size_limit": { "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" } } }, @@ -3051,9 +1559,6 @@ "type": "float" } } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -3062,9 +1567,6 @@ "disconnects": { "type": "long" }, - "status_codes": { - "type": "object" - }, "total": { "type": "long" } @@ -3080,49 +1582,15 @@ } } }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - } - } - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "timestamp": { - "format": "date_time", "type": "date" }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } }, @@ -3159,79 +1627,27 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "logstash_state": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "pipeline": { "properties": { - "batch_size": { - "type": "integer" - }, - "ephemeral_id": { - "type": "keyword" - }, - "format": { - "type": "keyword" - }, "hash": { "type": "keyword" }, "id": { "type": "keyword" - }, - "representation": { - "enabled": false, - "type": "object" - }, - "version": { - "type": "keyword" - }, - "workers": { - "type": "short" } } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "logstash_stats": { "properties": { - "batch_size": { - "type": "integer" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, "in": { "type": "long" }, @@ -3242,34 +1658,6 @@ }, "jvm": { "properties": { - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -3277,9 +1665,6 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } }, @@ -3290,34 +1675,6 @@ }, "logstash": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "batch_size": { - "type": "long" - }, - "workers": { - "type": "short" - } - } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -3332,9 +1689,6 @@ "properties": { "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -3352,9 +1706,6 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } @@ -3383,25 +1734,13 @@ }, "pipelines": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, - "in": { - "type": "long" - }, "out": { "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" } } }, @@ -3413,9 +1752,6 @@ }, "queue": { "properties": { - "events_count": { - "type": "long" - }, "max_queue_size_in_bytes": { "type": "long" }, @@ -3427,29 +1763,8 @@ } } }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, "vertices": { "properties": { - "double_gauges": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - }, - "type": "nested" - }, "duration_in_millis": { "type": "long" }, @@ -3462,22 +1777,11 @@ "id": { "type": "keyword" }, - "long_counters": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - }, - "type": "nested" - }, "pipeline_ephemeral_id": { "type": "keyword" }, "queue_push_duration_in_millis": { - "type": "long" + "type": "float" } }, "type": "nested" @@ -3493,12 +1797,6 @@ "type": "long" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -3506,50 +1804,24 @@ "properties": { "events_count": { "type": "long" - }, - "type": { - "type": "keyword" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" } } }, "timestamp": { "type": "date" - }, - "workers": { - "type": "short" } } }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_mb/mappings.json b/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_mb/mappings.json index adbd44d7281b6..03f954f92dd33 100644 --- a/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_mb/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/setup/collection/kibana_mb/mappings.json @@ -9,105 +9,6 @@ "properties": { "beats_state": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, "timestamp": { "format": "date_time", "type": "date" @@ -118,12 +19,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -139,146 +34,19 @@ "properties": { "apm-server": { "properties": { - "decoder": { - "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "missing-content-length": { - "properties": { - "count": { - "type": "long" - } - } - }, - "reader": { - "properties": { - "count": { - "type": "long" - }, - "size": { - "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - }, "processor": { "properties": { "error": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "errors": { - "type": "long" - }, - "frames": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, "metric": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } }, @@ -291,40 +59,8 @@ }, "transaction": { "properties": { - "decoding": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "frames": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "transactions": { - "type": "long" - }, "transformations": { "type": "long" - }, - "validation": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "type": "long" - } - } } } } @@ -332,17 +68,6 @@ }, "server": { "properties": { - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "request": { "properties": { "count": { @@ -363,9 +88,6 @@ "concurrency": { "type": "long" }, - "count": { - "type": "long" - }, "decode": { "type": "long" }, @@ -400,9 +122,6 @@ "accepted": { "type": "long" }, - "count": { - "type": "long" - }, "ok": { "type": "long" } @@ -418,65 +137,17 @@ "properties": { "cpu": { "properties": { - "system": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "total": { "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - }, "value": { "type": "long" } } - }, - "user": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } } } }, "handles": { "properties": { - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - }, "open": { "type": "long" } @@ -486,13 +157,6 @@ "properties": { "ephemeral_id": { "type": "keyword" - }, - "uptime": { - "properties": { - "ms": { - "type": "long" - } - } } } }, @@ -516,26 +180,6 @@ }, "libbeat": { "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, "output": { "properties": { "events": { @@ -546,21 +190,12 @@ "active": { "type": "long" }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, - "toomany": { - "type": "long" - }, "total": { "type": "long" } @@ -576,9 +211,6 @@ } } }, - "type": { - "type": "keyword" - }, "write": { "properties": { "bytes": { @@ -592,24 +224,15 @@ } }, "pipeline": { - "properties": { - "clients": { - "type": "long" - }, + "properties": { "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -620,13 +243,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -644,19 +260,6 @@ }, "5": { "type": "double" - }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "15": { - "type": "double" - }, - "5": { - "type": "double" - } - } } } } @@ -664,9 +267,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -676,25 +276,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -729,93 +320,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -825,112 +337,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -945,16 +375,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -975,22 +395,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -998,66 +402,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -1072,18 +420,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1111,17 +449,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1134,17 +463,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1209,32 +529,15 @@ "indices_stats": { "properties": { "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_time_in_millis": { - "type": "long" - }, - "index_total": { - "type": "long" - } - } - }, - "search": { + "properties": { + "primaries": { + "properties": { + "indexing": { "properties": { - "query_time_in_millis": { + "index_time_in_millis": { "type": "long" }, - "query_total": { + "index_total": { "type": "long" } } @@ -1243,18 +546,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -1276,58 +569,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -1335,13 +580,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -1349,15 +587,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -1369,12 +601,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -1382,18 +608,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -1414,33 +630,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1490,13 +688,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -1545,15 +736,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -1563,9 +748,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1583,26 +765,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -1610,14 +776,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -1633,12 +793,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -1651,22 +805,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1677,9 +815,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1690,22 +825,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1716,22 +835,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1760,12 +863,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1773,22 +870,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } @@ -1830,63 +914,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "kibana_stats": { "properties": { - "cloud": { - "properties": { - "id": { - "type": "keyword" - }, - "metadata": { - "type": "object" - }, - "name": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "zone": { - "type": "keyword" - } - } - }, "concurrent_connections": { "type": "long" }, "kibana": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -1909,22 +946,6 @@ "type": "half_float" } } - }, - "memory": { - "properties": { - "free_in_bytes": { - "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -1939,12 +960,6 @@ "properties": { "size_limit": { "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" } } }, @@ -1952,9 +967,6 @@ "type": "float" } } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -1963,9 +975,6 @@ "disconnects": { "type": "long" }, - "status_codes": { - "type": "object" - }, "total": { "type": "long" } @@ -1975,55 +984,21 @@ "properties": { "average": { "type": "float" - }, - "max": { - "type": "float" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - } - } - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" + }, + "max": { + "type": "float" + } + } }, "timestamp": { - "format": "date_time", "type": "date" }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } }, @@ -2060,63 +1035,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "kibana_stats": { "properties": { - "cloud": { - "properties": { - "id": { - "type": "keyword" - }, - "metadata": { - "type": "object" - }, - "name": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "zone": { - "type": "keyword" - } - } - }, "concurrent_connections": { "type": "long" }, "kibana": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -2139,22 +1067,6 @@ "type": "half_float" } } - }, - "memory": { - "properties": { - "free_in_bytes": { - "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -2169,12 +1081,6 @@ "properties": { "size_limit": { "type": "float" - }, - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" } } }, @@ -2182,9 +1088,6 @@ "type": "float" } } - }, - "uptime_in_millis": { - "type": "long" } } }, @@ -2193,9 +1096,6 @@ "disconnects": { "type": "long" }, - "status_codes": { - "type": "object" - }, "total": { "type": "long" } @@ -2211,49 +1111,15 @@ } } }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - } - } - }, - "source_node": { - "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "timestamp": { - "format": "date_time", "type": "date" }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } }, @@ -2290,79 +1156,27 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, "logstash_state": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "pipeline": { "properties": { - "batch_size": { - "type": "integer" - }, - "ephemeral_id": { - "type": "keyword" - }, - "format": { - "type": "keyword" - }, "hash": { "type": "keyword" }, "id": { "type": "keyword" - }, - "representation": { - "enabled": false, - "type": "object" - }, - "version": { - "type": "keyword" - }, - "workers": { - "type": "short" } } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "logstash_stats": { "properties": { - "batch_size": { - "type": "integer" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, "in": { "type": "long" }, @@ -2373,34 +1187,6 @@ }, "jvm": { "properties": { - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -2408,9 +1194,6 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } }, @@ -2421,34 +1204,6 @@ }, "logstash": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "batch_size": { - "type": "long" - }, - "workers": { - "type": "short" - } - } - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, "uuid": { "type": "keyword" }, @@ -2463,9 +1218,6 @@ "properties": { "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -2483,9 +1235,6 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } @@ -2514,25 +1263,13 @@ }, "pipelines": { "properties": { - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { "duration_in_millis": { "type": "long" }, - "filtered": { - "type": "long" - }, - "in": { - "type": "long" - }, "out": { "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" } } }, @@ -2544,9 +1281,6 @@ }, "queue": { "properties": { - "events_count": { - "type": "long" - }, "max_queue_size_in_bytes": { "type": "long" }, @@ -2558,29 +1292,8 @@ } } }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, "vertices": { "properties": { - "double_gauges": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - }, - "type": "nested" - }, "duration_in_millis": { "type": "long" }, @@ -2593,22 +1306,11 @@ "id": { "type": "keyword" }, - "long_counters": { - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - }, - "type": "nested" - }, "pipeline_ephemeral_id": { "type": "keyword" }, "queue_push_duration_in_millis": { - "type": "long" + "type": "float" } }, "type": "nested" @@ -2624,12 +1326,6 @@ "type": "long" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -2637,50 +1333,24 @@ "properties": { "events_count": { "type": "long" - }, - "type": { - "type": "keyword" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" } } }, "timestamp": { "type": "date" - }, - "workers": { - "type": "short" } } }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-basic-beats/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-basic-beats/mappings.json index 212391b038477..ba08a3d9f2460 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-basic-beats/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-basic-beats/mappings.json @@ -7,105 +7,6 @@ "properties": { "beats_state": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, "timestamp": { "format": "date_time", "type": "date" @@ -116,12 +17,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -135,108 +30,40 @@ }, "metrics": { "properties": { - "beat": { + "apm-server": { "properties": { - "cpu": { + "processor": { "properties": { - "system": { + "error": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "total": { + "metric": { "properties": { - "value": { - "type": "long" - }, - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "user": { + "span": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } - } - } - }, - "info": { - "properties": { - "ephemeral_id": { - "type": "keyword" }, - "uptime": { + "transaction": { "properties": { - "ms": { + "transformations": { "type": "long" } } } } }, - "memstats": { - "properties": { - "gc_next": { - "type": "long" - }, - "memory_alloc": { - "type": "long" - }, - "memory_total": { - "type": "long" - }, - "rss": { - "type": "long" - } - } - }, - "handles": { - "properties": { - "open": { - "type": "long" - }, - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - } - } - } - } - }, - "apm-server": { - "properties": { "server": { "properties": { "request": { @@ -246,17 +73,6 @@ } } }, - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "response": { "properties": { "count": { @@ -264,53 +80,47 @@ }, "errors": { "properties": { - "count": { - "type": "long" - }, - "toolarge": { + "closed": { "type": "long" }, - "validate": { + "concurrency": { "type": "long" }, - "ratelimit": { + "decode": { "type": "long" }, - "queue": { + "forbidden": { "type": "long" }, - "closed": { + "internal": { "type": "long" }, - "forbidden": { + "method": { "type": "long" }, - "concurrency": { + "queue": { "type": "long" }, - "unauthorized": { + "ratelimit": { "type": "long" }, - "internal": { + "toolarge": { "type": "long" }, - "decode": { + "unauthorized": { "type": "long" }, - "method": { + "validate": { "type": "long" } } }, "valid": { "properties": { - "ok": { - "type": "long" - }, "accepted": { "type": "long" }, - "count": { + "ok": { "type": "long" } } @@ -318,296 +128,109 @@ } } } - }, - "decoder": { + } + } + }, + "beat": { + "properties": { + "cpu": { "properties": { - "deflate": { + "total": { "properties": { - "content-length": { - "type": "long" - }, - "count": { + "value": { "type": "long" } } + } + } + }, + "handles": { + "properties": { + "open": { + "type": "long" + } + } + }, + "info": { + "properties": { + "ephemeral_id": { + "type": "keyword" + } + } + }, + "memstats": { + "properties": { + "gc_next": { + "type": "long" + }, + "memory_alloc": { + "type": "long" + }, + "memory_total": { + "type": "long" }, - "gzip": { + "rss": { + "type": "long" + } + } + } + } + }, + "libbeat": { + "properties": { + "output": { + "properties": { + "events": { "properties": { - "content-length": { + "acked": { "type": "long" }, - "count": { + "active": { "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { + }, + "dropped": { "type": "long" }, - "count": { + "failed": { + "type": "long" + }, + "total": { "type": "long" } } }, - "reader": { + "read": { "properties": { - "size": { + "bytes": { "type": "long" }, - "count": { + "errors": { "type": "long" } } }, - "missing-content-length": { + "write": { "properties": { - "count": { + "bytes": { + "type": "long" + }, + "errors": { "type": "long" } } } } }, - "processor": { - "properties": { - "metric": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - }, - "transaction": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "transactions": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "error": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "errors": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "span": { - "properties": { - "transformations": { - "type": "long" - } - } - } - } - } - } - }, - "libbeat": { - "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, - "output": { + "pipeline": { "properties": { "events": { "properties": { - "acked": { - "type": "long" - }, - "active": { - "type": "long" - }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, - "total": { - "type": "long" - }, - "toomany": { - "type": "long" - } - } - }, - "read": { - "properties": { - "bytes": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "write": { - "properties": { - "bytes": { - "type": "long" - }, - "errors": { - "type": "long" - } - } - } - } - }, - "pipeline": { - "properties": { - "clients": { - "type": "long" - }, - "events": { - "properties": { - "active": { - "type": "long" - }, - "dropped": { - "type": "long" - }, - "failed": { - "type": "long" - }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -618,13 +241,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -637,24 +253,11 @@ "1": { "type": "double" }, - "5": { - "type": "double" - }, "15": { "type": "double" }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "5": { - "type": "double" - }, - "15": { - "type": "double" - } - } + "5": { + "type": "double" } } } @@ -662,9 +265,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -674,25 +274,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -727,95 +318,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -823,86 +340,40 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { - "memory": { - "properties": { - "heap": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, - "size_limit": { - "type": "float" - } - } - }, - "resident_set_size_in_bytes": { - "type": "float" - } - } - }, "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } + "type": "float" }, - "https": { + "memory": { "properties": { - "total": { - "type": "long" + "heap": { + "properties": { + "size_limit": { + "type": "float" + } + } + }, + "resident_set_size_in_bytes": { + "type": "float" } } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -910,9 +381,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -926,10 +394,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -953,115 +435,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -1076,29 +490,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -1113,44 +510,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -1159,42 +521,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -1203,36 +535,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -1251,14 +566,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -1267,24 +580,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -1294,41 +598,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -1337,68 +641,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -1419,15 +747,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1435,15 +754,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1462,116 +772,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1587,16 +878,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -1608,12 +893,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -1623,12 +902,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -1638,71 +911,10 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, "thread_pool": { "properties": { "bulk": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1713,9 +925,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1726,22 +935,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1752,22 +945,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1790,246 +967,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-green-gold/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-green-gold/mappings.json index bd411ee5178e2..702f8731a31ca 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-green-gold/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-green-gold/mappings.json @@ -1,7 +1,8 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-kibana-6-2017.08.23", "mappings": { "dynamic": false, @@ -9,95 +10,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -105,47 +32,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -155,36 +63,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -192,9 +73,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -208,10 +86,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -230,29 +122,15 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-alerts-6", "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -274,6 +152,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, @@ -292,7 +185,8 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-logstash-6-2017.08.23", "mappings": { "dynamic": false, @@ -300,83 +194,25 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, - "logstash_stats": { - "type": "object", + "logstash_state": { "properties": { - "logstash": { + "pipeline": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "version": { + "hash": { "type": "keyword" }, - "snapshot": { - "type": "boolean" - }, - "status": { + "id": { "type": "keyword" - }, - "pipeline": { - "properties": { - "workers": { - "type": "short" - }, - "batch_size": { - "type": "long" - } - } } } - }, + } + } + }, + "logstash_stats": { + "properties": { "events": { "properties": { - "filtered": { + "duration_in_millis": { "type": "long" }, "in": { @@ -384,210 +220,120 @@ }, "out": { "type": "long" - }, - "duration_in_millis": { - "type": "long" } } }, - "timestamp": { - "type": "date" - }, "jvm": { "properties": { + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + } + } + }, "uptime_in_millis": { "type": "long" + } + } + }, + "logstash": { + "properties": { + "uuid": { + "type": "keyword" }, - "gc": { + "version": { + "type": "keyword" + } + } + }, + "os": { + "properties": { + "cgroup": { "properties": { - "collectors": { + "cpu": { "properties": { - "old": { + "stat": { "properties": { - "collection_count": { + "number_of_elapsed_periods": { "type": "long" }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { + "number_of_times_throttled": { "type": "long" }, - "collection_time_in_millis": { + "time_throttled_nanos": { "type": "long" } } } } - } - } - }, - "mem": { - "properties": { - "heap_max_in_bytes": { - "type": "long" - }, - "heap_used_in_bytes": { - "type": "long" }, - "heap_used_percent": { - "type": "long" + "cpuacct": { + "properties": { + "usage_nanos": { + "type": "long" + } + } } } - } - } - }, - "os": { - "properties": { + }, "cpu": { "properties": { "load_average": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } } } - }, - "cgroup": { - "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, - "cpu": { - "properties": { - "control_group": { - "type": "keyword" - }, - "stat": { - "properties": { - "number_of_elapsed_periods": { - "type": "long" - }, - "number_of_times_throttled": { - "type": "long" - }, - "time_throttled_nanos": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "process": { - "properties": { - "cpu": { - "properties": { - "percent": { - "type": "long" - } - } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, - "queue": { - "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" } } }, "pipelines": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { - "in": { - "type": "long" - }, - "filtered": { - "type": "long" - }, - "out": { - "type": "long" - }, "duration_in_millis": { "type": "long" }, - "queue_push_duration_in_millis": { + "out": { "type": "long" } } }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, "queue": { "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" - }, "max_queue_size_in_bytes": { "type": "long" }, "queue_size_in_bytes": { "type": "long" + }, + "type": { + "type": "keyword" } } }, "vertices": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "pipeline_ephemeral_id": { - "type": "keyword" + "duration_in_millis": { + "type": "long" }, "events_in": { "type": "long" @@ -595,111 +341,63 @@ "events_out": { "type": "long" }, - "duration_in_millis": { - "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" + "id": { + "type": "keyword" }, - "long_counters": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - } + "pipeline_ephemeral_id": { + "type": "keyword" }, - "double_gauges": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - } + "queue_push_duration_in_millis": { + "type": "float" } - } - }, - "reloads": { + }, + "type": "nested" + } + }, + "type": "nested" + }, + "process": { + "properties": { + "cpu": { "properties": { - "failures": { - "type": "long" - }, - "successes": { + "percent": { "type": "long" } } } } }, - "workers": { - "type": "short" + "queue": { + "properties": { + "events_count": { + "type": "long" + } + } }, - "batch_size": { - "type": "integer" + "timestamp": { + "type": "date" } } }, - "logstash_state": { + "metricset": { "properties": { - "uuid": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "workers": { - "type": "short" - }, - "batch_size": { - "type": "integer" - }, - "format": { - "type": "keyword" - }, - "version": { + "fields": { + "keyword": { + "ignore_above": 256, "type": "keyword" - }, - "representation": { - "enabled": false } - } + }, + "type": "text" } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -718,121 +416,54 @@ { "type": "index", "value": { - "aliases": {}, + "aliases": { + }, "index": ".monitoring-es-6-2017.08.23", "mappings": { "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" + } + } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -847,29 +478,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -884,44 +498,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -930,42 +509,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -974,36 +523,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -1022,14 +554,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -1038,24 +568,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -1065,41 +586,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -1108,68 +629,92 @@ } } }, - "cluster_stats": { - "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" - } - } - }, - "cluster_state": { - "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" - } - } - }, - "node_stats": { + "indices_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "_all": { "properties": { - "docs": { + "primaries": { "properties": { - "count": { - "type": "long" + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "job_stats": { + "properties": { + "job_id": { + "type": "keyword" + } + } + }, + "node_stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -1190,15 +735,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1206,15 +742,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1233,116 +760,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1358,16 +866,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -1379,12 +881,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -1394,12 +890,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -1409,71 +899,10 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, "thread_pool": { "properties": { "bulk": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1484,9 +913,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1497,22 +923,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1523,22 +933,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1561,246 +955,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-green-platinum/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-green-platinum/mappings.json index 0cae1b02ccaca..640b6f07f3470 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-green-platinum/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-green-platinum/mappings.json @@ -8,95 +8,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -104,47 +30,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -154,36 +61,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -191,9 +71,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -207,10 +84,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -235,105 +126,6 @@ "properties": { "beats_state": { "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "state": { - "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, "timestamp": { "format": "date_time", "type": "date" @@ -344,12 +136,6 @@ "properties": { "beat": { "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -363,128 +149,49 @@ }, "metrics": { "properties": { - "beat": { + "apm-server": { "properties": { - "cpu": { + "processor": { "properties": { - "system": { + "error": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "total": { + "metric": { "properties": { - "value": { - "type": "long" - }, - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } }, - "user": { + "span": { "properties": { - "ticks": { + "transformations": { "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } } } - } - } - }, - "info": { - "properties": { - "ephemeral_id": { - "type": "keyword" }, - "uptime": { + "transaction": { "properties": { - "ms": { + "transformations": { "type": "long" } } } } }, - "memstats": { - "properties": { - "gc_next": { - "type": "long" - }, - "memory_alloc": { - "type": "long" - }, - "memory_total": { - "type": "long" - }, - "rss": { - "type": "long" - } - } - }, - "handles": { + "server": { "properties": { - "open": { - "type": "long" - }, - "limit": { + "request": { "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - } - } - } - } - }, - "apm-server": { - "properties": { - "server": { - "properties": { - "request": { - "properties": { - "count": { + "count": { "type": "long" } } }, - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, "response": { "properties": { "count": { @@ -492,53 +199,47 @@ }, "errors": { "properties": { - "count": { - "type": "long" - }, - "toolarge": { + "closed": { "type": "long" }, - "validate": { + "concurrency": { "type": "long" }, - "ratelimit": { + "decode": { "type": "long" }, - "queue": { + "forbidden": { "type": "long" }, - "closed": { + "internal": { "type": "long" }, - "forbidden": { + "method": { "type": "long" }, - "concurrency": { + "queue": { "type": "long" }, - "unauthorized": { + "ratelimit": { "type": "long" }, - "internal": { + "toolarge": { "type": "long" }, - "decode": { + "unauthorized": { "type": "long" }, - "method": { + "validate": { "type": "long" } } }, "valid": { "properties": { - "ok": { - "type": "long" - }, "accepted": { "type": "long" }, - "count": { + "ok": { "type": "long" } } @@ -546,195 +247,49 @@ } } } - }, - "decoder": { + } + } + }, + "beat": { + "properties": { + "cpu": { "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "uncompressed": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "reader": { - "properties": { - "size": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "missing-content-length": { + "total": { "properties": { - "count": { + "value": { "type": "long" } } } } }, - "processor": { + "handles": { "properties": { - "metric": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "counter": { - "type": "long" - }, - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } + "open": { + "type": "long" + } + } + }, + "info": { + "properties": { + "ephemeral_id": { + "type": "keyword" + } + } + }, + "memstats": { + "properties": { + "gc_next": { + "type": "long" }, - "transaction": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "transactions": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } + "memory_alloc": { + "type": "long" }, - "error": { - "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "errors": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } + "memory_total": { + "type": "long" }, - "span": { - "properties": { - "transformations": { - "type": "long" - } - } + "rss": { + "type": "long" } } } @@ -742,26 +297,6 @@ }, "libbeat": { "properties": { - "config": { - "properties": { - "module": { - "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { - "type": "long" - } - } - }, - "reloads": { - "type": "long" - } - } - }, "output": { "properties": { "events": { @@ -772,23 +307,14 @@ "active": { "type": "long" }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, "total": { "type": "long" - }, - "toomany": { - "type": "long" } } }, @@ -802,9 +328,6 @@ } } }, - "type": { - "type": "keyword" - }, "write": { "properties": { "bytes": { @@ -819,23 +342,14 @@ }, "pipeline": { "properties": { - "clients": { - "type": "long" - }, "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -846,13 +360,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -865,24 +372,11 @@ "1": { "type": "double" }, - "5": { - "type": "double" - }, "15": { "type": "double" }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "5": { - "type": "double" - }, - "15": { - "type": "double" - } - } + "5": { + "type": "double" } } } @@ -890,9 +384,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -902,25 +393,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, @@ -953,115 +435,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -1076,29 +490,64 @@ } } }, - "fielddata": { + "indexing": { "properties": { - "memory_size_in_bytes": { + "index_time_in_millis": { "type": "long" }, - "evictions": { + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } }, + "segments": { + "properties": { + "count": { + "type": "integer" + } + } + }, "store": { "properties": { "size_in_bytes": { "type": "long" } } + } + } + }, + "total": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + } + } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -1117,14 +566,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -1133,24 +580,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -1160,176 +598,85 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } } } - }, - "total": { + } + } + }, + "indices_stats": { + "properties": { + "_all": { "properties": { - "docs": { + "primaries": { "properties": { - "count": { - "type": "long" + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } } } }, - "fielddata": { + "total": { "properties": { - "memory_size_in_bytes": { - "type": "long" + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - }, - "throttle_time_in_millis": { - "type": "long" - } - } - }, - "merges": { - "properties": { - "total_size_in_bytes": { - "type": "long" - } - } - }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - }, - "segments": { - "properties": { - "count": { - "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - } - } - }, - "refresh": { - "properties": { - "total_time_in_millis": { - "type": "long" + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } } } } @@ -1337,68 +684,49 @@ } } }, - "cluster_stats": { - "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" - } - } - }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -1419,15 +747,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1435,15 +754,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -1462,116 +772,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -1587,95 +878,21 @@ } } }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } - } - } - }, - "cpu": { - "properties": { - "load_average": { + "cpuacct": { "properties": { - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" + "usage_nanos": { + "type": "long" } - } - } - } - } - } - }, - "process": { - "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, - "cpu": { - "properties": { - "percent": { - "type": "half_float" - } - } - } - } - }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } + } + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "1m": { + "type": "half_float" } } } @@ -1683,26 +900,21 @@ } } }, - "thread_pool": { + "process": { "properties": { - "bulk": { + "cpu": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" + "percent": { + "type": "half_float" } } - }, - "generic": { + } + } + }, + "thread_pool": { + "properties": { + "bulk": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1713,9 +925,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1726,22 +935,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1752,22 +945,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1790,246 +967,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -2052,24 +1024,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -2091,6 +1048,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-green-trial-two-nodes-one-cgrouped/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-green-trial-two-nodes-one-cgrouped/mappings.json index babf4398a8502..6e6847a5fe30b 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-green-trial-two-nodes-one-cgrouped/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-green-trial-two-nodes-one-cgrouped/mappings.json @@ -8,95 +8,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -104,47 +30,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -154,36 +61,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -191,9 +71,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -207,10 +84,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -234,115 +125,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -357,29 +180,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -394,44 +200,9 @@ } } }, - "query_cache": { + "refresh": { "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -440,42 +211,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -484,36 +225,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -532,14 +256,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -548,24 +270,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -575,41 +288,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -618,68 +331,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -700,15 +437,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -716,15 +444,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -743,116 +462,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -868,16 +568,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -889,12 +583,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -904,12 +592,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -919,71 +601,10 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, "thread_pool": { "properties": { "bulk": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -994,9 +615,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1007,22 +625,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1033,22 +635,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1071,246 +657,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-red-platinum/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-red-platinum/mappings.json index b835ca4fdaf65..115d2fff4a4ac 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-red-platinum/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-red-platinum/mappings.json @@ -6,115 +6,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -129,29 +61,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -166,44 +81,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -212,42 +92,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -256,36 +106,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -304,14 +137,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -320,24 +151,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -347,41 +169,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -390,68 +212,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -472,15 +318,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -488,15 +325,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -515,116 +343,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -640,16 +449,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -661,12 +464,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -676,413 +473,106 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { "type": "half_float" } - } - } - } - }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "index": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "search": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "write": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - } - } - } - } - }, - "index_recovery": { - "type": "object" - }, - "shard": { - "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, - "index": { - "type": "keyword" - }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, - "node": { - "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" - }, - "state": { - "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - } - } - }, - "ccr_stats": { - "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { - "type": "keyword" - }, - "follower_index": { - "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" + } + } + } }, - "read_exceptions": { - "type": "nested", + "thread_pool": { "properties": { - "from_seq_no": { - "type": "long" + "bulk": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "retries": { - "type": "integer" + "get": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "exception": { - "type": "object", + "index": { "properties": { - "type": { - "type": "keyword" + "queue": { + "type": "integer" }, - "reason": { - "type": "text" + "rejected": { + "type": "long" + } + } + }, + "search": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" } } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" }, - "reason": { - "type": "text" + "write": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } } } } } }, - "ccr_auto_follow_stats": { + "shard": { "properties": { - "number_of_failed_follow_indices": { - "type": "long" + "index": { + "type": "keyword" }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" + "node": { + "type": "keyword" }, - "number_of_successful_follow_indices": { - "type": "long" + "primary": { + "type": "boolean" }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } + "state": { + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "type": "keyword" }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } + "uuid": { + "type": "keyword" } } + }, + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1107,95 +597,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -1203,47 +619,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -1253,36 +650,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -1290,9 +660,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -1306,10 +673,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1331,24 +712,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -1370,6 +736,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-three-nodes-shard-relocation/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-three-nodes-shard-relocation/mappings.json index d1101b9fb4659..f8edb978c0add 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-three-nodes-shard-relocation/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-three-nodes-shard-relocation/mappings.json @@ -8,95 +8,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -104,47 +30,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -154,36 +61,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -191,9 +71,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -207,10 +84,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -232,24 +123,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -271,6 +147,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, @@ -295,83 +186,25 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, - "logstash_stats": { - "type": "object", + "logstash_state": { "properties": { - "logstash": { + "pipeline": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "version": { + "hash": { "type": "keyword" }, - "snapshot": { - "type": "boolean" - }, - "status": { + "id": { "type": "keyword" - }, - "pipeline": { - "properties": { - "workers": { - "type": "short" - }, - "batch_size": { - "type": "long" - } - } } } - }, + } + } + }, + "logstash_stats": { + "properties": { "events": { "properties": { - "filtered": { + "duration_in_millis": { "type": "long" }, "in": { @@ -379,48 +212,11 @@ }, "out": { "type": "long" - }, - "duration_in_millis": { - "type": "long" } } }, - "timestamp": { - "type": "date" - }, "jvm": { "properties": { - "uptime_in_millis": { - "type": "long" - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, "mem": { "properties": { "heap_max_in_bytes": { @@ -428,50 +224,30 @@ }, "heap_used_in_bytes": { "type": "long" - }, - "heap_used_percent": { - "type": "long" } } - } - } + }, + "uptime_in_millis": { + "type": "long" + } + } }, - "os": { + "logstash": { "properties": { - "cpu": { - "properties": { - "load_average": { - "properties": { - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" - } - } - } - } + "uuid": { + "type": "keyword" }, + "version": { + "type": "keyword" + } + } + }, + "os": { + "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -486,103 +262,70 @@ } } } + }, + "cpuacct": { + "properties": { + "usage_nanos": { + "type": "long" + } + } } } - } - } - }, - "process": { - "properties": { + }, "cpu": { "properties": { - "percent": { - "type": "long" + "load_average": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" - } - } - }, - "reloads": { - "properties": { - "failures": { - "type": "long" - }, - "successes": { - "type": "long" - } - } - }, - "queue": { - "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" } } }, "pipelines": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, "events": { "properties": { - "in": { - "type": "long" - }, - "filtered": { - "type": "long" - }, - "out": { - "type": "long" - }, "duration_in_millis": { "type": "long" }, - "queue_push_duration_in_millis": { + "out": { "type": "long" } } }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, "queue": { "properties": { - "events_count": { - "type": "long" - }, - "type": { - "type": "keyword" - }, "max_queue_size_in_bytes": { "type": "long" }, "queue_size_in_bytes": { "type": "long" + }, + "type": { + "type": "keyword" } } }, "vertices": { - "type": "nested", "properties": { - "id": { - "type": "keyword" - }, - "pipeline_ephemeral_id": { - "type": "keyword" + "duration_in_millis": { + "type": "long" }, "events_in": { "type": "long" @@ -590,111 +333,63 @@ "events_out": { "type": "long" }, - "duration_in_millis": { - "type": "long" - }, - "queue_push_duration_in_millis": { - "type": "long" + "id": { + "type": "keyword" }, - "long_counters": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "long" - } - } + "pipeline_ephemeral_id": { + "type": "keyword" }, - "double_gauges": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "double" - } - } + "queue_push_duration_in_millis": { + "type": "float" } - } - }, - "reloads": { + }, + "type": "nested" + } + }, + "type": "nested" + }, + "process": { + "properties": { + "cpu": { "properties": { - "failures": { - "type": "long" - }, - "successes": { + "percent": { "type": "long" } } } } }, - "workers": { - "type": "short" + "queue": { + "properties": { + "events_count": { + "type": "long" + } + } }, - "batch_size": { - "type": "integer" + "timestamp": { + "type": "date" } } }, - "logstash_state": { + "metricset": { "properties": { - "uuid": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "http_address": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, - "status": { - "type": "keyword" - }, - "pipeline": { - "properties": { - "id": { - "type": "keyword" - }, - "hash": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "workers": { - "type": "short" - }, - "batch_size": { - "type": "integer" - }, - "format": { - "type": "keyword" - }, - "version": { + "fields": { + "keyword": { + "ignore_above": 256, "type": "keyword" - }, - "representation": { - "enabled": false } - } + }, + "type": "text" } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -717,115 +412,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -840,29 +467,64 @@ } } }, - "fielddata": { + "indexing": { "properties": { - "memory_size_in_bytes": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { "type": "long" }, - "evictions": { + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { "type": "long" } } }, + "refresh": { + "properties": { + "total_time_in_millis": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "integer" + } + } + }, "store": { "properties": { "size_in_bytes": { "type": "long" } } + } + } + }, + "total": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + } + } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -881,14 +543,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -897,24 +557,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -924,79 +575,144 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } } } - }, - "total": { + } + } + }, + "indices_stats": { + "properties": { + "_all": { "properties": { - "docs": { + "primaries": { "properties": { - "count": { - "type": "long" + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } } } }, - "fielddata": { + "total": { "properties": { - "memory_size_in_bytes": { - "type": "long" + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } }, - "evictions": { - "type": "long" + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "job_stats": { + "properties": { + "job_id": { + "type": "keyword" + } + } + }, + "node_stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, - "store": { + "total": { "properties": { - "size_in_bytes": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -1004,51 +720,26 @@ } } }, - "merges": { + "query_cache": { "properties": { - "total_size_in_bytes": { + "memory_size_in_bytes": { "type": "long" } } }, - "query_cache": { + "request_cache": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, - "request_cache": { + "search": { "properties": { - "memory_size_in_bytes": { + "query_time_in_millis": { "type": "long" }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -1058,178 +749,19 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, "doc_values_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, "fixed_bit_set_memory_in_bytes": { "type": "long" - } - } - }, - "refresh": { - "properties": { - "total_time_in_millis": { - "type": "long" - } - } - } - } - } - } - }, - "cluster_stats": { - "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" - } - } - }, - "cluster_state": { - "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" - } - } - }, - "node_stats": { - "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" }, - "evictions": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_time_in_millis": { - "type": "long" - }, - "index_total": { - "type": "long" - }, - "throttle_time_in_millis": { - "type": "long" - } - } - }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { + "index_writer_memory_in_bytes": { "type": "long" - } - } - }, - "segments": { - "properties": { - "count": { - "type": "integer" }, "memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, "points_memory_in_bytes": { @@ -1241,232 +773,125 @@ "term_vectors_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, "version_map_memory_in_bytes": { "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } } } }, - "fs": { - "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { - "properties": { - "total": { - "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" - }, - "write_kilobytes": { - "type": "long" - } - } - } - } - } - } - }, - "os": { + "jvm": { "properties": { - "cgroup": { + "gc": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, - "cpu": { + "collectors": { "properties": { - "cfs_quota_micros": { - "type": "long" - }, - "control_group": { - "type": "keyword" - }, - "stat": { + "old": { "properties": { - "number_of_elapsed_periods": { + "collection_count": { "type": "long" }, - "number_of_times_throttled": { + "collection_time_in_millis": { + "type": "long" + } + } + }, + "young": { + "properties": { + "collection_count": { "type": "long" }, - "time_throttled_nanos": { + "collection_time_in_millis": { "type": "long" } } } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } - } - } - }, - "cpu": { - "properties": { - "load_average": { - "properties": { - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" - } - } } } - } - } - }, - "process": { - "properties": { - "open_file_descriptors": { - "type": "long" }, - "max_file_descriptors": { - "type": "long" - }, - "cpu": { - "properties": { - "percent": { - "type": "half_float" - } - } - } - } - }, - "jvm": { - "properties": { "mem": { "properties": { + "heap_max_in_bytes": { + "type": "long" + }, "heap_used_in_bytes": { "type": "long" }, "heap_used_percent": { "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" } } - }, - "gc": { + } + } + }, + "node_id": { + "type": "keyword" + }, + "os": { + "properties": { + "cgroup": { "properties": { - "collectors": { + "cpu": { "properties": { - "young": { + "cfs_quota_micros": { + "type": "long" + }, + "stat": { "properties": { - "collection_count": { + "number_of_elapsed_periods": { "type": "long" }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { + "number_of_times_throttled": { "type": "long" }, - "collection_time_in_millis": { + "time_throttled_nanos": { "type": "long" } } } } + }, + "cpuacct": { + "properties": { + "usage_nanos": { + "type": "long" + } + } + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "1m": { + "type": "half_float" + } + } } } } } }, - "thread_pool": { + "process": { "properties": { - "bulk": { + "cpu": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" + "percent": { + "type": "half_float" } } - }, - "generic": { + } + } + }, + "thread_pool": { + "properties": { + "bulk": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1477,9 +902,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1490,22 +912,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1516,22 +922,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -1554,246 +944,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-basic/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-basic/mappings.json index ce8427c5cd657..0715e68298b16 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-basic/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-basic/mappings.json @@ -6,115 +6,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -129,29 +61,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -166,44 +81,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -212,42 +92,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -256,36 +106,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -304,14 +137,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -320,24 +151,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -347,41 +169,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -390,68 +212,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -472,15 +318,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -488,15 +325,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -515,116 +343,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -640,16 +449,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -661,12 +464,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -676,12 +473,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -691,71 +482,10 @@ } } }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, "thread_pool": { "properties": { "bulk": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -766,9 +496,6 @@ }, "get": { "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -779,22 +506,6 @@ }, "index": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -805,22 +516,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -843,246 +538,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1104,24 +594,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -1143,6 +618,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum--with-10-alerts/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum--with-10-alerts/mappings.json index 07ba8e2f5b814..c1552898816b7 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum--with-10-alerts/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum--with-10-alerts/mappings.json @@ -6,115 +6,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -129,29 +61,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -166,44 +81,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -212,42 +92,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -256,36 +106,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -304,14 +137,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -320,24 +151,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -347,41 +169,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -390,68 +212,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -472,15 +318,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -488,15 +325,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -515,116 +343,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -640,16 +449,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -661,12 +464,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -676,413 +473,106 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { "type": "half_float" } - } - } - } - }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "index": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "search": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "write": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - } - } - } - } - }, - "index_recovery": { - "type": "object" - }, - "shard": { - "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, - "index": { - "type": "keyword" - }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, - "node": { - "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" - }, - "state": { - "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - } - } - }, - "ccr_stats": { - "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { - "type": "keyword" - }, - "follower_index": { - "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" + } + } + } }, - "read_exceptions": { - "type": "nested", + "thread_pool": { "properties": { - "from_seq_no": { - "type": "long" + "bulk": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "retries": { - "type": "integer" + "get": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "exception": { - "type": "object", + "index": { "properties": { - "type": { - "type": "keyword" + "queue": { + "type": "integer" }, - "reason": { - "type": "text" + "rejected": { + "type": "long" + } + } + }, + "search": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" } } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" }, - "reason": { - "type": "text" + "write": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } } } } } }, - "ccr_auto_follow_stats": { + "shard": { "properties": { - "number_of_failed_follow_indices": { - "type": "long" + "index": { + "type": "keyword" }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" + "node": { + "type": "keyword" }, - "number_of_successful_follow_indices": { - "type": "long" + "primary": { + "type": "boolean" }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } + "state": { + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "type": "keyword" }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } + "uuid": { + "type": "keyword" } } + }, + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1104,24 +594,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -1143,6 +618,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, @@ -1167,95 +657,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -1263,47 +679,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -1313,36 +710,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -1350,9 +720,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -1366,10 +733,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum/mappings.json index 07ba8e2f5b814..c1552898816b7 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster-yellow-platinum/mappings.json @@ -6,115 +6,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -129,29 +61,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -166,44 +81,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -212,42 +92,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -256,36 +106,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -304,14 +137,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -320,24 +151,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -347,41 +169,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -390,68 +212,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -472,15 +318,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -488,15 +325,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -515,116 +343,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -640,16 +449,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -661,12 +464,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -676,413 +473,106 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { "type": "half_float" } - } - } - } - }, - "jvm": { - "properties": { - "mem": { - "properties": { - "heap_used_in_bytes": { - "type": "long" - }, - "heap_used_percent": { - "type": "half_float" - }, - "heap_max_in_bytes": { - "type": "long" - } - } - }, - "gc": { - "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "index": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "search": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "write": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - } - } - } - } - }, - "index_recovery": { - "type": "object" - }, - "shard": { - "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, - "index": { - "type": "keyword" - }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, - "node": { - "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" - }, - "state": { - "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - } - } - }, - "ccr_stats": { - "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { - "type": "keyword" - }, - "follower_index": { - "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" + } + } + } }, - "read_exceptions": { - "type": "nested", + "thread_pool": { "properties": { - "from_seq_no": { - "type": "long" + "bulk": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "retries": { - "type": "integer" + "get": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } }, - "exception": { - "type": "object", + "index": { "properties": { - "type": { - "type": "keyword" + "queue": { + "type": "integer" }, - "reason": { - "type": "text" + "rejected": { + "type": "long" + } + } + }, + "search": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" } } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" }, - "reason": { - "type": "text" + "write": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + } + } } } } } }, - "ccr_auto_follow_stats": { + "shard": { "properties": { - "number_of_failed_follow_indices": { - "type": "long" + "index": { + "type": "keyword" }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" + "node": { + "type": "keyword" }, - "number_of_successful_follow_indices": { - "type": "long" + "primary": { + "type": "boolean" }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } + "state": { + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "type": "keyword" }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } + "uuid": { + "type": "keyword" } } + }, + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1104,24 +594,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -1143,6 +618,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, @@ -1167,95 +657,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -1263,47 +679,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -1313,36 +710,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } } } }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -1350,9 +720,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -1366,10 +733,24 @@ } } }, - "concurrent_connections": { - "type": "long" + "timestamp": { + "type": "date" + }, + "usage": { + "properties": { + "index": { + "type": "keyword" + } + } } } + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster_lots_of_nodes/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster_lots_of_nodes/mappings.json index 7b75f0d15292d..d709a40befe3e 100644 --- a/x-pack/test/functional/es_archives/monitoring/singlecluster_lots_of_nodes/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster_lots_of_nodes/mappings.json @@ -8,96 +8,14 @@ "date_detection": false, "dynamic": "false", "properties": { - "ccr_auto_follow_stats": { - "properties": { - "auto_followed_clusters": { - "properties": { - "cluster_name": { - "type": "keyword" - }, - "last_seen_metadata_version": { - "type": "long" - }, - "time_since_last_check_millis": { - "type": "long" - } - }, - "type": "nested" - }, - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "properties": { - "auto_follow_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - } - }, - "type": "nested" - } - } - }, "ccr_stats": { "properties": { - "bytes_read": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "fatal_exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "follower_aliases_version": { - "type": "long" - }, "follower_global_checkpoint": { "type": "long" }, "follower_index": { "type": "keyword" }, - "follower_mapping_version": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, "leader_global_checkpoint": { "type": "long" }, @@ -107,112 +25,30 @@ "leader_max_seq_no": { "type": "long" }, - "operations_read": { - "type": "long" - }, "operations_written": { "type": "long" }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "read_exceptions": { - "properties": { - "exception": { - "properties": { - "reason": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - } - }, - "type": "nested" - }, "remote_cluster": { "type": "keyword" }, "shard_id": { "type": "integer" }, - "successful_read_requests": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, "time_since_last_read_millis": { "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" } } }, "cluster_state": { "properties": { - "master_node": { - "type": "keyword" - }, - "nodes": { - "type": "object" - }, "nodes_hash": { "type": "integer" - }, - "shards": { - "type": "object" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "version": { - "type": "long" - } - } - }, - "cluster_stats": { - "properties": { - "indices": { - "type": "object" - }, - "nodes": { - "type": "object" } } }, "cluster_uuid": { "type": "keyword" }, - "index_recovery": { - "type": "object" - }, "index_stats": { "properties": { "index": { @@ -227,16 +63,6 @@ } } }, - "fielddata": { - "properties": { - "evictions": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -257,22 +83,6 @@ } } }, - "query_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, "refresh": { "properties": { "total_time_in_millis": { @@ -280,66 +90,10 @@ } } }, - "request_cache": { - "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "memory_size_in_bytes": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } - }, "segments": { "properties": { "count": { "type": "integer" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" } } }, @@ -354,18 +108,8 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -393,17 +137,8 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -416,17 +151,8 @@ }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -494,13 +220,6 @@ "properties": { "primaries": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { "index_time_in_millis": { @@ -510,33 +229,13 @@ "type": "long" } } - }, - "search": { - "properties": { - "query_time_in_millis": { - "type": "long" - }, - "query_total": { - "type": "long" - } - } } } }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_time_in_millis": { - "type": "long" - }, "index_total": { "type": "long" } @@ -558,58 +257,10 @@ } } }, - "interval_ms": { - "type": "long" - }, "job_stats": { "properties": { - "data_counts": { - "properties": { - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "empty_bucket_count": { - "type": "long" - }, - "input_bytes": { - "type": "long" - }, - "latest_record_timestamp": { - "type": "date" - }, - "processed_record_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - } - } - }, "job_id": { "type": "keyword" - }, - "model_size_stats": { - "properties": { - "bucket_allocation_failures_count": { - "type": "long" - }, - "model_bytes": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "state": { - "type": "keyword" } } }, @@ -617,13 +268,6 @@ "properties": { "fs": { "properties": { - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, "io_stats": { "properties": { "total": { @@ -631,15 +275,9 @@ "operations": { "type": "long" }, - "read_kilobytes": { - "type": "long" - }, "read_operations": { "type": "long" }, - "write_kilobytes": { - "type": "long" - }, "write_operations": { "type": "long" } @@ -651,12 +289,6 @@ "properties": { "available_in_bytes": { "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "total_in_bytes": { - "type": "long" } } } @@ -664,18 +296,8 @@ }, "indices": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { - "evictions": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" } @@ -696,33 +318,15 @@ }, "query_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, "request_cache": { "properties": { - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, "memory_size_in_bytes": { "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -772,13 +376,6 @@ "type": "long" } } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } } } }, @@ -827,15 +424,9 @@ } } }, - "mlockall": { - "type": "boolean" - }, "node_id": { "type": "keyword" }, - "node_master": { - "type": "boolean" - }, "os": { "properties": { "cgroup": { @@ -845,9 +436,6 @@ "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -865,26 +453,10 @@ }, "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, "usage_nanos": { "type": "long" } } - }, - "memory": { - "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" - } - } } } }, @@ -892,14 +464,8 @@ "properties": { "load_average": { "properties": { - "15m": { - "type": "half_float" - }, "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" } } } @@ -915,12 +481,6 @@ "type": "half_float" } } - }, - "max_file_descriptors": { - "type": "long" - }, - "open_file_descriptors": { - "type": "long" } } }, @@ -933,22 +493,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "generic": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -959,9 +503,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -972,22 +513,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "management": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -998,22 +523,6 @@ }, "rejected": { "type": "long" - }, - "threads": { - "type": "integer" - } - } - }, - "watcher": { - "properties": { - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - }, - "threads": { - "type": "integer" } } }, @@ -1042,12 +551,6 @@ "primary": { "type": "boolean" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "state": { "type": "keyword" } @@ -1055,22 +558,9 @@ }, "source_node": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { "type": "keyword" }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "transport_address": { - "type": "keyword" - }, "uuid": { "type": "keyword" } diff --git a/x-pack/test/functional/es_archives/monitoring/standalone_cluster/mappings.json b/x-pack/test/functional/es_archives/monitoring/standalone_cluster/mappings.json index e1b952ef647d9..9ad3eef67cbd3 100644 --- a/x-pack/test/functional/es_archives/monitoring/standalone_cluster/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/standalone_cluster/mappings.json @@ -6,115 +6,47 @@ "date_detection": false, "dynamic": false, "properties": { - "cluster_uuid": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { + "ccr_stats": { "properties": { - "uuid": { - "type": "keyword" + "follower_global_checkpoint": { + "type": "long" }, - "host": { + "follower_index": { "type": "keyword" }, - "transport_address": { - "type": "keyword" + "leader_global_checkpoint": { + "type": "long" }, - "ip": { + "leader_index": { "type": "keyword" }, - "name": { + "leader_max_seq_no": { + "type": "long" + }, + "operations_written": { + "type": "long" + }, + "remote_cluster": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" + "shard_id": { + "type": "integer" + }, + "time_since_last_read_millis": { + "type": "long" } } }, - "indices_stats": { + "cluster_state": { "properties": { - "_all": { - "properties": { - "primaries": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "indexing": { - "properties": { - "index_total": { - "type": "long" - }, - "index_time_in_millis": { - "type": "long" - } - } - }, - "search": { - "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { - "type": "long" - } - } - } - } - } - } + "nodes_hash": { + "type": "integer" } } }, + "cluster_uuid": { + "type": "keyword" + }, "index_stats": { "properties": { "index": { @@ -129,29 +61,12 @@ } } }, - "fielddata": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -166,44 +81,9 @@ } } }, - "query_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "request_cache": { - "properties": { - "memory_size_in_bytes": { - "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" - } - } - }, - "search": { + "refresh": { "properties": { - "query_total": { - "type": "long" - }, - "query_time_in_millis": { + "total_time_in_millis": { "type": "long" } } @@ -212,42 +92,12 @@ "properties": { "count": { "type": "integer" - }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { - "type": "long" - }, - "points_memory_in_bytes": { - "type": "long" - }, - "stored_fields_memory_in_bytes": { - "type": "long" - }, - "term_vectors_memory_in_bytes": { - "type": "long" - }, - "norms_memory_in_bytes": { - "type": "long" - }, - "doc_values_memory_in_bytes": { - "type": "long" - }, - "index_writer_memory_in_bytes": { - "type": "long" - }, - "version_map_memory_in_bytes": { - "type": "long" - }, - "fixed_bit_set_memory_in_bytes": { - "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -256,36 +106,19 @@ }, "total": { "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" } } }, "indexing": { "properties": { - "index_total": { + "index_time_in_millis": { "type": "long" }, - "index_time_in_millis": { + "index_total": { "type": "long" }, "throttle_time_in_millis": { @@ -304,14 +137,12 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { "type": "long" } } @@ -320,24 +151,15 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, "search": { "properties": { - "query_total": { + "query_time_in_millis": { "type": "long" }, - "query_time_in_millis": { + "query_total": { "type": "long" } } @@ -347,41 +169,41 @@ "count": { "type": "integer" }, - "memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "terms_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "norms_memory_in_bytes": { "type": "long" }, - "norms_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "version_map_memory_in_bytes": { "type": "long" } } }, - "refresh": { + "store": { "properties": { - "total_time_in_millis": { + "size_in_bytes": { "type": "long" } } @@ -390,68 +212,92 @@ } } }, - "cluster_stats": { + "indices_stats": { "properties": { - "nodes": { - "type": "object" - }, - "indices": { - "type": "object" + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } } } }, - "cluster_state": { + "job_stats": { "properties": { - "version": { - "type": "long" - }, - "nodes_hash": { - "type": "integer" - }, - "master_node": { - "type": "keyword" - }, - "state_uuid": { - "type": "keyword" - }, - "status": { + "job_id": { "type": "keyword" - }, - "nodes": { - "type": "object" - }, - "shards": { - "type": "object" } } }, "node_stats": { "properties": { - "node_id": { - "type": "keyword" - }, - "node_master": { - "type": "boolean" - }, - "mlockall": { - "type": "boolean" - }, - "indices": { + "fs": { "properties": { - "docs": { + "io_stats": { "properties": { - "count": { - "type": "long" + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } } } }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { "fielddata": { "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" } } }, @@ -472,15 +318,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -488,15 +325,6 @@ "properties": { "memory_size_in_bytes": { "type": "long" - }, - "evictions": { - "type": "long" - }, - "hit_count": { - "type": "long" - }, - "miss_count": { - "type": "long" } } }, @@ -515,116 +343,97 @@ "count": { "type": "integer" }, - "memory_in_bytes": { - "type": "long" - }, - "terms_memory_in_bytes": { + "doc_values_memory_in_bytes": { "type": "long" }, - "points_memory_in_bytes": { + "fixed_bit_set_memory_in_bytes": { "type": "long" }, - "stored_fields_memory_in_bytes": { + "index_writer_memory_in_bytes": { "type": "long" }, - "term_vectors_memory_in_bytes": { + "memory_in_bytes": { "type": "long" }, "norms_memory_in_bytes": { "type": "long" }, - "doc_values_memory_in_bytes": { + "points_memory_in_bytes": { "type": "long" }, - "index_writer_memory_in_bytes": { + "stored_fields_memory_in_bytes": { "type": "long" }, - "version_map_memory_in_bytes": { + "term_vectors_memory_in_bytes": { "type": "long" }, - "fixed_bit_set_memory_in_bytes": { + "terms_memory_in_bytes": { "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { + }, + "version_map_memory_in_bytes": { "type": "long" } } } } }, - "fs": { + "jvm": { "properties": { - "total": { - "properties": { - "total_in_bytes": { - "type": "long" - }, - "free_in_bytes": { - "type": "long" - }, - "available_in_bytes": { - "type": "long" - } - } - }, - "data": { - "properties": { - "spins": { - "type": "boolean" - } - } - }, - "io_stats": { + "gc": { "properties": { - "total": { + "collectors": { "properties": { - "operations": { - "type": "long" - }, - "read_operations": { - "type": "long" - }, - "write_operations": { - "type": "long" - }, - "read_kilobytes": { - "type": "long" + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } }, - "write_kilobytes": { - "type": "long" + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } } } } } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } } } }, + "node_id": { + "type": "keyword" + }, "os": { "properties": { "cgroup": { "properties": { - "cpuacct": { - "properties": { - "control_group": { - "type": "keyword" - }, - "usage_nanos": { - "type": "long" - } - } - }, "cpu": { "properties": { "cfs_quota_micros": { "type": "long" }, - "control_group": { - "type": "keyword" - }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -640,16 +449,10 @@ } } }, - "memory": { + "cpuacct": { "properties": { - "control_group": { - "type": "keyword" - }, - "limit_in_bytes": { - "type": "keyword" - }, - "usage_in_bytes": { - "type": "keyword" + "usage_nanos": { + "type": "long" } } } @@ -661,12 +464,6 @@ "properties": { "1m": { "type": "half_float" - }, - "5m": { - "type": "half_float" - }, - "15m": { - "type": "half_float" } } } @@ -676,12 +473,6 @@ }, "process": { "properties": { - "open_file_descriptors": { - "type": "long" - }, - "max_file_descriptors": { - "type": "long" - }, "cpu": { "properties": { "percent": { @@ -691,110 +482,30 @@ } } }, - "jvm": { + "thread_pool": { "properties": { - "mem": { + "bulk": { "properties": { - "heap_used_in_bytes": { - "type": "long" + "queue": { + "type": "integer" }, - "heap_used_percent": { - "type": "half_float" + "rejected": { + "type": "long" + } + } + }, + "get": { + "properties": { + "queue": { + "type": "integer" }, - "heap_max_in_bytes": { + "rejected": { "type": "long" } } }, - "gc": { + "index": { "properties": { - "collectors": { - "properties": { - "young": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - }, - "old": { - "properties": { - "collection_count": { - "type": "long" - }, - "collection_time_in_millis": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "thread_pool": { - "properties": { - "bulk": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "generic": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "get": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "index": { - "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "management": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -805,22 +516,6 @@ }, "search": { "properties": { - "threads": { - "type": "integer" - }, - "queue": { - "type": "integer" - }, - "rejected": { - "type": "long" - } - } - }, - "watcher": { - "properties": { - "threads": { - "type": "integer" - }, "queue": { "type": "integer" }, @@ -843,246 +538,41 @@ } } }, - "index_recovery": { - "type": "object" - }, "shard": { "properties": { - "state": { - "type": "keyword" - }, - "primary": { - "type": "boolean" - }, "index": { "type": "keyword" }, - "relocating_node": { - "type": "keyword" - }, - "shard": { - "type": "long" - }, "node": { "type": "keyword" - } - } - }, - "job_stats": { - "properties": { - "job_id": { - "type": "keyword" + }, + "primary": { + "type": "boolean" }, "state": { "type": "keyword" - }, - "data_counts": { - "properties": { - "input_bytes": { - "type": "long" - }, - "processed_record_count": { - "type": "long" - }, - "empty_bucket_count": { - "type": "long" - }, - "sparse_bucket_count": { - "type": "long" - }, - "bucket_count": { - "type": "long" - }, - "earliest_record_timestamp": { - "type": "date" - }, - "latest_record_timestamp": { - "type": "date" - } - } - }, - "model_size_stats": { - "properties": { - "model_bytes": { - "type": "long" - }, - "bucket_allocation_failures_count": { - "type": "long" - } - } - }, - "node": { - "properties": { - "id": { - "type": "keyword" - } - } } } }, - "ccr_stats": { + "source_node": { "properties": { - "remote_cluster": { - "type": "keyword" - }, - "leader_index": { + "name": { "type": "keyword" }, - "follower_index": { + "uuid": { "type": "keyword" - }, - "shard_id": { - "type": "integer" - }, - "leader_global_checkpoint": { - "type": "long" - }, - "leader_max_seq_no": { - "type": "long" - }, - "follower_global_checkpoint": { - "type": "long" - }, - "follower_max_seq_no": { - "type": "long" - }, - "last_requested_seq_no": { - "type": "long" - }, - "outstanding_read_requests": { - "type": "long" - }, - "outstanding_write_requests": { - "type": "long" - }, - "write_buffer_operation_count": { - "type": "long" - }, - "write_buffer_size_in_bytes": { - "type": "long" - }, - "follower_mapping_version": { - "type": "long" - }, - "follower_settings_version": { - "type": "long" - }, - "total_read_time_millis": { - "type": "long" - }, - "total_read_remote_exec_time_millis": { - "type": "long" - }, - "successful_read_requests": { - "type": "long" - }, - "failed_read_requests": { - "type": "long" - }, - "operations_read": { - "type": "long" - }, - "bytes_read": { - "type": "long" - }, - "total_write_time_millis": { - "type": "long" - }, - "successful_write_requests": { - "type": "long" - }, - "failed_write_requests": { - "type": "long" - }, - "operations_written": { - "type": "long" - }, - "read_exceptions": { - "type": "nested", - "properties": { - "from_seq_no": { - "type": "long" - }, - "retries": { - "type": "integer" - }, - "exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "time_since_last_read_millis": { - "type": "long" - }, - "fatal_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } } } }, - "ccr_auto_follow_stats": { - "properties": { - "number_of_failed_follow_indices": { - "type": "long" - }, - "number_of_failed_remote_cluster_state_requests": { - "type": "long" - }, - "number_of_successful_follow_indices": { - "type": "long" - }, - "recent_auto_follow_errors": { - "type": "nested", - "properties": { - "leader_index": { - "type": "keyword" - }, - "timestamp": { - "type": "long" - }, - "auto_follow_exception": { - "type": "object", - "properties": { - "type": { - "type": "keyword" - }, - "reason": { - "type": "text" - } - } - } - } - }, - "auto_followed_clusters": { - "type": "nested", - "properties": { - "cluster_name": { - "type": "keyword" - }, - "time_since_last_check_millis": { - "type": "long" - }, - "last_seen_metadata_version": { - "type": "long" - } - } - } - } + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" } } }, @@ -1104,24 +594,9 @@ "mappings": { "dynamic": false, "properties": { - "timestamp": { - "type": "date" - }, - "update_timestamp": { - "type": "date" - }, - "resolved_timestamp": { - "type": "date" - }, - "prefix": { - "type": "text" - }, "message": { "type": "text" }, - "suffix": { - "type": "text" - }, "metadata": { "properties": { "cluster_uuid": { @@ -1143,6 +618,21 @@ "type": "keyword" } } + }, + "prefix": { + "type": "text" + }, + "resolved_timestamp": { + "type": "date" + }, + "suffix": { + "type": "text" + }, + "timestamp": { + "type": "date" + }, + "update_timestamp": { + "type": "date" } } }, @@ -1167,95 +657,21 @@ "cluster_uuid": { "type": "keyword" }, - "timestamp": { - "type": "date", - "format": "date_time" - }, - "interval_ms": { - "type": "long" - }, - "type": { - "type": "keyword" - }, - "source_node": { - "properties": { - "uuid": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "timestamp": { - "type": "date", - "format": "date_time" - } - } - }, "kibana_stats": { "properties": { + "concurrent_connections": { + "type": "long" + }, "kibana": { "properties": { - "uuid": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "snapshot": { - "type": "boolean" - }, "status": { "type": "keyword" }, - "statuses": { - "properties": { - "name": { - "type": "keyword" - }, - "state": { - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "name": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "vm_type": { - "type": "keyword" - }, - "region": { + "uuid": { "type": "keyword" }, - "zone": { + "version": { "type": "keyword" - }, - "metadata": { - "type": "object" } } }, @@ -1263,47 +679,28 @@ "properties": { "load": { "properties": { - "1m": { + "15m": { "type": "half_float" }, - "5m": { + "1m": { "type": "half_float" }, - "15m": { + "5m": { "type": "half_float" } } - }, - "memory": { - "properties": { - "total_in_bytes": { - "type": "float" - }, - "free_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - } - } - }, - "uptime_in_millis": { - "type": "long" } } }, "process": { "properties": { + "event_loop_delay": { + "type": "float" + }, "memory": { "properties": { "heap": { "properties": { - "total_in_bytes": { - "type": "float" - }, - "used_in_bytes": { - "type": "float" - }, "size_limit": { "type": "float" } @@ -1313,36 +710,9 @@ "type": "float" } } - }, - "event_loop_delay": { - "type": "float" - }, - "uptime_in_millis": { - "type": "long" } } }, - "sockets": { - "properties": { - "http": { - "properties": { - "total": { - "type": "long" - } - } - }, - "https": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "timestamp": { - "type": "date" - }, "requests": { "properties": { "disconnects": { @@ -1350,9 +720,6 @@ }, "total": { "type": "long" - }, - "status_codes": { - "type": "object" } } }, @@ -1366,531 +733,164 @@ } } }, - "concurrent_connections": { - "type": "long" - } - } - } - } - }, - "settings": { - "index": { - "codec": "best_compression", - "format": "6", - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "index": ".monitoring-beats-6-2019.02.04", - "mappings": { - "dynamic": false, - "properties": { - "beats_state": { - "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } + "timestamp": { + "type": "date" }, - "state": { + "usage": { "properties": { - "beat": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "hostname": { - "type": "keyword" - }, - "os": { - "properties": { - "build": { - "type": "keyword" - }, - "family": { - "type": "keyword" - }, - "platform": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - } - } - }, - "input": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "module": { - "properties": { - "count": { - "type": "long" - }, - "names": { - "type": "keyword" - } - } - }, - "output": { - "properties": { - "name": { - "type": "keyword" - } - } - }, - "service": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } + "index": { + "type": "keyword" } } - }, - "timestamp": { - "format": "date_time", - "type": "date" } } }, - "beats_stats": { - "properties": { - "beat": { - "properties": { - "host": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "metrics": { - "properties": { - "beat": { - "properties": { - "cpu": { - "properties": { - "system": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "total": { - "properties": { - "value": { - "type": "long" - }, - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "user": { - "properties": { - "ticks": { - "type": "long" - }, - "time": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - } - } - }, - "info": { - "properties": { - "ephemeral_id": { - "type": "keyword" - }, - "uptime": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "memstats": { - "properties": { - "gc_next": { - "type": "long" - }, - "memory_alloc": { - "type": "long" - }, - "memory_total": { - "type": "long" - }, - "rss": { - "type": "long" - } - } - }, - "handles": { - "properties": { - "open": { - "type": "long" - }, - "limit": { - "properties": { - "hard": { - "type": "long" - }, - "soft": { - "type": "long" - } - } - } - } - } - } - }, - "apm-server": { - "properties": { - "server": { - "properties": { - "request": { - "properties": { - "count": { - "type": "long" - } - } - }, - "concurrent": { - "properties": { - "wait": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "response": { - "properties": { - "count": { - "type": "long" - }, - "errors": { - "properties": { - "count": { - "type": "long" - }, - "toolarge": { - "type": "long" - }, - "validate": { - "type": "long" - }, - "ratelimit": { - "type": "long" - }, - "queue": { - "type": "long" - }, - "closed": { - "type": "long" - }, - "forbidden": { - "type": "long" - }, - "concurrency": { - "type": "long" - }, - "unauthorized": { - "type": "long" - }, - "internal": { - "type": "long" - }, - "decode": { - "type": "long" - }, - "method": { - "type": "long" - } - } - }, - "valid": { - "properties": { - "ok": { - "type": "long" - }, - "accepted": { - "type": "long" - }, - "count": { - "type": "long" - } - } - } - } - } - } - }, - "decoder": { + "timestamp": { + "format": "date_time", + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "format": "6", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "index": ".monitoring-beats-6-2019.02.04", + "mappings": { + "dynamic": false, + "properties": { + "beats_state": { + "properties": { + "timestamp": { + "format": "date_time", + "type": "date" + } + } + }, + "beats_stats": { + "properties": { + "beat": { + "properties": { + "type": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "metrics": { + "properties": { + "apm-server": { + "properties": { + "processor": { "properties": { - "deflate": { - "properties": { - "content-length": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "gzip": { + "error": { "properties": { - "content-length": { - "type": "long" - }, - "count": { + "transformations": { "type": "long" } } }, - "uncompressed": { + "metric": { "properties": { - "content-length": { - "type": "long" - }, - "count": { + "transformations": { "type": "long" } } }, - "reader": { + "span": { "properties": { - "size": { - "type": "long" - }, - "count": { + "transformations": { "type": "long" } } }, - "missing-content-length": { + "transaction": { "properties": { - "count": { + "transformations": { "type": "long" } } } } }, - "processor": { + "server": { "properties": { - "metric": { + "request": { "properties": { - "decoding": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { - "type": "long" - }, - "count": { - "type": "long" - } - } - }, - "transformations": { + "count": { "type": "long" } } }, - "sourcemap": { + "response": { "properties": { - "counter": { + "count": { "type": "long" }, - "decoding": { + "errors": { "properties": { - "errors": { + "closed": { "type": "long" }, - "count": { + "concurrency": { "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { + }, + "decode": { "type": "long" }, - "count": { + "forbidden": { "type": "long" - } - } - } - } - }, - "transaction": { - "properties": { - "decoding": { - "properties": { - "errors": { + }, + "internal": { "type": "long" }, - "count": { + "method": { "type": "long" - } - } - }, - "validation": { - "properties": { - "errors": { + }, + "queue": { "type": "long" }, - "count": { + "ratelimit": { "type": "long" - } - } - }, - "transformations": { - "type": "long" - }, - "transactions": { - "type": "long" - }, - "spans": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "error": { - "properties": { - "decoding": { - "properties": { - "errors": { + }, + "toolarge": { + "type": "long" + }, + "unauthorized": { "type": "long" }, - "count": { + "validate": { "type": "long" } } }, - "validation": { + "valid": { "properties": { - "errors": { + "accepted": { "type": "long" }, - "count": { + "ok": { "type": "long" } } - }, - "transformations": { - "type": "long" - }, - "errors": { - "type": "long" - }, - "stacktraces": { - "type": "long" - }, - "frames": { - "type": "long" - } - } - }, - "span": { - "properties": { - "transformations": { - "type": "long" } } } @@ -1898,28 +898,53 @@ } } }, - "libbeat": { + "beat": { "properties": { - "config": { + "cpu": { "properties": { - "module": { + "total": { "properties": { - "running": { - "type": "long" - }, - "starts": { - "type": "long" - }, - "stops": { + "value": { "type": "long" } } - }, - "reloads": { + } + } + }, + "handles": { + "properties": { + "open": { "type": "long" } } }, + "info": { + "properties": { + "ephemeral_id": { + "type": "keyword" + } + } + }, + "memstats": { + "properties": { + "gc_next": { + "type": "long" + }, + "memory_alloc": { + "type": "long" + }, + "memory_total": { + "type": "long" + }, + "rss": { + "type": "long" + } + } + } + } + }, + "libbeat": { + "properties": { "output": { "properties": { "events": { @@ -1930,23 +955,14 @@ "active": { "type": "long" }, - "batches": { - "type": "long" - }, "dropped": { "type": "long" }, - "duplicates": { - "type": "long" - }, "failed": { "type": "long" }, "total": { "type": "long" - }, - "toomany": { - "type": "long" } } }, @@ -1960,9 +976,6 @@ } } }, - "type": { - "type": "keyword" - }, "write": { "properties": { "bytes": { @@ -1977,23 +990,14 @@ }, "pipeline": { "properties": { - "clients": { - "type": "long" - }, "events": { "properties": { - "active": { - "type": "long" - }, "dropped": { "type": "long" }, "failed": { "type": "long" }, - "filtered": { - "type": "long" - }, "published": { "type": "long" }, @@ -2004,13 +1008,6 @@ "type": "long" } } - }, - "queue": { - "properties": { - "acked": { - "type": "long" - } - } } } } @@ -2023,24 +1020,11 @@ "1": { "type": "double" }, - "5": { - "type": "double" - }, "15": { "type": "double" }, - "norm": { - "properties": { - "1": { - "type": "double" - }, - "5": { - "type": "double" - }, - "15": { - "type": "double" - } - } + "5": { + "type": "double" } } } @@ -2048,9 +1032,6 @@ } } }, - "tags": { - "type": "keyword" - }, "timestamp": { "format": "date_time", "type": "date" @@ -2060,25 +1041,16 @@ "cluster_uuid": { "type": "keyword" }, - "interval_ms": { - "type": "long" - }, - "source_node": { + "metricset": { "properties": { - "host": { - "type": "keyword" - }, - "ip": { - "type": "keyword" - }, "name": { - "type": "keyword" - }, - "transport_address": { - "type": "keyword" - }, - "uuid": { - "type": "keyword" + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" } } }, diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/data.json.gz b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/data.json.gz new file mode 100644 index 0000000000000..06e83f8c267d6 Binary files /dev/null and b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/mappings.json b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/mappings.json new file mode 100644 index 0000000000000..1de04a64398c4 --- /dev/null +++ b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces/mappings.json @@ -0,0 +1,2635 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "43b8830d5d0df85a6823d290885fc9fd", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "74eb4b909f81222fa1ddeaba2881a37e", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "386dc9996a3b74607de64c2ab2171582", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", + "fleet-agents": "6012d61d15e72564e47fc3402332756e", + "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "45915a1ad866812242df474eb0479052", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "f74dfe498e1849267cda41580b2be110", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "52346cfec69ff7b47d5f0c12361a2797", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "7f9e077078cab612f6a58e3bfdedb71a", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "44d6bd48a1a653bcb60ea01614b9e3c9", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "agent_actions": { + "dynamic": "false", + "type": "object" + }, + "agent_configs": { + "dynamic": "false", + "type": "object" + }, + "agent_events": { + "dynamic": "false", + "type": "object" + }, + "agents": { + "dynamic": "false", + "type": "object" + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-services-telemetry": { + "dynamic": "false", + "type": "object" + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "datasources": { + "dynamic": "false", + "type": "object" + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "enrollment_api_keys": { + "dynamic": "false", + "type": "object" + }, + "epm-package": { + "dynamic": "false", + "type": "object" + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "install_started_at": { + "type": "date" + }, + "install_status": { + "type": "keyword" + }, + "install_version": { + "type": "keyword" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "policy_id": { + "type": "keyword" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_policies": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "policy_id": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "outputs": { + "dynamic": "false", + "type": "object" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "server": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "dynamic": "false", + "type": "object" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts index ce356dbd081c8..93f656ea952d2 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts @@ -115,7 +115,7 @@ export default function (providerContext: FtrProviderContext) { }, }) .expect(400); - expect(apiResponse.message).to.match(/Agent version is not compatible with kibana/); + expect(apiResponse.message).to.match(/is not compatible/); }); it('should allow to enroll an agent with a valid enrollment token', async () => { diff --git a/x-pack/test/ingest_manager_api_integration/apis/index.js b/x-pack/test/ingest_manager_api_integration/apis/index.js index fac8a26fd6aec..7c1ebef337baa 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/index.js +++ b/x-pack/test/ingest_manager_api_integration/apis/index.js @@ -22,5 +22,8 @@ export default function ({ loadTestFile }) { // Agent policies loadTestFile(require.resolve('./agent_policy/index')); + + // Settings + loadTestFile(require.resolve('./settings/index')); }); } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/index.js b/x-pack/test/ingest_manager_api_integration/apis/settings/index.js similarity index 61% rename from x-pack/plugins/index_lifecycle_management/public/application/store/actions/index.js rename to x-pack/test/ingest_manager_api_integration/apis/settings/index.js index fef79c7782bb0..99346fcabeff4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/index.js +++ b/x-pack/test/ingest_manager_api_integration/apis/settings/index.js @@ -4,4 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './policies'; +export default function loadTests({ loadTestFile }) { + describe('Settings Endpoints', () => { + loadTestFile(require.resolve('./update')); + }); +} diff --git a/x-pack/test/ingest_manager_api_integration/apis/settings/update.ts b/x-pack/test/ingest_manager_api_integration/apis/settings/update.ts new file mode 100644 index 0000000000000..86292b535db2d --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/settings/update.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + + describe('Settings - update', async function () { + skipIfNoDockerRegistry(providerContext); + + it("should bump all agent policy's revision", async function () { + const { body: testPolicy1PostRes } = await supertest + .post(`/api/ingest_manager/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'test', + description: '', + namespace: 'default', + }); + const { body: testPolicy2PostRes } = await supertest + .post(`/api/ingest_manager/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'test2', + description: '', + namespace: 'default', + }); + await supertest + .put(`/api/ingest_manager/settings`) + .set('kbn-xsrf', 'xxxx') + .send({ kibana_urls: ['http://localhost:1232/abc', 'http://localhost:1232/abc'] }); + + const getTestPolicy1Res = await kibanaServer.savedObjects.get({ + type: 'ingest-agent-policies', + id: testPolicy1PostRes.item.id, + }); + const getTestPolicy2Res = await kibanaServer.savedObjects.get({ + type: 'ingest-agent-policies', + id: testPolicy2PostRes.item.id, + }); + expect(getTestPolicy1Res.attributes.revision).equal(2); + expect(getTestPolicy2Res.attributes.revision).equal(2); + }); + }); +} diff --git a/x-pack/test/licensing_plugin/config.legacy.ts b/x-pack/test/licensing_plugin/config.legacy.ts deleted file mode 100644 index 14eadc3194f9e..0000000000000 --- a/x-pack/test/licensing_plugin/config.legacy.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; - -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const commonConfig = await readConfigFile(require.resolve('./config')); - - return { - ...commonConfig.getAll(), - testFiles: [require.resolve('./legacy')], - }; -} diff --git a/x-pack/test/licensing_plugin/legacy/index.ts b/x-pack/test/licensing_plugin/legacy/index.ts deleted file mode 100644 index 6274bd3969042..0000000000000 --- a/x-pack/test/licensing_plugin/legacy/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FtrProviderContext } from '../services'; - -// eslint-disable-next-line import/no-default-export -export default function ({ loadTestFile }: FtrProviderContext) { - describe('Legacy licensing plugin', function () { - this.tags('ciGroup2'); - // MUST BE LAST! CHANGES LICENSE TYPE! - loadTestFile(require.resolve('./updates')); - }); -} diff --git a/x-pack/test/licensing_plugin/legacy/updates.ts b/x-pack/test/licensing_plugin/legacy/updates.ts deleted file mode 100644 index 1de8659672d2f..0000000000000 --- a/x-pack/test/licensing_plugin/legacy/updates.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../services'; -import { createScenario } from '../scenario'; -import '../../../../test/plugin_functional/plugins/core_provider_plugin/types'; - -// eslint-disable-next-line import/no-default-export -export default function (ftrContext: FtrProviderContext) { - const { getService } = ftrContext; - const supertest = getService('supertest'); - const testSubjects = getService('testSubjects'); - - const scenario = createScenario(ftrContext); - - describe('changes in license types', () => { - after(async () => { - await scenario.teardown(); - }); - - it('provides changes in license types', async () => { - await scenario.setup(); - await scenario.waitForPluginToDetectLicenseUpdate(); - - const { - body: legacyInitialLicense, - header: legacyInitialLicenseHeaders, - } = await supertest.get('/api/xpack/v1/info').expect(200); - - expect(legacyInitialLicense.license?.type).to.be('basic'); - expect(legacyInitialLicenseHeaders['kbn-xpack-sig']).to.be.a('string'); - - await scenario.startTrial(); - await scenario.waitForPluginToDetectLicenseUpdate(); - - const { body: legacyTrialLicense, header: legacyTrialLicenseHeaders } = await supertest - .get('/api/xpack/v1/info') - .expect(200); - - expect(legacyTrialLicense.license?.type).to.be('trial'); - expect(legacyTrialLicenseHeaders['kbn-xpack-sig']).to.not.be( - legacyInitialLicenseHeaders['kbn-xpack-sig'] - ); - - await scenario.startBasic(); - await scenario.waitForPluginToDetectLicenseUpdate(); - - const { body: legacyBasicLicense } = await supertest.get('/api/xpack/v1/info').expect(200); - expect(legacyBasicLicense.license?.type).to.be('basic'); - - // banner shown only when license expired not just deleted - await testSubjects.missingOrFail('licenseExpiredBanner'); - }); - }); -} diff --git a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts index dc5aaba69604f..ee165bf45fc7e 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts @@ -44,7 +44,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.reporting.csv.maxSizeBytes=2850`, `--xpack.reporting.queue.pollInterval=3000`, `--xpack.security.session.idleTimeout=3600000`, - `--xpack.spaces.enabled=false`, `--xpack.reporting.capture.networkPolicy.rules=${JSON.stringify(testPolicyRules)}`, ], }, diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts index 18ef28150c42d..8bf4d2448498c 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts @@ -12,7 +12,8 @@ export default function ({ loadTestFile }: FtrProviderContext) { this.tags('ciGroup2'); loadTestFile(require.resolve('./csv_job_params')); loadTestFile(require.resolve('./csv_saved_search')); - loadTestFile(require.resolve('./usage')); loadTestFile(require.resolve('./network_policy')); + loadTestFile(require.resolve('./spaces')); + loadTestFile(require.resolve('./usage')); }); } diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts b/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts index 9f9800cafb99a..8692f79d5aea9 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts @@ -20,7 +20,7 @@ export default function ({ getService }: FtrProviderContext) { * The Reporting API Functional Test config implements a network policy that * is designed to disallow the following Canvas worksheet */ - describe('reporting network policy', () => { + describe('Network Policy', () => { before(async () => { await esArchiver.load(archive); // includes a canvas worksheet with an offending image URL }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts new file mode 100644 index 0000000000000..0145ca2a18092 --- /dev/null +++ b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import * as Rx from 'rxjs'; +import { filter, first, map, switchMap, tap, timeout } from 'rxjs/operators'; +import { FtrProviderContext } from '../ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const reportingAPI = getService('reportingAPI'); + const supertest = getService('supertest'); + const log = getService('log'); + + const getCompleted$ = (downloadPath: string) => { + return Rx.interval(2000).pipe( + tap(() => log.debug(`checking report status at ${downloadPath}...`)), + switchMap(() => supertest.get(downloadPath)), + filter(({ status: statusCode }) => statusCode === 200), + map((response) => response.text), + first(), + timeout(15000) + ); + }; + + describe('Exports from Non-default Space', () => { + before(async () => { + await esArchiver.load('reporting/ecommerce'); + await esArchiver.load('reporting/ecommerce_kibana_spaces'); // dashboard in non default space + }); + + after(async () => { + await esArchiver.unload('reporting/ecommerce'); + await esArchiver.unload('reporting/ecommerce_kibana_spaces'); + }); + + afterEach(async () => { + await reportingAPI.deleteAllReports(); + }); + + it('should complete a job of CSV saved search export in non-default space', async () => { + const downloadPath = await reportingAPI.postJob( + `/s/non_default_space/api/reporting/generate/csv?jobParams=%28browserTimezone%3AUTC%2CconflictedTypesFields%3A%21%28%29%2Cfields%3A%21%28order_date%2Ccategory%2Ccustomer_first_name%2Ccustomer_full_name%2Ctotal_quantity%2Ctotal_unique_products%2Ctaxless_total_price%2Ctaxful_total_price%2Ccurrency%29%2CindexPatternId%3A%27067dec90-e7ee-11ea-a730-d58e9ea7581b%27%2CmetaFields%3A%21%28_source%2C_id%2C_type%2C_index%2C_score%29%2CobjectType%3Asearch%2CsearchRequest%3A%28body%3A%28_source%3A%28includes%3A%21%28order_date%2Ccategory%2Ccustomer_first_name%2Ccustomer_full_name%2Ctotal_quantity%2Ctotal_unique_products%2Ctaxless_total_price%2Ctaxful_total_price%2Ccurrency%29%29%2Cdocvalue_fields%3A%21%28%28field%3Aorder_date%2Cformat%3Adate_time%29%29%2Cquery%3A%28bool%3A%28filter%3A%21%28%28match_all%3A%28%29%29%2C%28range%3A%28order_date%3A%28format%3Astrict_date_optional_time%2Cgte%3A%272019-06-11T08%3A24%3A16.425Z%27%2Clte%3A%272019-07-13T09%3A31%3A07.520Z%27%29%29%29%29%2Cmust%3A%21%28%29%2Cmust_not%3A%21%28%29%2Cshould%3A%21%28%29%29%29%2Cscript_fields%3A%28%29%2Csort%3A%21%28%28order_date%3A%28order%3Adesc%2Cunmapped_type%3Aboolean%29%29%29%2Cstored_fields%3A%21%28order_date%2Ccategory%2Ccustomer_first_name%2Ccustomer_full_name%2Ctotal_quantity%2Ctotal_unique_products%2Ctaxless_total_price%2Ctaxful_total_price%2Ccurrency%29%2Cversion%3A%21t%29%2Cindex%3A%27ecommerce%2A%27%29%2Ctitle%3A%27Ecom%20Search%27%29` + ); + + // Retry the download URL until a "completed" response status is returned + const completed$ = getCompleted$(downloadPath); + const reportCompleted = await completed$.toPromise(); + expect(reportCompleted).to.match(/^"order_date",/); + }); + + it('should complete a job of PNG export of a dashboard in non-default space', async () => { + const downloadPath = await reportingAPI.postJob( + `/s/non_default_space/api/reporting/generate/png?jobParams=%28browserTimezone%3AUTC%2Clayout%3A%28dimensions%3A%28height%3A512%2Cwidth%3A2402%29%2Cid%3Apng%29%2CobjectType%3Adashboard%2CrelativeUrl%3A%27%2Fs%2Fnon_default_space%2Fapp%2Fdashboards%23%2Fview%2F3c9ee360-e7ee-11ea-a730-d58e9ea7581b%3F_g%3D%28filters%3A%21%21%28%29%2CrefreshInterval%3A%28pause%3A%21%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3A%21%272019-06-10T03%3A17%3A28.800Z%21%27%2Cto%3A%21%272019-07-14T19%3A25%3A06.385Z%21%27%29%29%26_a%3D%28description%3A%21%27%21%27%2Cfilters%3A%21%21%28%29%2CfullScreenMode%3A%21%21f%2Coptions%3A%28hidePanelTitles%3A%21%21f%2CuseMargins%3A%21%21t%29%2Cquery%3A%28language%3Akuery%2Cquery%3A%21%27%21%27%29%2CtimeRestore%3A%21%21t%2Ctitle%3A%21%27Ecom%2520Dashboard%2520Non%2520Default%2520Space%21%27%2CviewMode%3Aview%29%27%2Ctitle%3A%27Ecom%20Dashboard%20Non%20Default%20Space%27%29` + ); + + const completed$: Rx.Observable = getCompleted$(downloadPath); + const reportCompleted = await completed$.toPromise(); + expect(reportCompleted).to.not.be(null); + }); + + it('should complete a job of PDF export of a dashboard in non-default space', async () => { + const downloadPath = await reportingAPI.postJob( + `/s/non_default_space/api/reporting/generate/printablePdf?jobParams=%28browserTimezone%3AUTC%2Clayout%3A%28dimensions%3A%28height%3A512%2Cwidth%3A2402%29%2Cid%3Apreserve_layout%29%2CobjectType%3Adashboard%2CrelativeUrls%3A%21%28%27%2Fs%2Fnon_default_space%2Fapp%2Fdashboards%23%2Fview%2F3c9ee360-e7ee-11ea-a730-d58e9ea7581b%3F_g%3D%28filters%3A%21%21%28%29%2CrefreshInterval%3A%28pause%3A%21%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3A%21%272019-06-10T03%3A17%3A28.800Z%21%27%2Cto%3A%21%272019-07-14T19%3A25%3A06.385Z%21%27%29%29%26_a%3D%28description%3A%21%27%21%27%2Cfilters%3A%21%21%28%29%2CfullScreenMode%3A%21%21f%2Coptions%3A%28hidePanelTitles%3A%21%21f%2CuseMargins%3A%21%21t%29%2Cquery%3A%28language%3Akuery%2Cquery%3A%21%27%21%27%29%2CtimeRestore%3A%21%21t%2Ctitle%3A%21%27Ecom%2520Dashboard%2520Non%2520Default%2520Space%21%27%2CviewMode%3Aview%29%27%29%2Ctitle%3A%27Ecom%20Dashboard%20Non%20Default%20Space%27%29` + ); + + const completed$ = getCompleted$(downloadPath); + const reportCompleted = await completed$.toPromise(); + expect(reportCompleted).to.not.be(null); + }); + }); +} diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts b/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts index 24e68b3917d6c..feda5c1386e98 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts @@ -21,7 +21,7 @@ export default function ({ getService }: FtrProviderContext) { const reportingAPI = getService('reportingAPI'); const usageAPI = getService('usageAPI'); - describe('reporting usage', () => { + describe('Usage', () => { before(async () => { await esArchiver.load(OSS_KIBANA_ARCHIVE_PATH); await esArchiver.load(OSS_DATA_ARCHIVE_PATH); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 3e04a507d3810..9a3489e9309bf 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import Url from 'url'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../services/endpoint_policy'; @@ -18,6 +19,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); const testSubjects = getService('testSubjects'); const policyTestResources = getService('policyTestResources'); + const config = getService('config'); + const kbnTestServer = config.get('servers.kibana'); + const { protocol, hostname, port } = kbnTestServer; + + const kibanaUrl = Url.format({ + protocol, + hostname, + port, + }); describe('When on the Endpoint Policy Details Page', function () { this.tags(['ciGroup7']); @@ -142,6 +152,42 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { relative_url: '/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', }, + 'endpoint-trustlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, }, // The manifest version could have changed when the Policy was updated because the // policy details page ensures that a save action applies the udpated policy on top @@ -186,6 +232,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'elasticsearch', }, }, + fleet: { + kibana: { + hosts: [kibanaUrl], + }, + }, revision: 3, agent: { monitoring: { diff --git a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts index 7f831973aea5c..e567828598b31 100644 --- a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts +++ b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import axios, { AxiosInstance } from 'axios'; -import { UICapabilities } from 'ui/capabilities'; +import type { Capabilities as UICapabilities } from 'src/core/types'; import { format as formatUrl } from 'url'; import util from 'util'; import { ToolingLog } from '@kbn/dev-utils'; diff --git a/x-pack/test_utils/jest/config.js b/x-pack/test_utils/jest/config.js index 7bb073023b7f8..c94fe02d2f4bd 100644 --- a/x-pack/test_utils/jest/config.js +++ b/x-pack/test_utils/jest/config.js @@ -17,7 +17,6 @@ export default { ], collectCoverageFrom: ['legacy/plugins/**/*.js', 'legacy/common/**/*.js', 'legacy/server/**/*.js'], moduleNameMapper: { - '^ui/(.*)': '**/public/$1', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/dev/jest/mocks/file_mock.js', '\\.(css|less|scss)$': '/../src/dev/jest/mocks/style_mock.js', diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index e978702a35634..7c6210bb9ce19 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -14,17 +14,13 @@ "test/**/*", "plugins/security_solution/cypress/**/*", "plugins/apm/e2e/cypress/**/*", - "plugins/apm/scripts/**/*", - "**/typespec_tests.ts" + "plugins/apm/scripts/**/*" ], "compilerOptions": { "outDir": ".", "paths": { "kibana/public": ["src/core/public"], "kibana/server": ["src/core/server"], - "ui/*": [ - "src/legacy/ui/public/*" - ], "plugins/xpack_main/*": [ "x-pack/legacy/plugins/xpack_main/public/*" ], diff --git a/yarn.lock b/yarn.lock index 496331aba0485..3be2599c64201 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1144,10 +1144,10 @@ dependencies: "@elastic/apm-rum-core" "^5.6.0" -"@elastic/charts@19.8.1": - version "19.8.1" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-19.8.1.tgz#27653823911c26e4703c73588367473215beaf0f" - integrity sha512-vONCrcZ8bZ+C16+bKLoLyNrMC/b2UvYNoPbYcnB5XYAg5a68finvXEcWD6Y+qa7GLaO2CMe5J9eSjLWXHHDmLg== +"@elastic/charts@21.0.1": + version "21.0.1" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-21.0.1.tgz#1e8be5303d0c7d53d7ccfcfa121ef68164ae200f" + integrity sha512-KMJE2JNwoy021Rvhgu1wiga0FVCa0u4NlFVUSR+h+G010KLarc+c9yUKBTG8v/nZ6ijBtuOLCjjU9OCWXYfxvA== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" @@ -1229,10 +1229,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@27.4.1": - version "27.4.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-27.4.1.tgz#9803f6f6c6997c769720381f521e5319ef07a599" - integrity sha512-eGztErFlvkwI4BFxDs6tFBGFiN15NQdJdRHvSi5caOQFGabuR8XCRNUWcNFHlwKVJaDaGsJRuwbbSLh9XdWubQ== +"@elastic/eui@28.2.0": + version "28.2.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-28.2.0.tgz#0c89c7adc27d4ac918191af8237a2634a26d0637" + integrity sha512-ovVkwbwcJLs87ycaof5pyTGjpuPH2sN38k2e+c3IiZjoeGAqWZJouptQguoKYglCyayxoBUV3TAE82m+qyxCnA== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.116" @@ -1241,6 +1241,7 @@ "@types/react-input-autosize" "^2.0.2" "@types/react-virtualized-auto-sizer" "^1.0.0" "@types/react-window" "^1.8.1" + "@types/vfile-message" "^2.0.0" chroma-js "^2.0.4" classnames "^2.2.5" highlight.js "^9.12.0" @@ -1251,15 +1252,25 @@ prop-types "^15.6.0" react-ace "^7.0.5" react-beautiful-dnd "^13.0.0" - react-focus-on "^3.4.1" + react-dropzone "^10.2.1" + react-focus-on "^3.5.0" react-input-autosize "^2.2.2" react-is "~16.3.0" react-virtualized-auto-sizer "^1.0.2" react-window "^1.8.5" + rehype-raw "^4.0.1" + rehype-react "^6.0.0" + rehype-stringify "^6.0.1" + remark-emoji "^2.1.0" + remark-highlight.js "^5.2.0" + remark-parse "^7.0.2" + remark-rehype "^7.0.0" resize-observer-polyfill "^1.5.0" tabbable "^3.0.0" text-diff "^1.0.1" + unified "^8.4.2" uuid "^3.1.0" + vfile "^4.1.1" "@elastic/filesaver@1.1.2": version "1.1.2" @@ -2182,6 +2193,13 @@ vfile "^4.0.0" vfile-reporter "^5.1.1" +"@mapbox/hast-util-table-cell-style@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@mapbox/hast-util-table-cell-style/-/hast-util-table-cell-style-0.1.3.tgz#5b7166ae01297d72216932b245e4b2f0b642dca6" + integrity sha512-QsEsh5YaDvHoMQ2YHdvZy2iDnU3GgKVBTcHf6cILyoWDZtPSdlG444pL/ioPYO/GpXSfODBb9sefEetfC4v9oA== + dependencies: + unist-util-visit "^1.3.0" + "@mapbox/jsonlint-lines-primitives@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234" @@ -3643,13 +3661,6 @@ resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.43.tgz#e9b4992817e0b6c5efaa7d6e5bb2cee4d73eab58" integrity sha512-t9ZmXOcpVxywRw86YtIC54g7M9puRh8hFedRvVfHKf5YyOP6pSxA0TvpXpfseXSCInoW4P7bggTrSDiUOs4g5w== -"@types/decompress@^4.2.3": - version "4.2.3" - resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.3.tgz#98eed48af80001038aa05690b2094915f296fe65" - integrity sha512-W24e3Ycz1UZPgr1ZEDHlK4XnvOr+CpJH3qNsFeqXwwlW/9END9gxn3oJSsp7gYdiQxrXUHwUUd3xuzVz37MrZQ== - dependencies: - "@types/node" "*" - "@types/dedent@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@types/dedent/-/dedent-0.7.0.tgz#155f339ca404e6dd90b9ce46a3f78fd69ca9b050" @@ -3716,6 +3727,11 @@ resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== +"@types/extract-zip@^1.6.2": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@types/extract-zip/-/extract-zip-1.6.2.tgz#5c7eb441c41136167a42b88b64051e6260c29e86" + integrity sha1-XH60QcQRNhZ6QriLZAUeYmDCnoY= + "@types/fancy-log@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.1.tgz#dd94fbc8c2e2ab8ab402ca8d04bb8c34965f0696" @@ -4151,6 +4167,13 @@ dependencies: "@types/node" "*" +"@types/mdast@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" + integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== + dependencies: + "@types/unist" "*" + "@types/memoize-one@^4.1.0": version "4.1.1" resolved "https://registry.yarnpkg.com/@types/memoize-one/-/memoize-one-4.1.1.tgz#41dd138a4335b5041f7d8fc038f9d593d88b3369" @@ -4833,7 +4856,7 @@ dependencies: "@types/undertaker-registry" "*" -"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== @@ -4865,6 +4888,13 @@ "@types/node" "*" "@types/unist" "*" +"@types/vfile-message@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-2.0.0.tgz#690e46af0fdfc1f9faae00cd049cc888957927d5" + integrity sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw== + dependencies: + vfile-message "*" + "@types/vfile@^3.0.0": version "3.0.2" resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-3.0.2.tgz#19c18cd232df11ce6fa6ad80259bc86c366b09b9" @@ -6577,6 +6607,11 @@ attr-accept@^1.1.3: dependencies: core-js "^2.5.0" +attr-accept@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.1.tgz#89b48de019ed4342f1865626b4389c666b3ed231" + integrity sha512-GpefLMsbH5ojNgfTW+OBin2xKzuHfyeNA+qCktzZojBhbA/lPZdCFMWdwk5ajb989Ok7ZT+EADqvW3TAFNMjhA== + autobind-decorator@^1.3.4: version "1.4.3" resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-1.4.3.tgz#4c96ffa77b10622ede24f110f5dbbf56691417d1" @@ -7343,19 +7378,14 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" - integrity sha1-ysMo977kVzDUBLaSID/LWQ4XLV4= - dependencies: - readable-stream "^2.0.5" - -bl@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" - integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== +bl@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" + integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== dependencies: - readable-stream "^3.0.1" + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" block-stream@*: version "0.0.9" @@ -7717,19 +7747,6 @@ btoa-lite@^1.0.0: resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -7750,11 +7767,6 @@ buffer-equal@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -7787,7 +7799,7 @@ buffer@^5.1.0, buffer@^5.2.0: base64-js "^1.0.2" ieee754 "^1.1.4" -buffer@^5.2.1: +buffer@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== @@ -8163,10 +8175,10 @@ catbox@10.x.x: hoek "5.x.x" joi "13.x.x" -ccount@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" - integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw== +ccount@^1.0.0, ccount@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" + integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== center-align@^0.1.1: version "0.1.3" @@ -8293,6 +8305,11 @@ change-emitter@^0.1.2: resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= +character-entities-html4@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" + integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== + character-entities-legacy@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.1.tgz#f40779df1a101872bb510a3d295e1fccf147202f" @@ -8450,7 +8467,7 @@ chokidar@^3.2.2, chokidar@^3.4.0: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2: +chownr@^1.1.1, chownr@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -8851,6 +8868,11 @@ coffeescript@~1.10.0: resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.10.0.tgz#e7aa8301917ef621b35d8a39f348dcdd1db7e33e" integrity sha1-56qDAZF+9iGzXYo580jc3R234z4= +collapse-white-space@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + collapse-white-space@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c" @@ -9008,7 +9030,12 @@ comma-separated-tokens@^1.0.0: dependencies: trim "0.0.1" -commander@2, commander@2.19.0, commander@^2.11.0, commander@^2.12.2: +comma-separated-tokens@^1.0.1: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + +commander@2, commander@2.19.0, commander@^2.11.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -10334,59 +10361,6 @@ decompress-response@^5.0.0: dependencies: mimic-response "^2.0.0" -decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" - integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== - dependencies: - file-type "^5.2.0" - is-stream "^1.1.0" - tar-stream "^1.5.2" - -decompress-tarbz2@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" - integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== - dependencies: - decompress-tar "^4.1.0" - file-type "^6.1.0" - is-stream "^1.1.0" - seek-bzip "^1.0.5" - unbzip2-stream "^1.0.9" - -decompress-targz@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" - integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== - dependencies: - decompress-tar "^4.1.1" - file-type "^5.2.0" - is-stream "^1.1.0" - -decompress-unzip@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" - integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k= - dependencies: - file-type "^3.8.0" - get-stream "^2.2.0" - pify "^2.3.0" - yauzl "^2.4.2" - -decompress@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118" - integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== - dependencies: - decompress-tar "^4.0.0" - decompress-tarbz2 "^4.0.0" - decompress-targz "^4.0.0" - decompress-unzip "^4.0.1" - graceful-fs "^4.1.10" - make-dir "^1.0.0" - pify "^2.3.0" - strip-dirs "^2.0.0" - dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -10684,6 +10658,13 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.3.tgz#33e5dd74d230501bd69985a0d2b9a3382699a130" + integrity sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A== + dependencies: + repeat-string "^1.5.4" + detect-conflict@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e" @@ -11381,6 +11362,11 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +emoticon@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-3.2.0.tgz#c008ca7d7620fac742fe1bf4af8ff8fed154ae7f" + integrity sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg== + emotion-theming@^10.0.19: version "10.0.27" resolved "https://registry.yarnpkg.com/emotion-theming/-/emotion-theming-10.0.27.tgz#1887baaec15199862c89b1b984b79806f2b9ab10" @@ -12687,10 +12673,10 @@ extract-zip@1.7.0, extract-zip@^1.6.6, extract-zip@^1.7.0: mkdirp "^0.5.4" yauzl "^2.10.0" -extract-zip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" - integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== +extract-zip@^2.0.0, extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== dependencies: debug "^4.1.1" get-stream "^5.1.0" @@ -12810,7 +12796,7 @@ fastq@^1.6.0: dependencies: reusify "^1.0.0" -fault@^1.0.2: +fault@^1.0.0, fault@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== @@ -12939,6 +12925,13 @@ file-saver@^1.3.8: resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" integrity sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg== +file-selector@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0" + integrity sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ== + dependencies: + tslib "^1.9.0" + file-sync-cmp@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz#a5e7a8ffbfa493b43b923bbd4ca89a53b63b612b" @@ -12958,21 +12951,6 @@ file-type@^10.9.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-10.9.0.tgz#f6c12c7cb9e6b8aeefd6917555fd4f9eadf31891" integrity sha512-9C5qtGR/fNibHC5gzuMmmgnjH3QDDLKMa8lYe9CiZVmAnI4aUaoMh40QyUPzzs0RYo837SOBKh7TYwle4G8E4w== -file-type@^3.8.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" - integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek= - -file-type@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" - integrity sha1-LdvqfHP/42No365J3DOMBYwritY= - -file-type@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" - integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== - file-type@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18" @@ -13778,14 +13756,6 @@ get-stream@3.0.0, get-stream@^3.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4= - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" - get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -14394,7 +14364,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4: +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -15094,6 +15064,31 @@ hasha@^5.0.0: is-stream "^2.0.0" type-fest "^0.8.0" +hast-to-hyperscript@^7.0.0: + version "7.0.4" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-7.0.4.tgz#7c4c037d9a8ea19b0a3fdb676a26448ad922353d" + integrity sha512-vmwriQ2H0RPS9ho4Kkbf3n3lY436QKLq6VaGA1pzBh36hBi3tm1DO9bR+kaJIbpT10UqaANDkMjxvjVfr+cnOA== + dependencies: + comma-separated-tokens "^1.0.0" + property-information "^5.3.0" + space-separated-tokens "^1.0.0" + style-to-object "^0.2.1" + unist-util-is "^3.0.0" + web-namespaces "^1.1.2" + +hast-to-hyperscript@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.0.tgz#768fb557765fe28749169c885056417342d71e83" + integrity sha512-NJvMYU3GlMLs7hN3CRbsNlMzusVNkYBogVWDGybsuuVQ336gFLiD+q9qtFZT2meSHzln3pNISZWTASWothMSMg== + dependencies: + "@types/unist" "^2.0.3" + comma-separated-tokens "^1.0.0" + property-information "^5.3.0" + space-separated-tokens "^1.0.0" + style-to-object "^0.3.0" + unist-util-is "^4.0.0" + web-namespaces "^1.0.0" + hast-util-from-parse5@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-5.0.0.tgz#a505a05766e0f96e389bfb0b1dd809eeefcef47b" @@ -15105,11 +15100,62 @@ hast-util-from-parse5@^5.0.0: web-namespaces "^1.1.2" xtend "^4.0.1" +hast-util-is-element@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.0.4.tgz#059090a05cc02e275df1ad02caf8cb422fcd2e02" + integrity sha512-NFR6ljJRvDcyPP5SbV7MyPBgF47X3BsskLnmw1U34yL+X6YC0MoBx9EyMg8Jtx4FzGH95jw8+c1VPLHaRA0wDQ== + hast-util-parse-selector@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.1.tgz#4ddbae1ae12c124e3eb91b581d2556441766f0ab" integrity sha512-Xyh0v+nHmQvrOqop2Jqd8gOdyQtE8sIP9IQf7mlVDqp924W4w/8Liuguk2L2qei9hARnQSG2m+wAOCxM7npJVw== +hast-util-raw@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-5.0.2.tgz#62288f311ec2f35e066a30d5e0277f963ad43a67" + integrity sha512-3ReYQcIHmzSgMq8UrDZHFL0oGlbuVGdLKs8s/Fe8BfHFAyZDrdv1fy/AGn+Fim8ZuvAHcJ61NQhVMtyfHviT/g== + dependencies: + hast-util-from-parse5 "^5.0.0" + hast-util-to-parse5 "^5.0.0" + html-void-elements "^1.0.0" + parse5 "^5.0.0" + unist-util-position "^3.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hast-util-to-html@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-6.1.0.tgz#86bcd19c3bd46af456984f8f34db16298c2b10b0" + integrity sha512-IlC+LG2HGv0Y8js3wqdhg9O2sO4iVpRDbHOPwXd7qgeagpGsnY49i8yyazwqS35RA35WCzrBQE/n0M6GG/ewxA== + dependencies: + ccount "^1.0.0" + comma-separated-tokens "^1.0.1" + hast-util-is-element "^1.0.0" + hast-util-whitespace "^1.0.0" + html-void-elements "^1.0.0" + property-information "^5.2.0" + space-separated-tokens "^1.0.0" + stringify-entities "^2.0.0" + unist-util-is "^3.0.0" + xtend "^4.0.1" + +hast-util-to-parse5@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-5.1.2.tgz#09d27bee9ba9348ea05a6cfcc44e02f9083969b6" + integrity sha512-ZgYLJu9lYknMfsBY0rBV4TJn2xiwF1fXFFjbP6EE7S0s5mS8LIKBVWzhA1MeIs1SWW6GnnE4In6c3kPb+CWhog== + dependencies: + hast-to-hyperscript "^7.0.0" + property-information "^5.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hast-util-whitespace@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" + integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== + hastscript@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-5.0.0.tgz#fee10382c1bc4ba3f1be311521d368c047d2c43a" @@ -15162,6 +15208,11 @@ highlight.js@^9.12.0, highlight.js@~9.12.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" integrity sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4= +highlight.js@~10.1.0: + version "10.1.2" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.1.2.tgz#c20db951ba1c22c055010648dfffd7b2a968e00c" + integrity sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA== + highlight.js@~9.13.0: version "9.13.1" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" @@ -15311,6 +15362,11 @@ html-to-react@^1.3.4: lodash.camelcase "^4.3.0" ramda "^0.26" +html-void-elements@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" + integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== + html-webpack-plugin@^4.0.0-beta.2: version "4.0.0-beta.5" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz#2c53083c1151bfec20479b1f8aaf0039e77b5513" @@ -15830,6 +15886,11 @@ ini@^1.2.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + inline-style-prefixer@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911" @@ -16280,6 +16341,11 @@ is-decimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.1.tgz#f5fb6a94996ad9e8e3761fbfbd091f1fca8c4e82" integrity sha1-9ftqlJlq2ejjdh+/vQkfH8qMToI= +is-decimal@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -16483,11 +16549,6 @@ is-native@^1.0.1: is-nil "^1.0.0" to-source-code "^1.0.0" -is-natural-number@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" - integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= - is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" @@ -16612,6 +16673,11 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-object@2.0.4, is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -18099,10 +18165,10 @@ kdbush@^3.0.0: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== -kea@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/kea/-/kea-2.1.1.tgz#6e3c65c4873b67d270a2ec7bf73b0d178937234c" - integrity sha512-W9o4lHLOcEDIu3ASHPrWJJJzL1bMkGyxaHn9kuaDgI96ztBShVrf52R0QPGlQ2k9ca3XnkB/dnVHio1UB8kGWA== +kea@2.2.0-rc.4: + version "2.2.0-rc.4" + resolved "https://registry.yarnpkg.com/kea/-/kea-2.2.0-rc.4.tgz#cc0376950530a6751f73387c4b25a39efa1faa77" + integrity sha512-pYuwaCiJkBvHZShi8kqhk8dC4DjeELdK51Lw7Pn0tNdJgZJDF6COhsUiF/yrh9d7woNYDxKfuxH+QWZFfo8PkA== kew@~0.1.7: version "0.1.7" @@ -18898,6 +18964,11 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + lodash.unescape@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" @@ -19087,6 +19158,14 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lowlight@^1.2.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.14.0.tgz#83ebc143fec0f9e6c0d3deffe01be129ce56b108" + integrity sha512-N2E7zTM7r1CwbzwspPxJvmjAbxljCPThTFawEX2Z7+P3NGrrvY54u8kyU16IY4qWfoVIxY8SYCS8jTkuG7TqYA== + dependencies: + fault "^1.0.0" + highlight.js "~10.1.0" + lowlight@~1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc" @@ -19375,6 +19454,30 @@ mdast-add-list-metadata@1.0.1: dependencies: unist-util-visit-parents "1.1.2" +mdast-util-definitions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-3.0.1.tgz#06af6c49865fc63d6d7d30125569e2f7ae3d0a86" + integrity sha512-BAv2iUm/e6IK/b2/t+Fx69EL/AGcq/IG2S+HxHjDJGfLJtd6i9SZUS76aC9cig+IEucsqxKTR0ot3m933R3iuA== + dependencies: + unist-util-visit "^2.0.0" + +mdast-util-to-hast@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-9.1.0.tgz#6ef121dd3cd3b006bf8650b1b9454da0faf79ffe" + integrity sha512-Akl2Vi9y9cSdr19/Dfu58PVwifPXuFt1IrHe7l+Crme1KvgUT+5z+cHLVcQVGCiNTZZcdqjnuv9vPkGsqWytWA== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.3" + collapse-white-space "^1.0.0" + detab "^2.0.0" + mdast-util-definitions "^3.0.0" + mdurl "^1.0.0" + trim-lines "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -19385,7 +19488,7 @@ mdn-data@2.0.6: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== -mdurl@^1.0.1: +mdurl@^1.0.0, mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= @@ -19900,6 +20003,11 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -20438,6 +20546,13 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" +node-emoji@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== + dependencies: + lodash.toarray "^4.4.0" + node-environment-flags@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -22737,6 +22852,13 @@ property-information@^5.0.0, property-information@^5.0.1: dependencies: xtend "^4.0.1" +property-information@^5.2.0, property-information@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943" + integrity sha512-RgEbCx2HLa1chNgvChcx+rrCWD0ctBmGSE0M7lVm1yyv4UbvbrWoXp/BkVLZefzjrRBGW8/Js6uh/BnlHXFyjA== + dependencies: + xtend "^4.0.0" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -22823,14 +22945,6 @@ puid@1.0.7: resolved "https://registry.yarnpkg.com/puid/-/puid-1.0.7.tgz#fa638a737d7b20419059d93965aed36ca20e1c84" integrity sha1-+mOKc317IEGQWdk5Za7TbKIOHIQ= -pump@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" - integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -23374,6 +23488,15 @@ react-draggable@^4.0.3: classnames "^2.2.5" prop-types "^15.6.0" +react-dropzone@^10.2.1: + version "10.2.2" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.2.2.tgz#67b4db7459589a42c3b891a82eaf9ade7650b815" + integrity sha512-U5EKckXVt6IrEyhMMsgmHQiWTGLudhajPPG77KFSvgsMqNEHSyGpqWvOMc5+DhEah/vH4E1n+J5weBNLd5VtyA== + dependencies: + attr-accept "^2.0.0" + file-selector "^0.1.12" + prop-types "^15.7.2" + react-dropzone@^4.2.9: version "4.3.0" resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-4.3.0.tgz#facdd7db16509772633c9f5200621ac01aa6706f" @@ -23412,14 +23535,14 @@ react-focus-lock@^2.1.0, react-focus-lock@^2.3.1: use-callback-ref "^1.2.1" use-sidecar "^1.0.1" -react-focus-on@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/react-focus-on/-/react-focus-on-3.4.1.tgz#e184f3c44185e341598c5d9d44b2987ad459b240" - integrity sha512-KGRIl0iAu+1k1dcX7eQCXF5ZR/nl+XyXN5Ukw/OY80vLaK2b6vDzNqnX0HdYbY5xSUhIRUvMWEzSsdEyPjvk/Q== +react-focus-on@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/react-focus-on/-/react-focus-on-3.5.0.tgz#3cdebbefee26a083976e1700ba75f7377040f7f1" + integrity sha512-RqGAHOxhRAaMSVHIN5IpY7YL6AJkD/DMa/+iPDV7aB6XWRQfg3v2q35egIZgMWP2xhXaRVai3B80dpVWyj4Rcw== dependencies: aria-hidden "^1.1.1" react-focus-lock "^2.3.1" - react-remove-scroll "^2.3.0" + react-remove-scroll "^2.4.0" react-style-singleton "^2.1.0" use-callback-ref "^1.2.3" use-sidecar "^1.0.1" @@ -23632,10 +23755,10 @@ react-remove-scroll-bar@^2.1.0: react-style-singleton "^2.1.0" tslib "^1.0.0" -react-remove-scroll@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.3.0.tgz#3af06fe2f7130500704b676cdef94452c08fe593" - integrity sha512-UqVimLeAe+5EHXKfsca081hAkzg3WuDmoT9cayjBegd6UZVhlTEchleNp9J4TMGkb/ftLve7ARB5Wph+HJ7A5g== +react-remove-scroll@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.0.tgz#190c16eb508c5927595935499e8f5dd9ab0e75cf" + integrity sha512-BZIO3GaEs0Or1OhA5C//n1ibUP1HdjJmqUVUsOCMxwoIpaCocbB9TFKwHOkBa/nyYy3slirqXeiPYGwdSDiseA== dependencies: react-remove-scroll-bar "^2.1.0" react-style-singleton "^2.1.0" @@ -24064,7 +24187,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -"readable-stream@1 || 2", readable-stream@^2.3.0, readable-stream@^2.3.7, readable-stream@~2.3.3: +"readable-stream@1 || 2", readable-stream@^2.3.7, readable-stream@~2.3.3: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -24087,7 +24210,7 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0": isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.1, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: +"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== @@ -24569,6 +24692,29 @@ rehype-parse@^6.0.0: parse5 "^5.0.0" xtend "^4.0.1" +rehype-raw@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-4.0.2.tgz#5d3191689df96c8c651ce5f51a6c668d2c07b9c8" + integrity sha512-xQt94oXfDaO7sK9mJBtsZXkjW/jm6kArCoYN+HqKZ51O19AFHlp3Xa5UfZZ2tJkbpAZzKtgVUYvnconk9IsFuA== + dependencies: + hast-util-raw "^5.0.0" + +rehype-react@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/rehype-react/-/rehype-react-6.1.0.tgz#95f8c936eea2159f92adfbf58e5e90be86a97cbf" + integrity sha512-hQ4DSGOJKA1a87Ei4fJtSHzopbfgoHkwjWMCFpLrcVR5+AIyCOtHy4oQcpGF11kTZOU6oKmJ9UKzO/JpI/XZWA== + dependencies: + "@mapbox/hast-util-table-cell-style" "^0.1.3" + hast-to-hyperscript "^9.0.0" + +rehype-stringify@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-6.0.1.tgz#b6aa9f84d5276c5d247c62fc1ea15983a35a4575" + integrity sha512-JfEPRDD4DiG7jet4md7sY07v6ACeb2x+9HWQtRPm2iA6/ic31hCv1SNBUtpolJASxQ/D8gicXiviW4TJKEMPKQ== + dependencies: + hast-util-to-html "^6.0.0" + xtend "^4.0.0" + relateurl@0.2.x: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" @@ -24593,6 +24739,23 @@ release-zalgo@^1.0.0: dependencies: es6-error "^4.0.1" +remark-emoji@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.1.0.tgz#69165d1181b98a54ad5d9ef811003d53d7ebc7db" + integrity sha512-lDddGsxXURV01WS9WAiS9rO/cedO1pvr9tahtLhr6qCGFhHG4yZSJW3Ha4Nw9Uk1hLNmUBtPC0+m45Ms+xEitg== + dependencies: + emoticon "^3.2.0" + node-emoji "^1.10.0" + unist-util-visit "^2.0.2" + +remark-highlight.js@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/remark-highlight.js/-/remark-highlight.js-5.2.0.tgz#6d8d22085e0c76573744b7e3706fc232269f5b02" + integrity sha512-5tCr1CfdXDYzR8HCAnohlr1rK8DjUTFxEZdr+QEul5o13+EOEt5RrO8U6Znf8Faj5rVLcMJtxLPq6hHrZFo33A== + dependencies: + lowlight "^1.2.0" + unist-util-visit "^1.0.0" + remark-parse@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" @@ -24614,6 +24777,34 @@ remark-parse@^5.0.0: vfile-location "^2.0.0" xtend "^4.0.1" +remark-parse@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-7.0.2.tgz#41e7170d9c1d96c3d32cf1109600a9ed50dba7cf" + integrity sha512-9+my0lQS80IQkYXsMA8Sg6m9QfXYJBnXjWYN5U+kFc5/n69t+XZVXU/ZBYr3cYH8FheEGf1v87rkFDhJ8bVgMA== + dependencies: + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^1.1.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^1.0.0" + vfile-location "^2.0.0" + xtend "^4.0.1" + +remark-rehype@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-7.0.0.tgz#8e106e49806c69b2e9523b76d24965119e2da67b" + integrity sha512-uqQ/VbaTdxyu/da6npHAso6hA00cMqhA3a59RziQdOLN2KEIkPykAVy52IcmZEVTuauXO0VtpxkyCey4phtHzQ== + dependencies: + mdast-util-to-hast "^9.1.0" + remedial@^1.0.7: version "1.0.8" resolved "https://registry.yarnpkg.com/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0" @@ -25446,13 +25637,6 @@ seedrandom@^3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -seek-bzip@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" - integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== - dependencies: - commander "^2.8.1" - select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -26736,6 +26920,17 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +stringify-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-2.0.0.tgz#fa7ca6614b355fb6c28448140a20c4ede7462827" + integrity sha512-fqqhZzXyAM6pGD9lky/GOPq6V4X0SeTAFBl0iXb/BzOegl40gpf/bV3QQP7zULNYvjr6+Dx8SCaDULjVoOru0A== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.2" + is-hexadecimal "^1.0.0" + stringify-object@3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd" @@ -26824,13 +27019,6 @@ strip-bom@^4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-dirs@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" - integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== - dependencies: - is-natural-number "^4.0.1" - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -26912,6 +27100,20 @@ style-loader@^1.1.3: loader-utils "^1.2.3" schema-utils "^2.6.4" +style-to-object@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.2.3.tgz#afcf42bc03846b1e311880c55632a26ad2780bcb" + integrity sha512-1d/k4EY2N7jVLOqf2j04dTc37TPOv/hHxZmvpg8Pdh8UYydxeu/C1W1U4vD8alzf5V2Gt7rLsmkr4dxAlDm9ng== + dependencies: + inline-style-parser "0.1.1" + +style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" + styled-components@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.1.0.tgz#2e3985b54f461027e1c91af3229e1c2530872a4e" @@ -27286,45 +27488,22 @@ tape@^5.0.1: string.prototype.trim "^1.2.1" through "^2.3.8" -tar-fs@^1.16.3: - version "1.16.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" - integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== - dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" - -tar-stream@^1.1.2: - version "1.5.5" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" - integrity sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg== - dependencies: - bl "^1.0.0" - end-of-stream "^1.0.0" - readable-stream "^2.0.0" - xtend "^4.0.0" - -tar-stream@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" - integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== +tar-fs@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5" + integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg== dependencies: - bl "^1.0.0" - buffer-alloc "^1.2.0" - end-of-stream "^1.0.0" - fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.1" - xtend "^4.0.0" + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" -tar-stream@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" - integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== +tar-stream@^2.0.0, tar-stream@^2.1.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41" + integrity sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA== dependencies: - bl "^3.0.0" + bl "^4.0.1" end-of-stream "^1.4.1" fs-constants "^1.0.0" inherits "^2.0.3" @@ -27748,11 +27927,6 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-buffer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== - to-camel-case@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46" @@ -27944,6 +28118,11 @@ treeify@^1.0.1, treeify@^1.1.0: resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== +trim-lines@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.3.tgz#839514be82428fd9e7ec89e35081afe8f6f93115" + integrity sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA== + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -28244,13 +28423,6 @@ typescript@4.0.2, typescript@^3.0.1, typescript@^3.0.3, typescript@^3.2.2, types resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== -typings-tester@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/typings-tester/-/typings-tester-0.3.2.tgz#04cc499d15ab1d8b2d14dd48415a13d01333bc5b" - integrity sha512-HjGoAM2UoGhmSKKy23TYEKkxlphdJFdix5VvqWFLzH1BJVnnwG38tpC6SXPgqhfFGfHY77RlN1K8ts0dbWBQ7A== - dependencies: - commander "^2.12.2" - ua-parser-js@^0.7.18: version "0.7.21" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" @@ -28301,14 +28473,6 @@ uid-safe@2.1.5: dependencies: random-bytes "~1.0.0" -unbzip2-stream@^1.0.9: - version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -28467,6 +28631,17 @@ unified@^7.1.0: vfile "^3.0.0" x-is-string "^0.1.0" +unified@^8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-8.4.2.tgz#13ad58b4a437faa2751a4a4c6a16f680c500fff1" + integrity sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -28518,11 +28693,36 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" +unist-builder@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" + integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== + +unist-util-generated@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.5.tgz#1e903e68467931ebfaea386dae9ea253628acd42" + integrity sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw== + unist-util-is@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b" integrity sha1-DDEmKeP5YMZukx6BLT2A53AQlHs= +unist-util-is@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" + integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== + +unist-util-is@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de" + integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ== + +unist-util-position@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" + integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== + unist-util-remove-position@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.1.tgz#5a85c1555fc1ba0c101b86707d15e50fa4c871bb" @@ -28547,6 +28747,28 @@ unist-util-visit-parents@1.1.2: resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06" integrity sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q== +unist-util-visit-parents@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" + integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g== + dependencies: + unist-util-is "^3.0.0" + +unist-util-visit-parents@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz#4dd262fb9dcfe44f297d53e882fc6ff3421173d5" + integrity sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" + integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw== + dependencies: + unist-util-visit-parents "^2.0.0" + unist-util-visit@^1.1.0, unist-util-visit@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.3.0.tgz#41ca7c82981fd1ce6c762aac397fc24e35711444" @@ -28554,6 +28776,15 @@ unist-util-visit@^1.1.0, unist-util-visit@^1.3.0: dependencies: unist-util-is "^2.1.1" +unist-util-visit@^2.0.0, unist-util-visit@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + universal-user-agent@^2.0.0, universal-user-agent@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-2.0.3.tgz#9f6f09f9cc33de867bb720d84c08069b14937c6c" @@ -29327,6 +29558,14 @@ vfile-location@^2.0.0: resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.2.tgz#d3675c59c877498e492b4756ff65e4af1a752255" integrity sha1-02dcWch3SY5JK0dW/2Xkrxp1IlU= +vfile-message@*: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.0.0.tgz#a6adb0474ea400fa25d929f1d673abea6a17e359" @@ -29395,6 +29634,17 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" +vfile@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" + integrity sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + replace-ext "1.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + vinyl-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a" @@ -29640,6 +29890,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-namespaces@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" + integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== + web-namespaces@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.2.tgz#c8dc267ab639505276bae19e129dbd6ae72b22b4" @@ -30598,7 +30853,7 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" -yauzl@2.10.0, yauzl@^2.10.0, yauzl@^2.4.2: +yauzl@2.10.0, yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= @@ -30814,3 +31069,8 @@ zlib@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA= + +zwitch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==