diff --git a/.buildkite/scripts/common/env.sh b/.buildkite/scripts/common/env.sh index 901fa9f50a61f7..b1701603ec084f 100755 --- a/.buildkite/scripts/common/env.sh +++ b/.buildkite/scripts/common/env.sh @@ -70,3 +70,4 @@ export TEST_ES_URL="http://elastic:changeme@localhost:6102" export TEST_ES_TRANSPORT_PORT=6301-6309 export TEST_CORS_SERVER_PORT=6106 export ALERTING_PROXY_PORT=6105 +export TEST_PROXY_SERVER_PORT=6107 diff --git a/docs/api/spaces-management.asciidoc b/docs/api/spaces-management.asciidoc index 2e3b9abec9120b..333a06cf3754e5 100644 --- a/docs/api/spaces-management.asciidoc +++ b/docs/api/spaces-management.asciidoc @@ -20,6 +20,8 @@ The following {kib} spaces APIs are available: * <> to overwrite saved objects returned as errors from the copy saved objects to space API +* <> to disable legacy URL aliases if an error is encountered + include::spaces-management/post.asciidoc[] include::spaces-management/put.asciidoc[] include::spaces-management/get.asciidoc[] @@ -27,3 +29,4 @@ include::spaces-management/get_all.asciidoc[] include::spaces-management/delete.asciidoc[] include::spaces-management/copy_saved_objects.asciidoc[] include::spaces-management/resolve_copy_saved_objects_conflicts.asciidoc[] +include::spaces-management/disable_legacy_url_aliases.asciidoc[] diff --git a/docs/api/spaces-management/disable_legacy_url_aliases.asciidoc b/docs/api/spaces-management/disable_legacy_url_aliases.asciidoc new file mode 100644 index 00000000000000..3f713d9d0c25e2 --- /dev/null +++ b/docs/api/spaces-management/disable_legacy_url_aliases.asciidoc @@ -0,0 +1,59 @@ +[[spaces-api-disable-legacy-url-aliases]] +=== Disable legacy URL aliases API +++++ +Disable legacy URL aliases +++++ + +experimental[] Disable a <> in {kib}. + +[[spaces-api-disable-legacy-url-aliases-request]] +==== {api-request-title} + +`POST :/api/spaces/_disable_legacy_url_aliases` + +[role="child_attributes"] +[[spaces-api-disable-legacy-url-aliases-request-body]] +==== {api-request-body-title} + +`aliases`:: + (Required, object array) The aliases to disable. ++ +.Properties of `aliases` +[%collapsible%open] +===== + `targetSpace`::: + (Required, string) The space where the alias target object exists. + + `targetType`::: + (Required, string) The type of the alias target object. + + `sourceId`::: + (Required, string) The ID of the alias source object. This is the "legacy" object ID. +===== + +[[spaces-api-disable-legacy-url-aliases-response-codes]] +==== {api-response-codes-title} + +`204`:: + Indicates a successful call. + +[[spaces-api-disable-legacy-url-aliases-example]] +==== {api-examples-title} + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/spaces/_disable_legacy_url_aliases +{ + "aliases": [ + { + "targetSpace": "bills-space", + "targetType": "dashboard", + "sourceId": "123" + } + ] +} +-------------------------------------------------- +// KIBANA + +This example leaves the alias intact, but the legacy URL for this alias, http://localhost:5601/s/bills-space/app/dashboards#/view/123, will +no longer function. The dashboard still exists, and you can access it with the new URL. \ No newline at end of file diff --git a/docs/developer/advanced/index.asciidoc b/docs/developer/advanced/index.asciidoc index 289b88cddd7a90..27072d85b65075 100644 --- a/docs/developer/advanced/index.asciidoc +++ b/docs/developer/advanced/index.asciidoc @@ -6,6 +6,7 @@ * <> * <> * <> +* <> include::development-es-snapshots.asciidoc[leveloffset=+1] @@ -15,4 +16,6 @@ include::development-basepath.asciidoc[leveloffset=+1] include::upgrading-nodejs.asciidoc[leveloffset=+1] -include::sharing-saved-objects.asciidoc[leveloffset=+1] \ No newline at end of file +include::sharing-saved-objects.asciidoc[leveloffset=+1] + +include::legacy-url-aliases.asciidoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/developer/advanced/legacy-url-aliases.asciidoc b/docs/developer/advanced/legacy-url-aliases.asciidoc new file mode 100644 index 00000000000000..3e441dd5821233 --- /dev/null +++ b/docs/developer/advanced/legacy-url-aliases.asciidoc @@ -0,0 +1,45 @@ +[[legacy-url-aliases]] +== Legacy URL Aliases + +This page describes legacy URL aliases: what they are, where they come from, and how to disable them. + +[[legacy-url-aliases-overview]] +=== Overview + +Many saved object types were converted in {kib} 8.0, so they can eventually be shared across <>. Before 8.0, you could +have two objects with the same type and same ID in two different spaces. Part of this conversion is to make sure all object IDs of a given +type are *globally unique across all spaces*. + +{kib} creates a special entity called a **legacy URL alias** for each saved object that requires a new ID. This legacy URL alias allows +{kib} to preserve any deep link URLs that exist for these objects. + +[[legacy-url-aliases-example]] +=== Example + +Consider the following scenario: + +You have {kib} 7.16, and you create a new dashboard.The ID of this dashboard is "123". You create a new space called "Bill's space" and +<> your dashboard to the other space. Now you have two different dashboards that can be accessed +at the following URLs: + +* *Default space*: `http://localhost:5601/app/dashboards#/view/123` +* *Bill's space*: `http://localhost:5601/s/bills-space/app/dashboards#/view/123` + +You use these two dashboards frequently, so you bookmark them in your web browser. After some time, you decide to upgrade to {kib} 8.0. When +these two dashboards go through the conversion process, the one in "Bill's space" will have its ID changed to "456". The URL to access that +dashboard is different -- not to worry though, there is a legacy URL alias for that dashboard. + +If you use your bookmark to access that dashboard using its old URL, {kib} detects that you are using a legacy URL, and finds the new object +ID. If you navigate to `http://localhost:5601/s/bills-space/app/dashboards#/view/123`, you'll see a message indicating that the dashboard +has a new URL, and you're automatically redirected to `http://localhost:5601/s/bills-space/app/dashboards#/view/456`. + +[[legacy-url-aliases-handling-errors]] +=== Handling errors + +Legacy URL aliases are intended to be fully transparent, but there are rare situations where this can lead to an error. For example, you +might have a dashboard and one of the visualizations fails to load, directing you to this page. If you encounter an error in this situation, +you might want to disable the legacy URL alias completely. This leaves the saved object intact, and you will not lose any data -- you just +won't be able to use the old URL to access that saved object. + +To disable a legacy URL alias, you need three pieces of information: the `targetSpace`, the `targetType`, and the `sourceId`. Then use the +<> API to disable the problematic legacy URL alias. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index a0611b79aae4c4..ac50062470d78c 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -371,7 +371,7 @@ The size limit policy will perform a rollover when the log file reaches a maximu The time interval policy will rotate the log file every given interval of time. *Default 24h* -| [[regionmap-ES-map]] `map.includeElasticMapsService:` {ess-icon} +| `map.includeElasticMapsService:` {ess-icon} | Set to `false` to disable connections to Elastic Maps Service. When `includeElasticMapsService` is turned off, only tile layer configured by <> is available in <>. *Default: `true`* @@ -383,88 +383,6 @@ When `includeElasticMapsService` is turned off, only tile layer configured by << Set to `true` to proxy all <> Elastic Maps Service requests through the {kib} server. *Default: `false`* -| [[regionmap-settings]] `map.regionmap:` {ess-icon} - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] - Specifies additional vector layers for -use in <> visualizations. Each layer -object points to an external vector file that contains a geojson -FeatureCollection. The file must use the -https://en.wikipedia.org/wiki/World_Geodetic_System[WGS84 coordinate reference system (ESPG:4326)] -and only include polygons. If the file is hosted on a separate domain from -{kib}, the server needs to be CORS-enabled so {kib} can download the file. -The following example shows a valid region map configuration. - -|=== - -[source,text] --- -map.regionmap: - layers: - - name: "Departments of France" - url: "http://my.cors.enabled.server.org/france_departements.geojson" - attribution: "INRAP" - fields: - - name: "department" - description: "Full department name" - - name: "INSEE" - description: "INSEE numeric identifier" --- - -[cols="2*<"] -|=== - -| [[regionmap-attribution]] `map.regionmap.layers[].attribution:` {ess-icon} - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] - Optional. References the originating source of the geojson file. - -| [[regionmap-fields]] `map.regionmap.layers[].fields[]:` {ess-icon} - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] - Mandatory. Each layer -can contain multiple fields to indicate what properties from the geojson -features you wish to expose. The following shows how to define multiple -properties: - -|=== - -[source,text] --- -map.regionmap: - layers: - - name: "Departments of France" - url: "http://my.cors.enabled.server.org/france_departements.geojson" - attribution: "INRAP" - fields: - - name: "department" - description: "Full department name" - - name: "INSEE" - description: "INSEE numeric identifier" --- - -[cols="2*<"] -|=== - -| [[regionmap-field-description]] `map.regionmap.layers[].fields[].description:` {ess-icon} - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] - Mandatory. The human readable text that is shown under the Options tab when -building the Region Map visualization. - -| [[regionmap-field-name]] `map.regionmap.layers[].fields[].name:` {ess-icon} - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] - Mandatory. -This value is used to do an inner-join between the document stored in -{es} and the geojson file. For example, if the field in the geojson is -called `Location` and has city names, there must be a field in {es} -that holds the same values that {kib} can then use to lookup for the geoshape -data. - -| [[regionmap-name]] `map.regionmap.layers[].name:` {ess-icon} - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] - Mandatory. A description of the map being provided. - -| [[regionmap-url]] `map.regionmap.layers[].url:` {ess-icon} - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] - Mandatory. The location of the geojson file as provided by a webserver. - | [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon} | The map attribution string. *Default: `"© [Elastic Maps Service](https://www.elastic.co/elastic-maps-service)"`* diff --git a/package.json b/package.json index 54c9039213ef6a..836e5336b7b50c 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "@elastic/apm-rum-react": "^1.3.1", "@elastic/charts": "34.2.1", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", - "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.18", + "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.19", "@elastic/ems-client": "7.15.0", "@elastic/eui": "37.3.0", "@elastic/filesaver": "1.1.2", @@ -287,7 +287,7 @@ "lodash": "^4.17.21", "lru-cache": "^4.1.5", "lz-string": "^1.4.4", - "mapbox-gl": "1.13.1", + "maplibre-gl": "1.15.2", "mapbox-gl-draw-rectangle-mode": "1.0.4", "markdown-it": "^10.0.0", "md5": "^2.1.0", @@ -332,7 +332,7 @@ "raw-loader": "^3.1.0", "rbush": "^3.0.1", "re-resizable": "^6.1.1", - "re2": "^1.15.4", + "re2": "^1.16.0", "react": "^16.12.0", "react-ace": "^7.0.5", "react-beautiful-dnd": "^13.0.0", @@ -400,7 +400,6 @@ "tar": "4.4.13", "tinycolor2": "1.4.1", "tinygradient": "0.4.3", - "topojson-client": "3.1.0", "tree-kill": "^1.2.2", "ts-easing": "^0.2.0", "tslib": "^2.0.0", @@ -430,6 +429,7 @@ "devDependencies": { "@babel/cli": "^7.12.10", "@babel/core": "^7.12.10", + "@babel/generator": "^7.12.11", "@babel/parser": "^7.12.11", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-export-namespace-from": "^7.12.1", @@ -572,7 +572,6 @@ "@types/lodash": "^4.14.159", "@types/lru-cache": "^5.1.0", "@types/lz-string": "^1.3.34", - "@types/mapbox-gl": "1.13.1", "@types/markdown-it": "^0.0.7", "@types/md5": "^2.2.0", "@types/memoize-one": "^4.1.0", @@ -790,7 +789,7 @@ "multimatch": "^4.0.0", "mutation-observer": "^1.0.3", "ncp": "^2.0.0", - "node-sass": "^4.14.1", + "node-sass": "^6.0.1", "null-loader": "^3.0.0", "nyc": "^15.0.1", "oboe": "^2.1.4", @@ -810,7 +809,7 @@ "regenerate": "^1.4.0", "resolve": "^1.7.1", "rxjs-marbles": "^5.0.6", - "sass-loader": "^8.0.2", + "sass-loader": "^10.2.0", "sass-resources-loader": "^2.0.1", "selenium-webdriver": "^4.0.0-alpha.7", "serve-static": "1.14.1", diff --git a/packages/kbn-dev-utils/BUILD.bazel b/packages/kbn-dev-utils/BUILD.bazel index 502bcd05b74f82..90b4d91b666925 100644 --- a/packages/kbn-dev-utils/BUILD.bazel +++ b/packages/kbn-dev-utils/BUILD.bazel @@ -57,6 +57,7 @@ RUNTIME_DEPS = [ "@npm//load-json-file", "@npm//markdown-it", "@npm//normalize-path", + "@npm//prettier", "@npm//rxjs", "@npm//tar", "@npm//tree-kill", @@ -81,6 +82,7 @@ TYPES_DEPS = [ "@npm//@types/markdown-it", "@npm//@types/node", "@npm//@types/normalize-path", + "@npm//@types/prettier", "@npm//@types/react", "@npm//@types/tar", "@npm//@types/testing-library__jest-dom", diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 9dc9d1723945a4..381e99ac677f57 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -32,3 +32,4 @@ export * from './plugins'; export * from './streams'; export * from './babel'; export * from './extract'; +export * from './vscode_config'; diff --git a/packages/kbn-dev-utils/src/vscode_config/index.ts b/packages/kbn-dev-utils/src/vscode_config/index.ts new file mode 100644 index 00000000000000..1b08881273edce --- /dev/null +++ b/packages/kbn-dev-utils/src/vscode_config/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './update_vscode_config_cli'; diff --git a/packages/kbn-dev-utils/src/vscode_config/managed_config_keys.ts b/packages/kbn-dev-utils/src/vscode_config/managed_config_keys.ts new file mode 100644 index 00000000000000..288bfad02d7f47 --- /dev/null +++ b/packages/kbn-dev-utils/src/vscode_config/managed_config_keys.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface ManagedConfigKey { + key: string; + value: Record; +} + +/** + * Defines the keys which we overrite in user's vscode config for the workspace. We currently + * only support object values because that's all we needed to support, but support for non object + * values should be easy to add. + */ +export const MANAGED_CONFIG_KEYS: ManagedConfigKey[] = [ + { + key: 'files.watcherExclude', + value: { + ['**/.eslintcache']: true, + ['**/.es']: true, + ['**/.yarn-local-mirror']: true, + ['**/.chromium']: true, + ['**/packages/kbn-pm/dist/index.js']: true, + ['**/bazel-*']: true, + ['**/node_modules']: true, + ['**/target']: true, + ['**/*.log']: true, + }, + }, + { + key: 'search.exclude', + value: { + ['**/packages/kbn-pm/dist/index.js']: true, + }, + }, +]; diff --git a/packages/kbn-dev-utils/src/vscode_config/update_vscode_config.test.ts b/packages/kbn-dev-utils/src/vscode_config/update_vscode_config.test.ts new file mode 100644 index 00000000000000..dd57449c21da3b --- /dev/null +++ b/packages/kbn-dev-utils/src/vscode_config/update_vscode_config.test.ts @@ -0,0 +1,340 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import dedent from 'dedent'; + +import { updateVscodeConfig } from './update_vscode_config'; +import { ManagedConfigKey } from './managed_config_keys'; + +// avoid excessive escaping in snapshots +expect.addSnapshotSerializer({ test: (v) => typeof v === 'string', print: (v) => `${v}` }); + +const TEST_KEYS: ManagedConfigKey[] = [ + { + key: 'key', + value: { + hello: true, + world: [1, 2, 3], + }, + }, +]; + +const run = (json?: string) => updateVscodeConfig(TEST_KEYS, '', json); + +it('updates the passed JSON with the managed settings', () => { + expect(run(`{}`)).toMatchInlineSnapshot(` + // @managed + { + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); +}); + +it('initialized empty or undefined json values', () => { + expect(run('')).toMatchInlineSnapshot(` + // @managed + { + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); + + expect(run()).toMatchInlineSnapshot(` + // @managed + { + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); +}); + +it('replaces conflicting managed keys which do not have object values', () => { + expect(run(`{ "key": false }`)).toMatchInlineSnapshot(` + // @managed + { + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); +}); + +it(`throws if the JSON file doesn't contain an object`, () => { + expect(() => run('[]')).toThrowErrorMatchingInlineSnapshot( + `expected VSCode config to contain a JSON object` + ); + expect(() => run('1')).toThrowErrorMatchingInlineSnapshot( + `expected VSCode config to contain a JSON object` + ); + expect(() => run('"foo"')).toThrowErrorMatchingInlineSnapshot( + `expected VSCode config to contain a JSON object` + ); +}); + +it('persists comments in the original file', () => { + const newJson = run(` + /** + * This is a top level comment + */ + { + "a": "bar", + // this is just test data + "b": "box" + } + `); + expect(newJson).toMatchInlineSnapshot(` + // @managed + + /** + * This is a top level comment + */ + { + "a": "bar", + // this is just test data + "b": "box", + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); +}); + +it('overrides old values for managed keys', () => { + const newJson = run(` + { + "foo": 0, + "bar": "some other config", + "complex": "some other config", + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // @managed + { + "foo": 0, + "bar": "some other config", + "complex": "some other config", + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); +}); + +it('does not modify files starting with // SELF MANAGED', () => { + const newJson = run(dedent` + // self managed + { + "invalid": "I know what I am doing", + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // self managed + { + "invalid": "I know what I am doing", + } + `); +}); + +it('does not modify properties with leading `// self managed` comment', () => { + const newJson = run(dedent` + { + // self managed + "key": { + "world": [5] + } + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // @managed + { + // self managed + "key": { + "world": [5] + } + } + + `); +}); + +it('does not modify child properties with leading `// self managed` comment', () => { + const newJson = run(dedent` + { + "key": { + // self managed + "world": [5] + } + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // @managed + { + "key": { + // self managed + "world": [5], + // @managed + "hello": true + } + } + + `); +}); + +it('does not modify unknown child properties', () => { + const newJson = run(dedent` + { + "key": { + "foo": "bar", + // self managed + "world": [5], + } + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // @managed + { + "key": { + "foo": "bar", + // self managed + "world": [5], + // @managed + "hello": true + } + } + + `); +}); + +it('removes managed properties which are no longer managed', () => { + const newJson = run(dedent` + { + "key": { + // @managed + "foo": "bar", + // self managed + "world": [5], + } + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // @managed + { + "key": { + // self managed + "world": [5], + // @managed + "hello": true + } + } + + `); +}); + +it('wipes out child keys which conflict with newly managed child keys', () => { + const newJson = run(dedent` + { + "key": { + // some user specified comment + "world": [5], + } + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // @managed + { + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); +}); + +it('correctly formats info text when specified', () => { + const newJson = updateVscodeConfig(TEST_KEYS, 'info users\nshould know', `{}`); + + expect(newJson).toMatchInlineSnapshot(` + /** + * @managed + * + * info users + * should know + */ + { + "key": { + // @managed + "hello": true, + // @managed + "world": [1, 2, 3] + } + } + + `); +}); + +it('allows "// self managed" comments conflicting with "// @managed" comments to win', () => { + const newJson = run(dedent` + { + "key": { + // @managed + // self managed + "hello": ["world"] + } + } + `); + + expect(newJson).toMatchInlineSnapshot(` + // @managed + { + "key": { + // self managed + "hello": ["world"], + // @managed + "world": [1, 2, 3] + } + } + + `); +}); diff --git a/packages/kbn-dev-utils/src/vscode_config/update_vscode_config.ts b/packages/kbn-dev-utils/src/vscode_config/update_vscode_config.ts new file mode 100644 index 00000000000000..ac1b5515252ddf --- /dev/null +++ b/packages/kbn-dev-utils/src/vscode_config/update_vscode_config.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { parseExpression } from '@babel/parser'; +import * as t from '@babel/types'; +import generate from '@babel/generator'; +import Prettier from 'prettier'; + +import { ManagedConfigKey } from './managed_config_keys'; + +type BasicObjectProp = t.ObjectProperty & { + key: t.StringLiteral; +}; + +const isBasicObjectProp = (n: t.Node): n is BasicObjectProp => + n.type === 'ObjectProperty' && n.key.type === 'StringLiteral'; + +const isManaged = (node?: t.Node) => + !!node?.leadingComments?.some( + (c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === '@managed' + ); + +const isSelfManaged = (node?: t.Node) => + !!node?.leadingComments?.some( + (c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === 'self managed' + ); + +const remove = (arr: T[], value: T) => { + const index = arr.indexOf(value); + if (index > -1) { + arr.splice(index, 1); + } +}; + +const createManagedChildProp = (key: string, value: any) => { + const childProp = t.objectProperty(t.stringLiteral(key), parseExpression(JSON.stringify(value))); + t.addComment(childProp, 'leading', ' @managed', true); + return childProp; +}; + +const createManagedProp = (key: string, value: Record) => { + return t.objectProperty( + t.stringLiteral(key), + t.objectExpression(Object.entries(value).map(([k, v]) => createManagedChildProp(k, v))) + ); +}; + +/** + * Adds a new setting to the settings.json file. Used when there is no existing key + * + * @param ast AST of the entire settings.json file + * @param key the key name to add + * @param value managed value which should be set at `key` + */ +const addManagedProp = (ast: t.ObjectExpression, key: string, value: Record) => { + ast.properties.push(createManagedProp(key, value)); +}; + +/** + * Replace an existing setting in the settings.json file with the `managedValue`, ignoring its + * type, used when the value of the existing setting is not an ObjectExpression + * + * @param ast AST of the entire settings.json file + * @param existing node which should be replaced + * @param value managed value which should replace the current value, regardless of its type + */ +const replaceManagedProp = ( + ast: t.ObjectExpression, + existing: BasicObjectProp, + value: Record +) => { + remove(ast.properties, existing); + addManagedProp(ast, existing.key.value, value); +}; + +/** + * Merge the managed value in to the value already in the settings.json file. Any property which is + * labeled with a `// self managed` comment is untouched, any property which is `// @managed` but + * no longer in the `managedValue` is removed, and any properties in the `managedValue` are either + * added or updated based on their existence in the AST. + * + * @param properties Object expression properties list which we will merge with ("key": ) + * @param managedValue the managed value that should be merged into the existing values + */ +const mergeManagedProperties = ( + properties: t.ObjectExpression['properties'], + managedValue: Record +) => { + // iterate through all the keys in the managed `value` and either add them to the + // prop, update their value, or ignore them because they are "// self managed" + for (const [key, value] of Object.entries(managedValue)) { + const existing = properties.filter(isBasicObjectProp).find((p) => p.key.value === key); + + if (!existing) { + // add the new managed prop + properties.push(createManagedChildProp(key, value)); + continue; + } + + if (isSelfManaged(existing)) { + // strip "// @managed" comment if conflicting with "// self managed" + existing.leadingComments = (existing.leadingComments ?? []).filter( + (c) => c.value.trim() !== '@managed' + ); + continue; + } + + if (isManaged(existing)) { + // the prop already exists and is still managed, so update it's value + existing.value = parseExpression(JSON.stringify(value)); + continue; + } + + // take over the unmanaged child prop by deleting the previous prop and replacing it + // with a brand new one + remove(properties, existing); + properties.push(createManagedChildProp(key, value)); + } + + // iterate through the props to find "// @managed" props which are no longer in + // the `managedValue` and remove them + for (const prop of properties) { + if ( + isBasicObjectProp(prop) && + isManaged(prop) && + !Object.prototype.hasOwnProperty.call(managedValue, prop.key.value) + ) { + remove(properties, prop); + } + } +}; + +/** + * Update the settings.json file used by VSCode in the Kibana repository. If the file starts + * with the comment "// self managed" then it is not touched. If a top-level keys is prefixed with + * `// self managed` then all the properties of that setting are left untouched. And finally, if + * a specific child property of a setting like `search.exclude` is prefixed with `// self managed` + * then it is left untouched. + * + * We don't just use `JSON.parse()` and `JSON.stringify()` in order to support this customization and + * also to support users using comments in this file, which is very useful for temporarily disabling settings. + * + * After the config file is updated it is formatted with prettier. + * + * @param keys The config keys which are managed + * @param infoText The text which should be written to the top of the file to educate users how to customize the settings + * @param json The settings file as a string + */ +export function updateVscodeConfig(keys: ManagedConfigKey[], infoText: string, json?: string) { + json = json || '{}'; + const ast = parseExpression(json); + + if (ast.type !== 'ObjectExpression') { + throw new Error(`expected VSCode config to contain a JSON object`); + } + + if (isSelfManaged(ast)) { + return json; + } + + for (const { key, value } of keys) { + const existingProp = ast.properties.filter(isBasicObjectProp).find((p) => p.key.value === key); + + if (isSelfManaged(existingProp)) { + continue; + } + + if (existingProp && existingProp.value.type === 'ObjectExpression') { + // setting exists and is an object so merge properties of `value` with it + mergeManagedProperties(existingProp.value.properties, value); + continue; + } + + if (existingProp) { + // setting exists but its value is not an object expression so replace it + replaceManagedProp(ast, existingProp, value); + continue; + } + + // setting isn't in config file so create it + addManagedProp(ast, key, value); + } + + ast.leadingComments = [ + (infoText + ? { + type: 'CommentBlock', + value: `* + * @managed + * + * ${infoText.split(/\r?\n/).join('\n * ')} +`, + } + : { + type: 'CommentLine', + value: ' @managed', + }) as t.CommentBlock, + ...(ast.leadingComments ?? [])?.filter((c) => !c.value.includes('@managed')), + ]; + + return Prettier.format(generate(ast).code, { + endOfLine: 'auto', + filepath: 'settings.json', + }); +} diff --git a/packages/kbn-dev-utils/src/vscode_config/update_vscode_config_cli.ts b/packages/kbn-dev-utils/src/vscode_config/update_vscode_config_cli.ts new file mode 100644 index 00000000000000..273aed95855724 --- /dev/null +++ b/packages/kbn-dev-utils/src/vscode_config/update_vscode_config_cli.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import Fs from 'fs/promises'; + +import { REPO_ROOT } from '@kbn/utils'; +import dedent from 'dedent'; + +import { run } from '../run'; + +import { MANAGED_CONFIG_KEYS } from './managed_config_keys'; +import { updateVscodeConfig } from './update_vscode_config'; + +export function runUpdateVscodeConfigCli() { + run(async ({ log }) => { + const path = Path.resolve(REPO_ROOT, '.vscode/settings.json'); + + let json; + try { + json = await Fs.readFile(path, 'utf-8'); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + + const updatedJson = updateVscodeConfig( + MANAGED_CONFIG_KEYS, + dedent` + Some settings in this file are managed by @kbn/dev-utils. When a setting is managed it is preceeded + with a comment "// @managed" comment. Replace that with "// self managed" and the scripts will not + touch that value. Put a "// self managed" comment at the top of the file, or above a group of settings + to disable management of that entire section. + `, + json + ); + await Fs.mkdir(Path.dirname(path), { recursive: true }); + await Fs.writeFile(path, updatedJson); + + log.success('updated', path); + }); +} diff --git a/packages/kbn-es-query/src/es_query/index.ts b/packages/kbn-es-query/src/es_query/index.ts index 6e4a58fbe96c3f..0690e6ab984349 100644 --- a/packages/kbn-es-query/src/es_query/index.ts +++ b/packages/kbn-es-query/src/es_query/index.ts @@ -10,4 +10,11 @@ export { buildEsQuery, EsQueryConfig } from './build_es_query'; export { buildQueryFromFilters } from './from_filters'; export { luceneStringToDsl } from './lucene_string_to_dsl'; export { decorateQuery } from './decorate_query'; -export { IndexPatternBase, IndexPatternFieldBase, IFieldSubType, BoolQuery } from './types'; +export { + IndexPatternBase, + IndexPatternFieldBase, + IFieldSubType, + BoolQuery, + DataViewBase, + DataViewFieldBase, +} from './types'; diff --git a/packages/kbn-es-query/src/es_query/types.ts b/packages/kbn-es-query/src/es_query/types.ts index 333536a5f3ecd2..0d443366626a03 100644 --- a/packages/kbn-es-query/src/es_query/types.ts +++ b/packages/kbn-es-query/src/es_query/types.ts @@ -21,7 +21,7 @@ export interface IFieldSubType { * A base interface for an index pattern field * @public */ -export interface IndexPatternFieldBase { +export interface DataViewFieldBase { name: string; /** * Kibana field type @@ -40,16 +40,26 @@ export interface IndexPatternFieldBase { scripted?: boolean; } +/** + * @deprecated Use DataViewField instead. All index pattern interfaces were renamed. + */ +export type IndexPatternFieldBase = DataViewFieldBase; + /** * A base interface for an index pattern * @public */ -export interface IndexPatternBase { - fields: IndexPatternFieldBase[]; +export interface DataViewBase { + fields: DataViewFieldBase[]; id?: string; title?: string; } +/** + * @deprecated Use DataViewBase instead. All index pattern interfaces were renamed. + */ +export type IndexPatternBase = DataViewBase; + export interface BoolQuery { must: estypes.QueryDslQueryContainer[]; must_not: estypes.QueryDslQueryContainer[]; diff --git a/packages/kbn-mapbox-gl/BUILD.bazel b/packages/kbn-mapbox-gl/BUILD.bazel index e0de6848c22899..00b3213c4dea76 100644 --- a/packages/kbn-mapbox-gl/BUILD.bazel +++ b/packages/kbn-mapbox-gl/BUILD.bazel @@ -30,13 +30,13 @@ NPM_MODULE_EXTRA_FILES = [ RUNTIME_DEPS = [ "@npm//@mapbox/mapbox-gl-rtl-text", "@npm//file-loader", - "@npm//mapbox-gl", + "@npm//maplibre-gl", ] TYPES_DEPS = [ "@npm//@mapbox/mapbox-gl-rtl-text", "@npm//file-loader", - "@npm//@types/mapbox-gl", + "@npm//maplibre-gl", ] jsts_transpiler( diff --git a/packages/kbn-mapbox-gl/src/index.ts b/packages/kbn-mapbox-gl/src/index.ts index 87b85002d7598c..97c4238f1e569f 100644 --- a/packages/kbn-mapbox-gl/src/index.ts +++ b/packages/kbn-mapbox-gl/src/index.ts @@ -24,13 +24,13 @@ import type { MapboxGeoJSONFeature, Point, CustomLayerInterface, -} from 'mapbox-gl'; -import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; +} from 'maplibre-gl'; +import mapboxgl from 'maplibre-gl/dist/maplibre-gl-csp'; // @ts-expect-error import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; // @ts-expect-error -import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; -import 'mapbox-gl/dist/mapbox-gl.css'; +import mbWorkerUrl from '!!file-loader!maplibre-gl/dist/maplibre-gl-csp-worker'; +import 'maplibre-gl/dist/maplibre-gl.css'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); diff --git a/packages/kbn-mapbox-gl/src/typings.ts b/packages/kbn-mapbox-gl/src/typings.ts index 0cc6908aca4284..76d8850cc1d6fa 100644 --- a/packages/kbn-mapbox-gl/src/typings.ts +++ b/packages/kbn-mapbox-gl/src/typings.ts @@ -7,4 +7,4 @@ */ // Mapbox-gl doesn't declare this type. -declare module 'mapbox-gl/dist/mapbox-gl-csp'; +declare module 'maplibre-gl/dist/maplibre-gl-csp'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 445fa6bff28007..77bbeabb7f73b0 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -112,7 +112,7 @@ pageLoadAssetSize: expressionImage: 19288 expressionMetric: 22238 expressionShape: 34008 - interactiveSetup: 18532 + interactiveSetup: 70000 expressionTagcloud: 27505 expressions: 239290 securitySolution: 231753 diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts index 55d267b729515d..39a7e0ab0e6229 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts @@ -446,78 +446,33 @@ describe('OptimizerConfig::create()', () => { } `); - expect(findKibanaPlatformPlugins.mock).toMatchInlineSnapshot(` - Object { - "calls": Array [ - Array [ - Symbol(parsed plugin scan dirs), - Symbol(parsed plugin paths), - ], - ], - "instances": Array [ - [Window], - ], - "invocationCallOrder": Array [ - 25, - ], - "results": Array [ - Object { - "type": "return", - "value": Symbol(new platform plugins), - }, + expect(findKibanaPlatformPlugins.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Symbol(parsed plugin scan dirs), + Symbol(parsed plugin paths), ], - } + ] `); - expect(filterById.mock).toMatchInlineSnapshot(` - Object { - "calls": Array [ - Array [ - Array [], - Symbol(focused bundles), - ], - ], - "instances": Array [ - [Window], - ], - "invocationCallOrder": Array [ - 28, + expect(filterById.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Array [], + Symbol(focused bundles), ], - "results": Array [ - Object { - "type": "return", - "value": Symbol(filtered bundles), - }, - ], - } + ] `); - expect(getPluginBundles.mock).toMatchInlineSnapshot(` - Object { - "calls": Array [ - Array [ - Symbol(new platform plugins), - Symbol(parsed repo root), - Symbol(parsed output root), - Symbol(limits), - ], - ], - "instances": Array [ - [Window], + expect(getPluginBundles.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Symbol(new platform plugins), + Symbol(parsed repo root), + Symbol(parsed output root), + Symbol(limits), ], - "invocationCallOrder": Array [ - 26, - ], - "results": Array [ - Object { - "type": "return", - "value": Array [ - Symbol(bundle1), - Symbol(bundle2), - ], - }, - ], - } + ] `); }); }); diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 47d34cd8b29fd6..1ea2849ac09833 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -159,14 +159,14 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: { loader: 'sass-loader', options: { - prependData(loaderContext: webpack.loader.LoaderContext) { + additionalData(content: string, loaderContext: webpack.loader.LoaderContext) { return `@import ${stringifyRequest( loaderContext, Path.resolve( worker.repoRoot, `src/core/public/core_app/styles/_globals_${theme}.scss` ) - )};\n`; + )};\n${content}`; }, webpackImporter: false, implementation: require('node-sass'), diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 10b0230a91ff60..f0a95a612f02ce 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -8968,9 +8968,17 @@ const BootstrapCommand = { // NOTE: We don't probably need this anymore, is actually not being used - await Object(_utils_link_project_executables__WEBPACK_IMPORTED_MODULE_2__["linkProjectExecutables"])(projects, projectGraph); // Build typescript references + await Object(_utils_link_project_executables__WEBPACK_IMPORTED_MODULE_2__["linkProjectExecutables"])(projects, projectGraph); // Update vscode settings - await Object(_utils_child_process__WEBPACK_IMPORTED_MODULE_1__["spawnStreaming"])('node', ['scripts/build_ts_refs', '--ignore-type-failures', '--info'], { + await Object(_utils_child_process__WEBPACK_IMPORTED_MODULE_1__["spawnStreaming"])(process.execPath, ['scripts/update_vscode_config'], { + cwd: kbn.getAbsolute(), + env: process.env + }, { + prefix: '[vscode]', + debug: false + }); // Build typescript references + + await Object(_utils_child_process__WEBPACK_IMPORTED_MODULE_1__["spawnStreaming"])(process.execPath, ['scripts/build_ts_refs', '--ignore-type-failures', '--info'], { cwd: kbn.getAbsolute(), env: process.env }, { @@ -63468,7 +63476,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildBazelProductionProjects"]; }); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(813); +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(814); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); /* @@ -63490,11 +63498,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return buildBazelProductionProjects; }); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(566); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(775); +/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(776); /* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(globby__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(813); +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(814); /* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(372); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(188); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(186); @@ -63600,11 +63608,11 @@ const os = __webpack_require__(124); const pMap = __webpack_require__(567); const arrify = __webpack_require__(562); const globby = __webpack_require__(570); -const hasGlob = __webpack_require__(759); -const cpFile = __webpack_require__(761); -const junk = __webpack_require__(771); -const pFilter = __webpack_require__(772); -const CpyError = __webpack_require__(774); +const hasGlob = __webpack_require__(760); +const cpFile = __webpack_require__(762); +const junk = __webpack_require__(772); +const pFilter = __webpack_require__(773); +const CpyError = __webpack_require__(775); const defaultOptions = { ignoreJunk: true @@ -63948,8 +63956,8 @@ const fs = __webpack_require__(142); const arrayUnion = __webpack_require__(571); const glob = __webpack_require__(201); const fastGlob = __webpack_require__(573); -const dirGlob = __webpack_require__(752); -const gitignore = __webpack_require__(755); +const dirGlob = __webpack_require__(753); +const gitignore = __webpack_require__(756); const DEFAULT_FILTER = () => false; @@ -64200,11 +64208,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(575); var taskManager = __webpack_require__(576); -var reader_async_1 = __webpack_require__(723); -var reader_stream_1 = __webpack_require__(747); -var reader_sync_1 = __webpack_require__(748); -var arrayUtils = __webpack_require__(750); -var streamUtils = __webpack_require__(751); +var reader_async_1 = __webpack_require__(724); +var reader_stream_1 = __webpack_require__(748); +var reader_sync_1 = __webpack_require__(749); +var arrayUtils = __webpack_require__(751); +var streamUtils = __webpack_require__(752); /** * Synchronous API. */ @@ -64785,13 +64793,13 @@ module.exports.win32 = win32; var util = __webpack_require__(115); var braces = __webpack_require__(582); var toRegex = __webpack_require__(583); -var extend = __webpack_require__(691); +var extend = __webpack_require__(690); /** * Local dependencies */ -var compilers = __webpack_require__(693); +var compilers = __webpack_require__(692); var parsers = __webpack_require__(719); var cache = __webpack_require__(720); var utils = __webpack_require__(721); @@ -65667,17 +65675,17 @@ module.exports = micromatch; */ var toRegex = __webpack_require__(583); -var unique = __webpack_require__(603); -var extend = __webpack_require__(604); +var unique = __webpack_require__(605); +var extend = __webpack_require__(606); /** * Local dependencies */ -var compilers = __webpack_require__(606); -var parsers = __webpack_require__(619); -var Braces = __webpack_require__(624); -var utils = __webpack_require__(607); +var compilers = __webpack_require__(608); +var parsers = __webpack_require__(621); +var Braces = __webpack_require__(625); +var utils = __webpack_require__(609); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -65989,8 +65997,8 @@ module.exports = braces; var safe = __webpack_require__(584); var define = __webpack_require__(590); -var extend = __webpack_require__(596); -var not = __webpack_require__(600); +var extend = __webpack_require__(598); +var not = __webpack_require__(602); var MAX_LENGTH = 1024 * 64; /** @@ -66803,7 +66811,7 @@ module.exports = function isObject(val) { var typeOf = __webpack_require__(593); var isAccessor = __webpack_require__(594); -var isData = __webpack_require__(595); +var isData = __webpack_require__(596); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -66965,7 +66973,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(593); +var typeOf = __webpack_require__(595); // accessor descriptor properties var accessor = { @@ -67029,6 +67037,141 @@ module.exports = isAccessorDescriptor; /***/ }), /* 595 */ +/***/ (function(module, exports) { + +var toString = Object.prototype.toString; + +module.exports = function kindOf(val) { + if (val === void 0) return 'undefined'; + if (val === null) return 'null'; + + var type = typeof val; + if (type === 'boolean') return 'boolean'; + if (type === 'string') return 'string'; + if (type === 'number') return 'number'; + if (type === 'symbol') return 'symbol'; + if (type === 'function') { + return isGeneratorFn(val) ? 'generatorfunction' : 'function'; + } + + if (isArray(val)) return 'array'; + if (isBuffer(val)) return 'buffer'; + if (isArguments(val)) return 'arguments'; + if (isDate(val)) return 'date'; + if (isError(val)) return 'error'; + if (isRegexp(val)) return 'regexp'; + + switch (ctorName(val)) { + case 'Symbol': return 'symbol'; + case 'Promise': return 'promise'; + + // Set, Map, WeakSet, WeakMap + case 'WeakMap': return 'weakmap'; + case 'WeakSet': return 'weakset'; + case 'Map': return 'map'; + case 'Set': return 'set'; + + // 8-bit typed arrays + case 'Int8Array': return 'int8array'; + case 'Uint8Array': return 'uint8array'; + case 'Uint8ClampedArray': return 'uint8clampedarray'; + + // 16-bit typed arrays + case 'Int16Array': return 'int16array'; + case 'Uint16Array': return 'uint16array'; + + // 32-bit typed arrays + case 'Int32Array': return 'int32array'; + case 'Uint32Array': return 'uint32array'; + case 'Float32Array': return 'float32array'; + case 'Float64Array': return 'float64array'; + } + + if (isGeneratorObj(val)) { + return 'generator'; + } + + // Non-plain objects + type = toString.call(val); + switch (type) { + case '[object Object]': return 'object'; + // iterators + case '[object Map Iterator]': return 'mapiterator'; + case '[object Set Iterator]': return 'setiterator'; + case '[object String Iterator]': return 'stringiterator'; + case '[object Array Iterator]': return 'arrayiterator'; + } + + // other + return type.slice(8, -1).toLowerCase().replace(/\s/g, ''); +}; + +function ctorName(val) { + return typeof val.constructor === 'function' ? val.constructor.name : null; +} + +function isArray(val) { + if (Array.isArray) return Array.isArray(val); + return val instanceof Array; +} + +function isError(val) { + return val instanceof Error || (typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number'); +} + +function isDate(val) { + if (val instanceof Date) return true; + return typeof val.toDateString === 'function' + && typeof val.getDate === 'function' + && typeof val.setDate === 'function'; +} + +function isRegexp(val) { + if (val instanceof RegExp) return true; + return typeof val.flags === 'string' + && typeof val.ignoreCase === 'boolean' + && typeof val.multiline === 'boolean' + && typeof val.global === 'boolean'; +} + +function isGeneratorFn(name, val) { + return ctorName(name) === 'GeneratorFunction'; +} + +function isGeneratorObj(val) { + return typeof val.throw === 'function' + && typeof val.return === 'function' + && typeof val.next === 'function'; +} + +function isArguments(val) { + try { + if (typeof val.length === 'number' && typeof val.callee === 'function') { + return true; + } + } catch (err) { + if (err.message.indexOf('callee') !== -1) { + return true; + } + } + return false; +} + +/** + * If you need to support Safari 5-7 (8-10 yr-old browser), + * take a look at https://github.com/feross/is-buffer + */ + +function isBuffer(val) { + if (val.constructor && typeof val.constructor.isBuffer === 'function') { + return val.constructor.isBuffer(val); + } + return false; +} + + +/***/ }), +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67041,7 +67184,7 @@ module.exports = isAccessorDescriptor; -var typeOf = __webpack_require__(593); +var typeOf = __webpack_require__(597); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -67084,14 +67227,149 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 596 */ +/* 597 */ +/***/ (function(module, exports) { + +var toString = Object.prototype.toString; + +module.exports = function kindOf(val) { + if (val === void 0) return 'undefined'; + if (val === null) return 'null'; + + var type = typeof val; + if (type === 'boolean') return 'boolean'; + if (type === 'string') return 'string'; + if (type === 'number') return 'number'; + if (type === 'symbol') return 'symbol'; + if (type === 'function') { + return isGeneratorFn(val) ? 'generatorfunction' : 'function'; + } + + if (isArray(val)) return 'array'; + if (isBuffer(val)) return 'buffer'; + if (isArguments(val)) return 'arguments'; + if (isDate(val)) return 'date'; + if (isError(val)) return 'error'; + if (isRegexp(val)) return 'regexp'; + + switch (ctorName(val)) { + case 'Symbol': return 'symbol'; + case 'Promise': return 'promise'; + + // Set, Map, WeakSet, WeakMap + case 'WeakMap': return 'weakmap'; + case 'WeakSet': return 'weakset'; + case 'Map': return 'map'; + case 'Set': return 'set'; + + // 8-bit typed arrays + case 'Int8Array': return 'int8array'; + case 'Uint8Array': return 'uint8array'; + case 'Uint8ClampedArray': return 'uint8clampedarray'; + + // 16-bit typed arrays + case 'Int16Array': return 'int16array'; + case 'Uint16Array': return 'uint16array'; + + // 32-bit typed arrays + case 'Int32Array': return 'int32array'; + case 'Uint32Array': return 'uint32array'; + case 'Float32Array': return 'float32array'; + case 'Float64Array': return 'float64array'; + } + + if (isGeneratorObj(val)) { + return 'generator'; + } + + // Non-plain objects + type = toString.call(val); + switch (type) { + case '[object Object]': return 'object'; + // iterators + case '[object Map Iterator]': return 'mapiterator'; + case '[object Set Iterator]': return 'setiterator'; + case '[object String Iterator]': return 'stringiterator'; + case '[object Array Iterator]': return 'arrayiterator'; + } + + // other + return type.slice(8, -1).toLowerCase().replace(/\s/g, ''); +}; + +function ctorName(val) { + return typeof val.constructor === 'function' ? val.constructor.name : null; +} + +function isArray(val) { + if (Array.isArray) return Array.isArray(val); + return val instanceof Array; +} + +function isError(val) { + return val instanceof Error || (typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number'); +} + +function isDate(val) { + if (val instanceof Date) return true; + return typeof val.toDateString === 'function' + && typeof val.getDate === 'function' + && typeof val.setDate === 'function'; +} + +function isRegexp(val) { + if (val instanceof RegExp) return true; + return typeof val.flags === 'string' + && typeof val.ignoreCase === 'boolean' + && typeof val.multiline === 'boolean' + && typeof val.global === 'boolean'; +} + +function isGeneratorFn(name, val) { + return ctorName(name) === 'GeneratorFunction'; +} + +function isGeneratorObj(val) { + return typeof val.throw === 'function' + && typeof val.return === 'function' + && typeof val.next === 'function'; +} + +function isArguments(val) { + try { + if (typeof val.length === 'number' && typeof val.callee === 'function') { + return true; + } + } catch (err) { + if (err.message.indexOf('callee') !== -1) { + return true; + } + } + return false; +} + +/** + * If you need to support Safari 5-7 (8-10 yr-old browser), + * take a look at https://github.com/feross/is-buffer + */ + +function isBuffer(val) { + if (val.constructor && typeof val.constructor.isBuffer === 'function') { + return val.constructor.isBuffer(val); + } + return false; +} + + +/***/ }), +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(597); -var assignSymbols = __webpack_require__(599); +var isExtendable = __webpack_require__(599); +var assignSymbols = __webpack_require__(601); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -67151,7 +67429,7 @@ function isEnum(obj, key) { /***/ }), -/* 597 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67164,7 +67442,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(598); +var isPlainObject = __webpack_require__(600); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -67172,7 +67450,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 598 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67216,7 +67494,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 599 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67263,13 +67541,13 @@ module.exports = function(receiver, objects) { /***/ }), -/* 600 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(601); +var extend = __webpack_require__(603); var safe = __webpack_require__(584); /** @@ -67342,14 +67620,14 @@ module.exports = toRegex; /***/ }), -/* 601 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(602); -var assignSymbols = __webpack_require__(599); +var isExtendable = __webpack_require__(604); +var assignSymbols = __webpack_require__(601); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -67409,7 +67687,7 @@ function isEnum(obj, key) { /***/ }), -/* 602 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67422,7 +67700,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(598); +var isPlainObject = __webpack_require__(600); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -67430,7 +67708,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 603 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67480,13 +67758,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 604 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(605); +var isObject = __webpack_require__(607); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -67520,7 +67798,7 @@ function hasOwn(obj, key) { /***/ }), -/* 605 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67540,13 +67818,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 606 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(607); +var utils = __webpack_require__(609); module.exports = function(braces, options) { braces.compiler @@ -67829,25 +68107,25 @@ function hasQueue(node) { /***/ }), -/* 607 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(608); +var splitString = __webpack_require__(610); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(604); -utils.flatten = __webpack_require__(611); +utils.extend = __webpack_require__(606); +utils.flatten = __webpack_require__(613); utils.isObject = __webpack_require__(591); -utils.fillRange = __webpack_require__(612); -utils.repeat = __webpack_require__(618); -utils.unique = __webpack_require__(603); +utils.fillRange = __webpack_require__(614); +utils.repeat = __webpack_require__(620); +utils.unique = __webpack_require__(605); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -68179,7 +68457,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 608 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68192,7 +68470,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(609); +var extend = __webpack_require__(611); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -68357,14 +68635,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 609 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(610); -var assignSymbols = __webpack_require__(599); +var isExtendable = __webpack_require__(612); +var assignSymbols = __webpack_require__(601); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -68424,7 +68702,7 @@ function isEnum(obj, key) { /***/ }), -/* 610 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68437,7 +68715,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(598); +var isPlainObject = __webpack_require__(600); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -68445,7 +68723,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 611 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68474,7 +68752,7 @@ function flat(arr, res) { /***/ }), -/* 612 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68488,10 +68766,10 @@ function flat(arr, res) { var util = __webpack_require__(115); -var isNumber = __webpack_require__(613); -var extend = __webpack_require__(604); -var repeat = __webpack_require__(616); -var toRegex = __webpack_require__(617); +var isNumber = __webpack_require__(615); +var extend = __webpack_require__(606); +var repeat = __webpack_require__(618); +var toRegex = __webpack_require__(619); /** * Return a range of numbers or letters. @@ -68689,7 +68967,7 @@ module.exports = fillRange; /***/ }), -/* 613 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68702,7 +68980,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(614); +var typeOf = __webpack_require__(616); module.exports = function isNumber(num) { var type = typeOf(num); @@ -68718,10 +68996,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 614 */ +/* 616 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(615); +var isBuffer = __webpack_require__(617); var toString = Object.prototype.toString; /** @@ -68840,7 +69118,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 615 */ +/* 617 */ /***/ (function(module, exports) { /*! @@ -68867,7 +69145,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 616 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68944,7 +69222,7 @@ function repeat(str, num) { /***/ }), -/* 617 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68957,8 +69235,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(616); -var isNumber = __webpack_require__(613); +var repeat = __webpack_require__(618); +var isNumber = __webpack_require__(615); var cache = {}; function toRegexRange(min, max, options) { @@ -69245,7 +69523,7 @@ module.exports = toRegexRange; /***/ }), -/* 618 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69270,14 +69548,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 619 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(620); -var utils = __webpack_require__(607); +var Node = __webpack_require__(622); +var utils = __webpack_require__(609); /** * Braces parsers @@ -69637,15 +69915,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 620 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var isObject = __webpack_require__(591); -var define = __webpack_require__(621); -var utils = __webpack_require__(622); +var define = __webpack_require__(623); +var utils = __webpack_require__(624); var ownNames; /** @@ -70136,7 +70414,7 @@ exports = module.exports = Node; /***/ }), -/* 621 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70174,13 +70452,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 622 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(623); +var typeOf = __webpack_require__(616); var utils = module.exports; /** @@ -71200,139 +71478,17 @@ function assert(val, message) { /***/ }), -/* 623 */ -/***/ (function(module, exports, __webpack_require__) { - -var isBuffer = __webpack_require__(615); -var toString = Object.prototype.toString; - -/** - * Get the native `typeof` a value. - * - * @param {*} `val` - * @return {*} Native javascript type - */ - -module.exports = function kindOf(val) { - // primitivies - if (typeof val === 'undefined') { - return 'undefined'; - } - if (val === null) { - return 'null'; - } - if (val === true || val === false || val instanceof Boolean) { - return 'boolean'; - } - if (typeof val === 'string' || val instanceof String) { - return 'string'; - } - if (typeof val === 'number' || val instanceof Number) { - return 'number'; - } - - // functions - if (typeof val === 'function' || val instanceof Function) { - return 'function'; - } - - // array - if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { - return 'array'; - } - - // check for instances of RegExp and Date before calling `toString` - if (val instanceof RegExp) { - return 'regexp'; - } - if (val instanceof Date) { - return 'date'; - } - - // other objects - var type = toString.call(val); - - if (type === '[object RegExp]') { - return 'regexp'; - } - if (type === '[object Date]') { - return 'date'; - } - if (type === '[object Arguments]') { - return 'arguments'; - } - if (type === '[object Error]') { - return 'error'; - } - - // buffer - if (isBuffer(val)) { - return 'buffer'; - } - - // es6: Map, WeakMap, Set, WeakSet - if (type === '[object Set]') { - return 'set'; - } - if (type === '[object WeakSet]') { - return 'weakset'; - } - if (type === '[object Map]') { - return 'map'; - } - if (type === '[object WeakMap]') { - return 'weakmap'; - } - if (type === '[object Symbol]') { - return 'symbol'; - } - - // typed arrays - if (type === '[object Int8Array]') { - return 'int8array'; - } - if (type === '[object Uint8Array]') { - return 'uint8array'; - } - if (type === '[object Uint8ClampedArray]') { - return 'uint8clampedarray'; - } - if (type === '[object Int16Array]') { - return 'int16array'; - } - if (type === '[object Uint16Array]') { - return 'uint16array'; - } - if (type === '[object Int32Array]') { - return 'int32array'; - } - if (type === '[object Uint32Array]') { - return 'uint32array'; - } - if (type === '[object Float32Array]') { - return 'float32array'; - } - if (type === '[object Float64Array]') { - return 'float64array'; - } - - // must be a plain object - return 'object'; -}; - - -/***/ }), -/* 624 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(604); -var Snapdragon = __webpack_require__(625); -var compilers = __webpack_require__(606); -var parsers = __webpack_require__(619); -var utils = __webpack_require__(607); +var extend = __webpack_require__(606); +var Snapdragon = __webpack_require__(626); +var compilers = __webpack_require__(608); +var parsers = __webpack_require__(621); +var utils = __webpack_require__(609); /** * Customize Snapdragon parser and renderer @@ -71433,17 +71589,17 @@ module.exports = Braces; /***/ }), -/* 625 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(626); +var Base = __webpack_require__(627); var define = __webpack_require__(654); -var Compiler = __webpack_require__(665); -var Parser = __webpack_require__(688); -var utils = __webpack_require__(668); +var Compiler = __webpack_require__(664); +var Parser = __webpack_require__(687); +var utils = __webpack_require__(667); var regexCache = {}; var cache = {}; @@ -71614,16 +71770,16 @@ module.exports.Parser = Parser; /***/ }), -/* 626 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(115); -var define = __webpack_require__(627); -var CacheBase = __webpack_require__(628); -var Emitter = __webpack_require__(629); +var define = __webpack_require__(628); +var CacheBase = __webpack_require__(629); +var Emitter = __webpack_require__(630); var isObject = __webpack_require__(591); var merge = __webpack_require__(648); var pascal = __webpack_require__(651); @@ -72056,7 +72212,7 @@ module.exports.namespace = namespace; /***/ }), -/* 627 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72094,16 +72250,16 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 628 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var isObject = __webpack_require__(591); -var Emitter = __webpack_require__(629); -var visit = __webpack_require__(630); -var toPath = __webpack_require__(633); +var Emitter = __webpack_require__(630); +var visit = __webpack_require__(631); +var toPath = __webpack_require__(634); var union = __webpack_require__(635); var del = __webpack_require__(639); var get = __webpack_require__(637); @@ -72362,7 +72518,7 @@ module.exports.namespace = namespace; /***/ }), -/* 629 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { @@ -72531,7 +72687,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 630 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72544,8 +72700,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(631); -var mapVisit = __webpack_require__(632); +var visit = __webpack_require__(632); +var mapVisit = __webpack_require__(633); module.exports = function(collection, method, val) { var result; @@ -72568,7 +72724,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 631 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72608,14 +72764,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 632 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(115); -var visit = __webpack_require__(631); +var visit = __webpack_require__(632); /** * Map `visit` over an array of objects. @@ -72652,7 +72808,7 @@ function isObject(val) { /***/ }), -/* 633 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72665,7 +72821,7 @@ function isObject(val) { -var typeOf = __webpack_require__(634); +var typeOf = __webpack_require__(616); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -72691,128 +72847,6 @@ function filter(arr) { } -/***/ }), -/* 634 */ -/***/ (function(module, exports, __webpack_require__) { - -var isBuffer = __webpack_require__(615); -var toString = Object.prototype.toString; - -/** - * Get the native `typeof` a value. - * - * @param {*} `val` - * @return {*} Native javascript type - */ - -module.exports = function kindOf(val) { - // primitivies - if (typeof val === 'undefined') { - return 'undefined'; - } - if (val === null) { - return 'null'; - } - if (val === true || val === false || val instanceof Boolean) { - return 'boolean'; - } - if (typeof val === 'string' || val instanceof String) { - return 'string'; - } - if (typeof val === 'number' || val instanceof Number) { - return 'number'; - } - - // functions - if (typeof val === 'function' || val instanceof Function) { - return 'function'; - } - - // array - if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { - return 'array'; - } - - // check for instances of RegExp and Date before calling `toString` - if (val instanceof RegExp) { - return 'regexp'; - } - if (val instanceof Date) { - return 'date'; - } - - // other objects - var type = toString.call(val); - - if (type === '[object RegExp]') { - return 'regexp'; - } - if (type === '[object Date]') { - return 'date'; - } - if (type === '[object Arguments]') { - return 'arguments'; - } - if (type === '[object Error]') { - return 'error'; - } - - // buffer - if (isBuffer(val)) { - return 'buffer'; - } - - // es6: Map, WeakMap, Set, WeakSet - if (type === '[object Set]') { - return 'set'; - } - if (type === '[object WeakSet]') { - return 'weakset'; - } - if (type === '[object Map]') { - return 'map'; - } - if (type === '[object WeakMap]') { - return 'weakmap'; - } - if (type === '[object Symbol]') { - return 'symbol'; - } - - // typed arrays - if (type === '[object Int8Array]') { - return 'int8array'; - } - if (type === '[object Uint8Array]') { - return 'uint8array'; - } - if (type === '[object Uint8ClampedArray]') { - return 'uint8clampedarray'; - } - if (type === '[object Int16Array]') { - return 'int16array'; - } - if (type === '[object Uint16Array]') { - return 'uint16array'; - } - if (type === '[object Int32Array]') { - return 'int32array'; - } - if (type === '[object Uint32Array]') { - return 'uint32array'; - } - if (type === '[object Float32Array]') { - return 'float32array'; - } - if (type === '[object Float64Array]') { - return 'float64array'; - } - - // must be a plain object - return 'object'; -}; - - /***/ }), /* 635 */ /***/ (function(module, exports, __webpack_require__) { @@ -72820,7 +72854,7 @@ module.exports = function kindOf(val) { "use strict"; -var isObject = __webpack_require__(605); +var isObject = __webpack_require__(607); var union = __webpack_require__(636); var get = __webpack_require__(637); var set = __webpack_require__(638); @@ -72956,10 +72990,10 @@ function toString(val) { -var split = __webpack_require__(608); -var extend = __webpack_require__(604); -var isPlainObject = __webpack_require__(598); -var isObject = __webpack_require__(605); +var split = __webpack_require__(610); +var extend = __webpack_require__(606); +var isPlainObject = __webpack_require__(600); +var isObject = __webpack_require__(607); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -73182,7 +73216,7 @@ module.exports = function(val, prop) { var typeOf = __webpack_require__(646); -var isNumber = __webpack_require__(613); +var isNumber = __webpack_require__(615); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -73238,7 +73272,7 @@ module.exports = function hasValue(val) { /* 646 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(615); +var isBuffer = __webpack_require__(617); var toString = Object.prototype.toString; /** @@ -73373,10 +73407,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(608); -var extend = __webpack_require__(604); -var isPlainObject = __webpack_require__(598); -var isObject = __webpack_require__(605); +var split = __webpack_require__(610); +var extend = __webpack_require__(606); +var isPlainObject = __webpack_require__(600); +var isObject = __webpack_require__(607); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -73506,7 +73540,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(598); +var isPlainObject = __webpack_require__(600); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -74263,7 +74297,7 @@ module.exports = isAccessorDescriptor; /* 658 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(615); +var isBuffer = __webpack_require__(617); var toString = Object.prototype.toString; /** @@ -74447,7 +74481,7 @@ module.exports = isDataDescriptor; /* 660 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(615); +var isBuffer = __webpack_require__(617); var toString = Object.prototype.toString; /** @@ -74669,8 +74703,8 @@ module.exports = extend; "use strict"; -var typeOf = __webpack_require__(663); -var copyDescriptor = __webpack_require__(664); +var typeOf = __webpack_require__(616); +var copyDescriptor = __webpack_require__(663); var define = __webpack_require__(654); /** @@ -74847,128 +74881,6 @@ module.exports.has = has; /* 663 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(615); -var toString = Object.prototype.toString; - -/** - * Get the native `typeof` a value. - * - * @param {*} `val` - * @return {*} Native javascript type - */ - -module.exports = function kindOf(val) { - // primitivies - if (typeof val === 'undefined') { - return 'undefined'; - } - if (val === null) { - return 'null'; - } - if (val === true || val === false || val instanceof Boolean) { - return 'boolean'; - } - if (typeof val === 'string' || val instanceof String) { - return 'string'; - } - if (typeof val === 'number' || val instanceof Number) { - return 'number'; - } - - // functions - if (typeof val === 'function' || val instanceof Function) { - return 'function'; - } - - // array - if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { - return 'array'; - } - - // check for instances of RegExp and Date before calling `toString` - if (val instanceof RegExp) { - return 'regexp'; - } - if (val instanceof Date) { - return 'date'; - } - - // other objects - var type = toString.call(val); - - if (type === '[object RegExp]') { - return 'regexp'; - } - if (type === '[object Date]') { - return 'date'; - } - if (type === '[object Arguments]') { - return 'arguments'; - } - if (type === '[object Error]') { - return 'error'; - } - - // buffer - if (isBuffer(val)) { - return 'buffer'; - } - - // es6: Map, WeakMap, Set, WeakSet - if (type === '[object Set]') { - return 'set'; - } - if (type === '[object WeakSet]') { - return 'weakset'; - } - if (type === '[object Map]') { - return 'map'; - } - if (type === '[object WeakMap]') { - return 'weakmap'; - } - if (type === '[object Symbol]') { - return 'symbol'; - } - - // typed arrays - if (type === '[object Int8Array]') { - return 'int8array'; - } - if (type === '[object Uint8Array]') { - return 'uint8array'; - } - if (type === '[object Uint8ClampedArray]') { - return 'uint8clampedarray'; - } - if (type === '[object Int16Array]') { - return 'int16array'; - } - if (type === '[object Uint16Array]') { - return 'uint16array'; - } - if (type === '[object Int32Array]') { - return 'int32array'; - } - if (type === '[object Uint32Array]') { - return 'uint32array'; - } - if (type === '[object Float32Array]') { - return 'float32array'; - } - if (type === '[object Float64Array]') { - return 'float64array'; - } - - // must be a plain object - return 'object'; -}; - - -/***/ }), -/* 664 */ -/***/ (function(module, exports, __webpack_require__) { - "use strict"; /*! * copy-descriptor @@ -75054,16 +74966,16 @@ function isObject(val) { /***/ }), -/* 665 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(666); +var use = __webpack_require__(665); var define = __webpack_require__(654); var debug = __webpack_require__(544)('snapdragon:compiler'); -var utils = __webpack_require__(668); +var utils = __webpack_require__(667); /** * Create a new `Compiler` with the given `options`. @@ -75217,7 +75129,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(687); + var sourcemaps = __webpack_require__(686); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -75238,7 +75150,7 @@ module.exports = Compiler; /***/ }), -/* 666 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75251,7 +75163,7 @@ module.exports = Compiler; -var utils = __webpack_require__(667); +var utils = __webpack_require__(666); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -75366,7 +75278,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 667 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75396,7 +75308,7 @@ module.exports = utils; /***/ }), -/* 668 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75406,9 +75318,9 @@ module.exports = utils; * Module dependencies */ -exports.extend = __webpack_require__(604); -exports.SourceMap = __webpack_require__(669); -exports.sourceMapResolve = __webpack_require__(680); +exports.extend = __webpack_require__(606); +exports.SourceMap = __webpack_require__(668); +exports.sourceMapResolve = __webpack_require__(679); /** * Convert backslash in the given string to forward slashes @@ -75451,7 +75363,7 @@ exports.last = function(arr, n) { /***/ }), -/* 669 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -75459,13 +75371,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(670).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(676).SourceMapConsumer; -exports.SourceNode = __webpack_require__(679).SourceNode; +exports.SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(675).SourceMapConsumer; +exports.SourceNode = __webpack_require__(678).SourceNode; /***/ }), -/* 670 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -75475,10 +75387,10 @@ exports.SourceNode = __webpack_require__(679).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(671); -var util = __webpack_require__(673); -var ArraySet = __webpack_require__(674).ArraySet; -var MappingList = __webpack_require__(675).MappingList; +var base64VLQ = __webpack_require__(670); +var util = __webpack_require__(672); +var ArraySet = __webpack_require__(673).ArraySet; +var MappingList = __webpack_require__(674).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -75887,7 +75799,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 671 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -75927,7 +75839,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(672); +var base64 = __webpack_require__(671); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -76033,7 +75945,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 672 */ +/* 671 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76106,7 +76018,7 @@ exports.decode = function (charCode) { /***/ }), -/* 673 */ +/* 672 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76529,7 +76441,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 674 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76539,7 +76451,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(673); +var util = __webpack_require__(672); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -76656,7 +76568,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 675 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76666,7 +76578,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(673); +var util = __webpack_require__(672); /** * Determine whether mappingB is after mappingA with respect to generated @@ -76741,7 +76653,7 @@ exports.MappingList = MappingList; /***/ }), -/* 676 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76751,11 +76663,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(673); -var binarySearch = __webpack_require__(677); -var ArraySet = __webpack_require__(674).ArraySet; -var base64VLQ = __webpack_require__(671); -var quickSort = __webpack_require__(678).quickSort; +var util = __webpack_require__(672); +var binarySearch = __webpack_require__(676); +var ArraySet = __webpack_require__(673).ArraySet; +var base64VLQ = __webpack_require__(670); +var quickSort = __webpack_require__(677).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -77829,7 +77741,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 677 */ +/* 676 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -77946,7 +77858,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 678 */ +/* 677 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78066,7 +77978,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 679 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78076,8 +77988,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(670).SourceMapGenerator; -var util = __webpack_require__(673); +var SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; +var util = __webpack_require__(672); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -78485,17 +78397,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 680 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(681) -var resolveUrl = __webpack_require__(682) -var decodeUriComponent = __webpack_require__(683) -var urix = __webpack_require__(685) -var atob = __webpack_require__(686) +var sourceMappingURL = __webpack_require__(680) +var resolveUrl = __webpack_require__(681) +var decodeUriComponent = __webpack_require__(682) +var urix = __webpack_require__(684) +var atob = __webpack_require__(685) @@ -78793,7 +78705,7 @@ module.exports = { /***/ }), -/* 681 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -78856,7 +78768,7 @@ void (function(root, factory) { /***/ }), -/* 682 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -78874,13 +78786,13 @@ module.exports = resolveUrl /***/ }), -/* 683 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(684) +var decodeUriComponent = __webpack_require__(683) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -78891,7 +78803,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 684 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78992,7 +78904,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 685 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -79015,7 +78927,7 @@ module.exports = urix /***/ }), -/* 686 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79029,7 +78941,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 687 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79038,7 +78950,7 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(142); var path = __webpack_require__(4); var define = __webpack_require__(654); -var utils = __webpack_require__(668); +var utils = __webpack_require__(667); /** * Expose `mixin()`. @@ -79181,19 +79093,19 @@ exports.comment = function(node) { /***/ }), -/* 688 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(666); +var use = __webpack_require__(665); var util = __webpack_require__(115); -var Cache = __webpack_require__(689); +var Cache = __webpack_require__(688); var define = __webpack_require__(654); var debug = __webpack_require__(544)('snapdragon:parser'); -var Position = __webpack_require__(690); -var utils = __webpack_require__(668); +var Position = __webpack_require__(689); +var utils = __webpack_require__(667); /** * Create a new `Parser` with the given `input` and `options`. @@ -79721,7 +79633,7 @@ module.exports = Parser; /***/ }), -/* 689 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79828,7 +79740,7 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 690 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79849,14 +79761,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 691 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(692); -var assignSymbols = __webpack_require__(599); +var isExtendable = __webpack_require__(691); +var assignSymbols = __webpack_require__(601); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -79916,7 +79828,7 @@ function isEnum(obj, key) { /***/ }), -/* 692 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79929,7 +79841,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(598); +var isPlainObject = __webpack_require__(600); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -79937,13 +79849,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 693 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(694); +var nanomatch = __webpack_require__(693); var extglob = __webpack_require__(708); module.exports = function(snapdragon) { @@ -80021,7 +79933,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 694 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80033,16 +79945,16 @@ function escapeExtglobs(compiler) { var util = __webpack_require__(115); var toRegex = __webpack_require__(583); -var extend = __webpack_require__(695); +var extend = __webpack_require__(694); /** * Local dependencies */ -var compilers = __webpack_require__(697); -var parsers = __webpack_require__(698); -var cache = __webpack_require__(701); -var utils = __webpack_require__(703); +var compilers = __webpack_require__(696); +var parsers = __webpack_require__(697); +var cache = __webpack_require__(700); +var utils = __webpack_require__(702); var MAX_LENGTH = 1024 * 64; /** @@ -80866,14 +80778,14 @@ module.exports = nanomatch; /***/ }), -/* 695 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(696); -var assignSymbols = __webpack_require__(599); +var isExtendable = __webpack_require__(695); +var assignSymbols = __webpack_require__(601); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -80933,7 +80845,7 @@ function isEnum(obj, key) { /***/ }), -/* 696 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80946,7 +80858,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(598); +var isPlainObject = __webpack_require__(600); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -80954,7 +80866,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 697 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81300,15 +81212,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 698 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(600); +var regexNot = __webpack_require__(602); var toRegex = __webpack_require__(583); -var isOdd = __webpack_require__(699); +var isOdd = __webpack_require__(698); /** * Characters to use in negation regex (we want to "not" match @@ -81694,7 +81606,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 699 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81707,7 +81619,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(700); +var isNumber = __webpack_require__(699); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -81721,7 +81633,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 700 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81749,14 +81661,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 701 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(702))(); +module.exports = new (__webpack_require__(701))(); /***/ }), -/* 702 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81769,7 +81681,7 @@ module.exports = new (__webpack_require__(702))(); -var MapCache = __webpack_require__(689); +var MapCache = __webpack_require__(688); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -81891,7 +81803,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 703 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81904,14 +81816,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(704)(); -var Snapdragon = __webpack_require__(625); -utils.define = __webpack_require__(705); -utils.diff = __webpack_require__(706); -utils.extend = __webpack_require__(695); -utils.pick = __webpack_require__(707); -utils.typeOf = __webpack_require__(593); -utils.unique = __webpack_require__(603); +var isWindows = __webpack_require__(703)(); +var Snapdragon = __webpack_require__(626); +utils.define = __webpack_require__(704); +utils.diff = __webpack_require__(705); +utils.extend = __webpack_require__(694); +utils.pick = __webpack_require__(706); +utils.typeOf = __webpack_require__(707); +utils.unique = __webpack_require__(605); /** * Returns true if the given value is effectively an empty string @@ -82277,7 +82189,7 @@ utils.unixify = function(options) { /***/ }), -/* 704 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -82305,7 +82217,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 705 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82350,7 +82262,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 706 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82404,7 +82316,7 @@ function diffArray(one, two) { /***/ }), -/* 707 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82445,6 +82357,141 @@ module.exports = function pick(obj, keys) { }; +/***/ }), +/* 707 */ +/***/ (function(module, exports) { + +var toString = Object.prototype.toString; + +module.exports = function kindOf(val) { + if (val === void 0) return 'undefined'; + if (val === null) return 'null'; + + var type = typeof val; + if (type === 'boolean') return 'boolean'; + if (type === 'string') return 'string'; + if (type === 'number') return 'number'; + if (type === 'symbol') return 'symbol'; + if (type === 'function') { + return isGeneratorFn(val) ? 'generatorfunction' : 'function'; + } + + if (isArray(val)) return 'array'; + if (isBuffer(val)) return 'buffer'; + if (isArguments(val)) return 'arguments'; + if (isDate(val)) return 'date'; + if (isError(val)) return 'error'; + if (isRegexp(val)) return 'regexp'; + + switch (ctorName(val)) { + case 'Symbol': return 'symbol'; + case 'Promise': return 'promise'; + + // Set, Map, WeakSet, WeakMap + case 'WeakMap': return 'weakmap'; + case 'WeakSet': return 'weakset'; + case 'Map': return 'map'; + case 'Set': return 'set'; + + // 8-bit typed arrays + case 'Int8Array': return 'int8array'; + case 'Uint8Array': return 'uint8array'; + case 'Uint8ClampedArray': return 'uint8clampedarray'; + + // 16-bit typed arrays + case 'Int16Array': return 'int16array'; + case 'Uint16Array': return 'uint16array'; + + // 32-bit typed arrays + case 'Int32Array': return 'int32array'; + case 'Uint32Array': return 'uint32array'; + case 'Float32Array': return 'float32array'; + case 'Float64Array': return 'float64array'; + } + + if (isGeneratorObj(val)) { + return 'generator'; + } + + // Non-plain objects + type = toString.call(val); + switch (type) { + case '[object Object]': return 'object'; + // iterators + case '[object Map Iterator]': return 'mapiterator'; + case '[object Set Iterator]': return 'setiterator'; + case '[object String Iterator]': return 'stringiterator'; + case '[object Array Iterator]': return 'arrayiterator'; + } + + // other + return type.slice(8, -1).toLowerCase().replace(/\s/g, ''); +}; + +function ctorName(val) { + return typeof val.constructor === 'function' ? val.constructor.name : null; +} + +function isArray(val) { + if (Array.isArray) return Array.isArray(val); + return val instanceof Array; +} + +function isError(val) { + return val instanceof Error || (typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number'); +} + +function isDate(val) { + if (val instanceof Date) return true; + return typeof val.toDateString === 'function' + && typeof val.getDate === 'function' + && typeof val.setDate === 'function'; +} + +function isRegexp(val) { + if (val instanceof RegExp) return true; + return typeof val.flags === 'string' + && typeof val.ignoreCase === 'boolean' + && typeof val.multiline === 'boolean' + && typeof val.global === 'boolean'; +} + +function isGeneratorFn(name, val) { + return ctorName(name) === 'GeneratorFunction'; +} + +function isGeneratorObj(val) { + return typeof val.throw === 'function' + && typeof val.return === 'function' + && typeof val.next === 'function'; +} + +function isArguments(val) { + try { + if (typeof val.length === 'number' && typeof val.callee === 'function') { + return true; + } + } catch (err) { + if (err.message.indexOf('callee') !== -1) { + return true; + } + } + return false; +} + +/** + * If you need to support Safari 5-7 (8-10 yr-old browser), + * take a look at https://github.com/feross/is-buffer + */ + +function isBuffer(val) { + if (val.constructor && typeof val.constructor.isBuffer === 'function') { + return val.constructor.isBuffer(val); + } + return false; +} + + /***/ }), /* 708 */ /***/ (function(module, exports, __webpack_require__) { @@ -82456,8 +82503,8 @@ module.exports = function pick(obj, keys) { * Module dependencies */ -var extend = __webpack_require__(604); -var unique = __webpack_require__(603); +var extend = __webpack_require__(606); +var unique = __webpack_require__(605); var toRegex = __webpack_require__(583); /** @@ -82978,8 +83025,8 @@ var parsers = __webpack_require__(713); */ var debug = __webpack_require__(544)('expand-brackets'); -var extend = __webpack_require__(604); -var Snapdragon = __webpack_require__(625); +var extend = __webpack_require__(606); +var Snapdragon = __webpack_require__(626); var toRegex = __webpack_require__(583); /** @@ -83534,7 +83581,7 @@ module.exports.TEXT_REGEX = TEXT_REGEX; var toRegex = __webpack_require__(583); -var regexNot = __webpack_require__(600); +var regexNot = __webpack_require__(602); var cached; /** @@ -83775,8 +83822,8 @@ module.exports = function defineProperty(obj, prop, val) { "use strict"; -var regex = __webpack_require__(600); -var Cache = __webpack_require__(702); +var regex = __webpack_require__(602); +var Cache = __webpack_require__(701); /** * Utils @@ -83855,9 +83902,9 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(625); +var Snapdragon = __webpack_require__(626); var define = __webpack_require__(716); -var extend = __webpack_require__(604); +var extend = __webpack_require__(606); /** * Local dependencies @@ -83937,8 +83984,8 @@ module.exports = Extglob; var extglob = __webpack_require__(708); -var nanomatch = __webpack_require__(694); -var regexNot = __webpack_require__(600); +var nanomatch = __webpack_require__(693); +var regexNot = __webpack_require__(602); var toRegex = __webpack_require__(583); var not; @@ -84023,7 +84070,7 @@ function textRegex(pattern) { /* 720 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(702))(); +module.exports = new (__webpack_require__(701))(); /***/ }), @@ -84040,13 +84087,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(625); +var Snapdragon = __webpack_require__(626); utils.define = __webpack_require__(722); -utils.diff = __webpack_require__(706); -utils.extend = __webpack_require__(691); -utils.pick = __webpack_require__(707); -utils.typeOf = __webpack_require__(593); -utils.unique = __webpack_require__(603); +utils.diff = __webpack_require__(705); +utils.extend = __webpack_require__(690); +utils.pick = __webpack_require__(706); +utils.typeOf = __webpack_require__(723); +utils.unique = __webpack_require__(605); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -84389,6 +84436,141 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), /* 723 */ +/***/ (function(module, exports) { + +var toString = Object.prototype.toString; + +module.exports = function kindOf(val) { + if (val === void 0) return 'undefined'; + if (val === null) return 'null'; + + var type = typeof val; + if (type === 'boolean') return 'boolean'; + if (type === 'string') return 'string'; + if (type === 'number') return 'number'; + if (type === 'symbol') return 'symbol'; + if (type === 'function') { + return isGeneratorFn(val) ? 'generatorfunction' : 'function'; + } + + if (isArray(val)) return 'array'; + if (isBuffer(val)) return 'buffer'; + if (isArguments(val)) return 'arguments'; + if (isDate(val)) return 'date'; + if (isError(val)) return 'error'; + if (isRegexp(val)) return 'regexp'; + + switch (ctorName(val)) { + case 'Symbol': return 'symbol'; + case 'Promise': return 'promise'; + + // Set, Map, WeakSet, WeakMap + case 'WeakMap': return 'weakmap'; + case 'WeakSet': return 'weakset'; + case 'Map': return 'map'; + case 'Set': return 'set'; + + // 8-bit typed arrays + case 'Int8Array': return 'int8array'; + case 'Uint8Array': return 'uint8array'; + case 'Uint8ClampedArray': return 'uint8clampedarray'; + + // 16-bit typed arrays + case 'Int16Array': return 'int16array'; + case 'Uint16Array': return 'uint16array'; + + // 32-bit typed arrays + case 'Int32Array': return 'int32array'; + case 'Uint32Array': return 'uint32array'; + case 'Float32Array': return 'float32array'; + case 'Float64Array': return 'float64array'; + } + + if (isGeneratorObj(val)) { + return 'generator'; + } + + // Non-plain objects + type = toString.call(val); + switch (type) { + case '[object Object]': return 'object'; + // iterators + case '[object Map Iterator]': return 'mapiterator'; + case '[object Set Iterator]': return 'setiterator'; + case '[object String Iterator]': return 'stringiterator'; + case '[object Array Iterator]': return 'arrayiterator'; + } + + // other + return type.slice(8, -1).toLowerCase().replace(/\s/g, ''); +}; + +function ctorName(val) { + return typeof val.constructor === 'function' ? val.constructor.name : null; +} + +function isArray(val) { + if (Array.isArray) return Array.isArray(val); + return val instanceof Array; +} + +function isError(val) { + return val instanceof Error || (typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number'); +} + +function isDate(val) { + if (val instanceof Date) return true; + return typeof val.toDateString === 'function' + && typeof val.getDate === 'function' + && typeof val.setDate === 'function'; +} + +function isRegexp(val) { + if (val instanceof RegExp) return true; + return typeof val.flags === 'string' + && typeof val.ignoreCase === 'boolean' + && typeof val.multiline === 'boolean' + && typeof val.global === 'boolean'; +} + +function isGeneratorFn(name, val) { + return ctorName(name) === 'GeneratorFunction'; +} + +function isGeneratorObj(val) { + return typeof val.throw === 'function' + && typeof val.return === 'function' + && typeof val.next === 'function'; +} + +function isArguments(val) { + try { + if (typeof val.length === 'number' && typeof val.callee === 'function') { + return true; + } + } catch (err) { + if (err.message.indexOf('callee') !== -1) { + return true; + } + } + return false; +} + +/** + * If you need to support Safari 5-7 (8-10 yr-old browser), + * take a look at https://github.com/feross/is-buffer + */ + +function isBuffer(val) { + if (val.constructor && typeof val.constructor.isBuffer === 'function') { + return val.constructor.isBuffer(val); + } + return false; +} + + +/***/ }), +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84407,9 +84589,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(724); -var reader_1 = __webpack_require__(737); -var fs_stream_1 = __webpack_require__(741); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_stream_1 = __webpack_require__(742); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -84470,15 +84652,15 @@ exports.default = ReaderAsync; /***/ }), -/* 724 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(725); -const readdirAsync = __webpack_require__(733); -const readdirStream = __webpack_require__(736); +const readdirSync = __webpack_require__(726); +const readdirAsync = __webpack_require__(734); +const readdirStream = __webpack_require__(737); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -84562,7 +84744,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 725 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84570,11 +84752,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(726); +const DirectoryReader = __webpack_require__(727); let syncFacade = { - fs: __webpack_require__(731), - forEach: __webpack_require__(732), + fs: __webpack_require__(732), + forEach: __webpack_require__(733), sync: true }; @@ -84603,7 +84785,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 726 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84612,9 +84794,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(134).Readable; const EventEmitter = __webpack_require__(166).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(727); -const stat = __webpack_require__(729); -const call = __webpack_require__(730); +const normalizeOptions = __webpack_require__(728); +const stat = __webpack_require__(730); +const call = __webpack_require__(731); /** * Asynchronously reads the contents of a directory and streams the results @@ -84990,14 +85172,14 @@ module.exports = DirectoryReader; /***/ }), -/* 727 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(728); +const globToRegExp = __webpack_require__(729); module.exports = normalizeOptions; @@ -85174,7 +85356,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 728 */ +/* 729 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -85311,13 +85493,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 729 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(730); +const call = __webpack_require__(731); module.exports = stat; @@ -85392,7 +85574,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 730 */ +/* 731 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85453,14 +85635,14 @@ function callOnce (fn) { /***/ }), -/* 731 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(142); -const call = __webpack_require__(730); +const call = __webpack_require__(731); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -85524,7 +85706,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 732 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85553,7 +85735,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 733 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85561,12 +85743,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(734); -const DirectoryReader = __webpack_require__(726); +const maybe = __webpack_require__(735); +const DirectoryReader = __webpack_require__(727); let asyncFacade = { fs: __webpack_require__(142), - forEach: __webpack_require__(735), + forEach: __webpack_require__(736), async: true }; @@ -85608,7 +85790,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 734 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85635,7 +85817,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 735 */ +/* 736 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85671,7 +85853,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 736 */ +/* 737 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85679,11 +85861,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(726); +const DirectoryReader = __webpack_require__(727); let streamFacade = { fs: __webpack_require__(142), - forEach: __webpack_require__(735), + forEach: __webpack_require__(736), async: true }; @@ -85703,16 +85885,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 737 */ +/* 738 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(738); -var entry_1 = __webpack_require__(740); -var pathUtil = __webpack_require__(739); +var deep_1 = __webpack_require__(739); +var entry_1 = __webpack_require__(741); +var pathUtil = __webpack_require__(740); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -85778,13 +85960,13 @@ exports.default = Reader; /***/ }), -/* 738 */ +/* 739 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(739); +var pathUtils = __webpack_require__(740); var patternUtils = __webpack_require__(577); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -85868,7 +86050,7 @@ exports.default = DeepFilter; /***/ }), -/* 739 */ +/* 740 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85899,13 +86081,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 740 */ +/* 741 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(739); +var pathUtils = __webpack_require__(740); var patternUtils = __webpack_require__(577); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -85991,7 +86173,7 @@ exports.default = EntryFilter; /***/ }), -/* 741 */ +/* 742 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86011,8 +86193,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(134); -var fsStat = __webpack_require__(742); -var fs_1 = __webpack_require__(746); +var fsStat = __webpack_require__(743); +var fs_1 = __webpack_require__(747); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -86062,14 +86244,14 @@ exports.default = FileSystemStream; /***/ }), -/* 742 */ +/* 743 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(743); -const statProvider = __webpack_require__(745); +const optionsManager = __webpack_require__(744); +const statProvider = __webpack_require__(746); /** * Asynchronous API. */ @@ -86100,13 +86282,13 @@ exports.statSync = statSync; /***/ }), -/* 743 */ +/* 744 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(744); +const fsAdapter = __webpack_require__(745); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -86119,7 +86301,7 @@ exports.prepare = prepare; /***/ }), -/* 744 */ +/* 745 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86142,7 +86324,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 745 */ +/* 746 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86194,7 +86376,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 746 */ +/* 747 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86225,7 +86407,7 @@ exports.default = FileSystem; /***/ }), -/* 747 */ +/* 748 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86245,9 +86427,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(134); -var readdir = __webpack_require__(724); -var reader_1 = __webpack_require__(737); -var fs_stream_1 = __webpack_require__(741); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_stream_1 = __webpack_require__(742); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -86315,7 +86497,7 @@ exports.default = ReaderStream; /***/ }), -/* 748 */ +/* 749 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86334,9 +86516,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(724); -var reader_1 = __webpack_require__(737); -var fs_sync_1 = __webpack_require__(749); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_sync_1 = __webpack_require__(750); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -86396,7 +86578,7 @@ exports.default = ReaderSync; /***/ }), -/* 749 */ +/* 750 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86415,8 +86597,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(742); -var fs_1 = __webpack_require__(746); +var fsStat = __webpack_require__(743); +var fs_1 = __webpack_require__(747); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -86462,7 +86644,7 @@ exports.default = FileSystemSync; /***/ }), -/* 750 */ +/* 751 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86478,7 +86660,7 @@ exports.flatten = flatten; /***/ }), -/* 751 */ +/* 752 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86499,13 +86681,13 @@ exports.merge = merge; /***/ }), -/* 752 */ +/* 753 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(753); +const pathType = __webpack_require__(754); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -86571,13 +86753,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 753 */ +/* 754 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(142); -const pify = __webpack_require__(754); +const pify = __webpack_require__(755); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -86620,7 +86802,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 754 */ +/* 755 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86711,7 +86893,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 755 */ +/* 756 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86719,9 +86901,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(142); const path = __webpack_require__(4); const fastGlob = __webpack_require__(573); -const gitIgnore = __webpack_require__(756); -const pify = __webpack_require__(757); -const slash = __webpack_require__(758); +const gitIgnore = __webpack_require__(757); +const pify = __webpack_require__(758); +const slash = __webpack_require__(759); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -86819,7 +87001,7 @@ module.exports.sync = options => { /***/ }), -/* 756 */ +/* 757 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -87288,7 +87470,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 757 */ +/* 758 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87363,7 +87545,7 @@ module.exports = (input, options) => { /***/ }), -/* 758 */ +/* 759 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87381,7 +87563,7 @@ module.exports = input => { /***/ }), -/* 759 */ +/* 760 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87394,7 +87576,7 @@ module.exports = input => { -var isGlob = __webpack_require__(760); +var isGlob = __webpack_require__(761); module.exports = function hasGlob(val) { if (val == null) return false; @@ -87414,7 +87596,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 760 */ +/* 761 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -87445,17 +87627,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 761 */ +/* 762 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(142); -const pEvent = __webpack_require__(762); -const CpFileError = __webpack_require__(765); -const fs = __webpack_require__(767); -const ProgressEmitter = __webpack_require__(770); +const pEvent = __webpack_require__(763); +const CpFileError = __webpack_require__(766); +const fs = __webpack_require__(768); +const ProgressEmitter = __webpack_require__(771); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -87569,12 +87751,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 762 */ +/* 763 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(763); +const pTimeout = __webpack_require__(764); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -87865,12 +88047,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 763 */ +/* 764 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(764); +const pFinally = __webpack_require__(765); class TimeoutError extends Error { constructor(message) { @@ -87916,7 +88098,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 764 */ +/* 765 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87938,12 +88120,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 765 */ +/* 766 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(766); +const NestedError = __webpack_require__(767); class CpFileError extends NestedError { constructor(message, nested) { @@ -87957,7 +88139,7 @@ module.exports = CpFileError; /***/ }), -/* 766 */ +/* 767 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(115).inherits; @@ -88013,16 +88195,16 @@ module.exports = NestedError; /***/ }), -/* 767 */ +/* 768 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(115); const fs = __webpack_require__(190); -const makeDir = __webpack_require__(768); -const pEvent = __webpack_require__(762); -const CpFileError = __webpack_require__(765); +const makeDir = __webpack_require__(769); +const pEvent = __webpack_require__(763); +const CpFileError = __webpack_require__(766); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -88119,7 +88301,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 768 */ +/* 769 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88127,7 +88309,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(142); const path = __webpack_require__(4); const {promisify} = __webpack_require__(115); -const semver = __webpack_require__(769); +const semver = __webpack_require__(770); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -88282,7 +88464,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 769 */ +/* 770 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -89884,7 +90066,7 @@ function coerce (version, options) { /***/ }), -/* 770 */ +/* 771 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89925,7 +90107,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 771 */ +/* 772 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89971,12 +90153,12 @@ exports.default = module.exports; /***/ }), -/* 772 */ +/* 773 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(773); +const pMap = __webpack_require__(774); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -89993,7 +90175,7 @@ module.exports.default = pFilter; /***/ }), -/* 773 */ +/* 774 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90072,12 +90254,12 @@ module.exports.default = pMap; /***/ }), -/* 774 */ +/* 775 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(766); +const NestedError = __webpack_require__(767); class CpyError extends NestedError { constructor(message, nested) { @@ -90091,7 +90273,7 @@ module.exports = CpyError; /***/ }), -/* 775 */ +/* 776 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90099,10 +90281,10 @@ module.exports = CpyError; const fs = __webpack_require__(142); const arrayUnion = __webpack_require__(199); const merge2 = __webpack_require__(200); -const fastGlob = __webpack_require__(776); +const fastGlob = __webpack_require__(777); const dirGlob = __webpack_require__(283); -const gitignore = __webpack_require__(811); -const {FilterStream, UniqueStream} = __webpack_require__(812); +const gitignore = __webpack_require__(812); +const {FilterStream, UniqueStream} = __webpack_require__(813); const DEFAULT_FILTER = () => false; @@ -90279,17 +90461,17 @@ module.exports.gitignore = gitignore; /***/ }), -/* 776 */ +/* 777 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const taskManager = __webpack_require__(777); -const async_1 = __webpack_require__(797); -const stream_1 = __webpack_require__(807); -const sync_1 = __webpack_require__(808); -const settings_1 = __webpack_require__(810); -const utils = __webpack_require__(778); +const taskManager = __webpack_require__(778); +const async_1 = __webpack_require__(798); +const stream_1 = __webpack_require__(808); +const sync_1 = __webpack_require__(809); +const settings_1 = __webpack_require__(811); +const utils = __webpack_require__(779); async function FastGlob(source, options) { assertPatternsInput(source); const works = getWorks(source, async_1.default, options); @@ -90353,14 +90535,14 @@ module.exports = FastGlob; /***/ }), -/* 777 */ +/* 778 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertPatternGroupToTask = exports.convertPatternGroupsToTasks = exports.groupPatternsByBaseDirectory = exports.getNegativePatternsAsPositive = exports.getPositivePatterns = exports.convertPatternsToTasks = exports.generate = void 0; -const utils = __webpack_require__(778); +const utils = __webpack_require__(779); function generate(patterns, settings) { const positivePatterns = getPositivePatterns(patterns); const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); @@ -90425,31 +90607,31 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 778 */ +/* 779 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.string = exports.stream = exports.pattern = exports.path = exports.fs = exports.errno = exports.array = void 0; -const array = __webpack_require__(779); +const array = __webpack_require__(780); exports.array = array; -const errno = __webpack_require__(780); +const errno = __webpack_require__(781); exports.errno = errno; -const fs = __webpack_require__(781); +const fs = __webpack_require__(782); exports.fs = fs; -const path = __webpack_require__(782); +const path = __webpack_require__(783); exports.path = path; -const pattern = __webpack_require__(783); +const pattern = __webpack_require__(784); exports.pattern = pattern; -const stream = __webpack_require__(795); +const stream = __webpack_require__(796); exports.stream = stream; -const string = __webpack_require__(796); +const string = __webpack_require__(797); exports.string = string; /***/ }), -/* 779 */ +/* 780 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90478,7 +90660,7 @@ exports.splitWhen = splitWhen; /***/ }), -/* 780 */ +/* 781 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90492,7 +90674,7 @@ exports.isEnoentCodeError = isEnoentCodeError; /***/ }), -/* 781 */ +/* 782 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90518,7 +90700,7 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 782 */ +/* 783 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90558,7 +90740,7 @@ exports.removeLeadingDotSegment = removeLeadingDotSegment; /***/ }), -/* 783 */ +/* 784 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90567,7 +90749,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.matchAny = exports.convertPatternsToRe = exports.makeRe = exports.getPatternParts = exports.expandBraceExpansion = exports.expandPatternsWithBraceExpansion = exports.isAffectDepthOfReadingPattern = exports.endsWithSlashGlobStar = exports.hasGlobStar = exports.getBaseDirectory = exports.getPositivePatterns = exports.getNegativePatterns = exports.isPositivePattern = exports.isNegativePattern = exports.convertToNegativePattern = exports.convertToPositivePattern = exports.isDynamicPattern = exports.isStaticPattern = void 0; const path = __webpack_require__(4); const globParent = __webpack_require__(222); -const micromatch = __webpack_require__(784); +const micromatch = __webpack_require__(785); const picomatch = __webpack_require__(236); const GLOBSTAR = '**'; const ESCAPE_SYMBOL = '\\'; @@ -90697,14 +90879,14 @@ exports.matchAny = matchAny; /***/ }), -/* 784 */ +/* 785 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const util = __webpack_require__(115); -const braces = __webpack_require__(785); +const braces = __webpack_require__(786); const picomatch = __webpack_require__(236); const utils = __webpack_require__(239); const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); @@ -91171,16 +91353,16 @@ module.exports = micromatch; /***/ }), -/* 785 */ +/* 786 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(786); -const compile = __webpack_require__(788); -const expand = __webpack_require__(792); -const parse = __webpack_require__(793); +const stringify = __webpack_require__(787); +const compile = __webpack_require__(789); +const expand = __webpack_require__(793); +const parse = __webpack_require__(794); /** * Expand the given pattern or create a regex-compatible string. @@ -91348,13 +91530,13 @@ module.exports = braces; /***/ }), -/* 786 */ +/* 787 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(787); +const utils = __webpack_require__(788); module.exports = (ast, options = {}) => { let stringify = (node, parent = {}) => { @@ -91387,7 +91569,7 @@ module.exports = (ast, options = {}) => { /***/ }), -/* 787 */ +/* 788 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91506,14 +91688,14 @@ exports.flatten = (...args) => { /***/ }), -/* 788 */ +/* 789 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(789); -const utils = __webpack_require__(787); +const fill = __webpack_require__(790); +const utils = __webpack_require__(788); const compile = (ast, options = {}) => { let walk = (node, parent = {}) => { @@ -91570,7 +91752,7 @@ module.exports = compile; /***/ }), -/* 789 */ +/* 790 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91584,7 +91766,7 @@ module.exports = compile; const util = __webpack_require__(115); -const toRegexRange = __webpack_require__(790); +const toRegexRange = __webpack_require__(791); const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); @@ -91826,7 +92008,7 @@ module.exports = fill; /***/ }), -/* 790 */ +/* 791 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91839,7 +92021,7 @@ module.exports = fill; -const isNumber = __webpack_require__(791); +const isNumber = __webpack_require__(792); const toRegexRange = (min, max, options) => { if (isNumber(min) === false) { @@ -92121,7 +92303,7 @@ module.exports = toRegexRange; /***/ }), -/* 791 */ +/* 792 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92146,15 +92328,15 @@ module.exports = function(num) { /***/ }), -/* 792 */ +/* 793 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(789); -const stringify = __webpack_require__(786); -const utils = __webpack_require__(787); +const fill = __webpack_require__(790); +const stringify = __webpack_require__(787); +const utils = __webpack_require__(788); const append = (queue = '', stash = '', enclose = false) => { let result = []; @@ -92266,13 +92448,13 @@ module.exports = expand; /***/ }), -/* 793 */ +/* 794 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(786); +const stringify = __webpack_require__(787); /** * Constants @@ -92294,7 +92476,7 @@ const { CHAR_SINGLE_QUOTE, /* ' */ CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(794); +} = __webpack_require__(795); /** * parse @@ -92606,7 +92788,7 @@ module.exports = parse; /***/ }), -/* 794 */ +/* 795 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92670,7 +92852,7 @@ module.exports = { /***/ }), -/* 795 */ +/* 796 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92694,7 +92876,7 @@ function propagateCloseEventToSources(streams) { /***/ }), -/* 796 */ +/* 797 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92712,14 +92894,14 @@ exports.isEmpty = isEmpty; /***/ }), -/* 797 */ +/* 798 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(798); -const provider_1 = __webpack_require__(800); +const stream_1 = __webpack_require__(799); +const provider_1 = __webpack_require__(801); class ProviderAsync extends provider_1.default { constructor() { super(...arguments); @@ -92747,7 +92929,7 @@ exports.default = ProviderAsync; /***/ }), -/* 798 */ +/* 799 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92756,7 +92938,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(134); const fsStat = __webpack_require__(246); const fsWalk = __webpack_require__(251); -const reader_1 = __webpack_require__(799); +const reader_1 = __webpack_require__(800); class ReaderStream extends reader_1.default { constructor() { super(...arguments); @@ -92809,7 +92991,7 @@ exports.default = ReaderStream; /***/ }), -/* 799 */ +/* 800 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92817,7 +92999,7 @@ exports.default = ReaderStream; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); const fsStat = __webpack_require__(246); -const utils = __webpack_require__(778); +const utils = __webpack_require__(779); class Reader { constructor(_settings) { this._settings = _settings; @@ -92849,17 +93031,17 @@ exports.default = Reader; /***/ }), -/* 800 */ +/* 801 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const deep_1 = __webpack_require__(801); -const entry_1 = __webpack_require__(804); -const error_1 = __webpack_require__(805); -const entry_2 = __webpack_require__(806); +const deep_1 = __webpack_require__(802); +const entry_1 = __webpack_require__(805); +const error_1 = __webpack_require__(806); +const entry_2 = __webpack_require__(807); class Provider { constructor(_settings) { this._settings = _settings; @@ -92904,14 +93086,14 @@ exports.default = Provider; /***/ }), -/* 801 */ +/* 802 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(778); -const partial_1 = __webpack_require__(802); +const utils = __webpack_require__(779); +const partial_1 = __webpack_require__(803); class DeepFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -92973,13 +93155,13 @@ exports.default = DeepFilter; /***/ }), -/* 802 */ +/* 803 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const matcher_1 = __webpack_require__(803); +const matcher_1 = __webpack_require__(804); class PartialMatcher extends matcher_1.default { match(filepath) { const parts = filepath.split('/'); @@ -93018,13 +93200,13 @@ exports.default = PartialMatcher; /***/ }), -/* 803 */ +/* 804 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(778); +const utils = __webpack_require__(779); class Matcher { constructor(_patterns, _settings, _micromatchOptions) { this._patterns = _patterns; @@ -93075,13 +93257,13 @@ exports.default = Matcher; /***/ }), -/* 804 */ +/* 805 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(778); +const utils = __webpack_require__(779); class EntryFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -93138,13 +93320,13 @@ exports.default = EntryFilter; /***/ }), -/* 805 */ +/* 806 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(778); +const utils = __webpack_require__(779); class ErrorFilter { constructor(_settings) { this._settings = _settings; @@ -93160,13 +93342,13 @@ exports.default = ErrorFilter; /***/ }), -/* 806 */ +/* 807 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(778); +const utils = __webpack_require__(779); class EntryTransformer { constructor(_settings) { this._settings = _settings; @@ -93193,15 +93375,15 @@ exports.default = EntryTransformer; /***/ }), -/* 807 */ +/* 808 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(134); -const stream_2 = __webpack_require__(798); -const provider_1 = __webpack_require__(800); +const stream_2 = __webpack_require__(799); +const provider_1 = __webpack_require__(801); class ProviderStream extends provider_1.default { constructor() { super(...arguments); @@ -93231,14 +93413,14 @@ exports.default = ProviderStream; /***/ }), -/* 808 */ +/* 809 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(809); -const provider_1 = __webpack_require__(800); +const sync_1 = __webpack_require__(810); +const provider_1 = __webpack_require__(801); class ProviderSync extends provider_1.default { constructor() { super(...arguments); @@ -93261,7 +93443,7 @@ exports.default = ProviderSync; /***/ }), -/* 809 */ +/* 810 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93269,7 +93451,7 @@ exports.default = ProviderSync; Object.defineProperty(exports, "__esModule", { value: true }); const fsStat = __webpack_require__(246); const fsWalk = __webpack_require__(251); -const reader_1 = __webpack_require__(799); +const reader_1 = __webpack_require__(800); class ReaderSync extends reader_1.default { constructor() { super(...arguments); @@ -93311,7 +93493,7 @@ exports.default = ReaderSync; /***/ }), -/* 810 */ +/* 811 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93375,7 +93557,7 @@ exports.default = Settings; /***/ }), -/* 811 */ +/* 812 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93383,7 +93565,7 @@ exports.default = Settings; const {promisify} = __webpack_require__(115); const fs = __webpack_require__(142); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(776); +const fastGlob = __webpack_require__(777); const gitIgnore = __webpack_require__(286); const slash = __webpack_require__(287); @@ -93502,7 +93684,7 @@ module.exports.sync = options => { /***/ }), -/* 812 */ +/* 813 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93555,7 +93737,7 @@ module.exports = { /***/ }), -/* 813 */ +/* 814 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 48cce0365ca811..87276bc5841089 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -102,9 +102,20 @@ export const BootstrapCommand: ICommand = { // NOTE: We don't probably need this anymore, is actually not being used await linkProjectExecutables(projects, projectGraph); + // Update vscode settings + await spawnStreaming( + process.execPath, + ['scripts/update_vscode_config'], + { + cwd: kbn.getAbsolute(), + env: process.env, + }, + { prefix: '[vscode]', debug: false } + ); + // Build typescript references await spawnStreaming( - 'node', + process.execPath, ['scripts/build_ts_refs', '--ignore-type-failures', '--info'], { cwd: kbn.getAbsolute(), diff --git a/packages/kbn-securitysolution-list-hooks/.babelrc b/packages/kbn-securitysolution-list-hooks/.babelrc new file mode 100644 index 00000000000000..40a198521b9035 --- /dev/null +++ b/packages/kbn-securitysolution-list-hooks/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-hooks/.babelrc.browser b/packages/kbn-securitysolution-list-hooks/.babelrc.browser new file mode 100644 index 00000000000000..71bbfbcd6eb2f8 --- /dev/null +++ b/packages/kbn-securitysolution-list-hooks/.babelrc.browser @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/webpack_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-hooks/BUILD.bazel b/packages/kbn-securitysolution-list-hooks/BUILD.bazel index 87075604a75c34..ba8c579bb97ded 100644 --- a/packages/kbn-securitysolution-list-hooks/BUILD.bazel +++ b/packages/kbn-securitysolution-list-hooks/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-securitysolution-list-hooks" @@ -27,28 +28,42 @@ NPM_MODULE_EXTRA_FILES = [ "README.md", ] -SRC_DEPS = [ +RUNTIME_DEPS = [ "//packages/kbn-securitysolution-hook-utils", "//packages/kbn-securitysolution-io-ts-list-types", "//packages/kbn-securitysolution-list-api", "//packages/kbn-securitysolution-list-constants", "//packages/kbn-securitysolution-list-utils", "//packages/kbn-securitysolution-utils", - "@npm//lodash", - "@npm//tslib", + "@npm//@testing-library/react-hooks", "@npm//react", - "@npm//react-intl", ] TYPES_DEPS = [ + "//packages/kbn-securitysolution-hook-utils", + "//packages/kbn-securitysolution-io-ts-list-types", + "//packages/kbn-securitysolution-list-api", + "//packages/kbn-securitysolution-list-constants", + "//packages/kbn-securitysolution-list-utils", + "//packages/kbn-securitysolution-utils", "@npm//@types/jest", - "@npm//@types/lodash", "@npm//@types/node", "@npm//@types/react", - "@npm//@types/react-intl", + "@npm//@types/testing-library__react-hooks", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + config_file = ".babelrc.browser" +) ts_config( name = "tsconfig", @@ -60,24 +75,25 @@ ts_config( ) ts_project( - name = "tsc", - srcs = SRCS, + name = "tsc_types", args = ["--pretty"], + srcs = SRCS, + deps = TYPES_DEPS, declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", root_dir = "src", source_map = True, tsconfig = ":tsconfig", - deps = DEPS, ) js_library( name = PKG_BASE_NAME, - package_name = PKG_REQUIRE_NAME, srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], - deps = DEPS + [":tsc"], ) pkg_npm( diff --git a/packages/kbn-securitysolution-list-hooks/package.json b/packages/kbn-securitysolution-list-hooks/package.json index 2eb456da97b727..ffd4ad61850fcb 100644 --- a/packages/kbn-securitysolution-list-hooks/package.json +++ b/packages/kbn-securitysolution-list-hooks/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "description": "Security solution list ReactJS hooks", "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target/index.js", - "types": "./target/index.d.ts", + "browser": "./target_web/index.js", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", "private": true } diff --git a/packages/kbn-securitysolution-list-hooks/tsconfig.json b/packages/kbn-securitysolution-list-hooks/tsconfig.json index 9b09c02bd4aa18..41ec03f2ebf352 100644 --- a/packages/kbn-securitysolution-list-hooks/tsconfig.json +++ b/packages/kbn-securitysolution-list-hooks/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "outDir": "target", + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-securitysolution-list-hooks/src", diff --git a/packages/kbn-securitysolution-list-utils/.babelrc b/packages/kbn-securitysolution-list-utils/.babelrc new file mode 100644 index 00000000000000..40a198521b9035 --- /dev/null +++ b/packages/kbn-securitysolution-list-utils/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-utils/.babelrc.browser b/packages/kbn-securitysolution-list-utils/.babelrc.browser new file mode 100644 index 00000000000000..71bbfbcd6eb2f8 --- /dev/null +++ b/packages/kbn-securitysolution-list-utils/.babelrc.browser @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/webpack_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-list-utils/BUILD.bazel b/packages/kbn-securitysolution-list-utils/BUILD.bazel index b35b13004b1a86..4701723286eff0 100644 --- a/packages/kbn-securitysolution-list-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-list-utils/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-securitysolution-list-utils" @@ -27,23 +28,38 @@ NPM_MODULE_EXTRA_FILES = [ "README.md", ] -SRC_DEPS = [ +RUNTIME_DEPS = [ + "//packages/kbn-es-query", "//packages/kbn-i18n", - "//packages/kbn-securitysolution-list-constants", "//packages/kbn-securitysolution-io-ts-list-types", + "//packages/kbn-securitysolution-list-constants", "//packages/kbn-securitysolution-utils", - "//packages/kbn-es-query", "@npm//lodash", - "@npm//tslib", ] TYPES_DEPS = [ + "//packages/kbn-es-query", + "//packages/kbn-i18n", + "//packages/kbn-securitysolution-io-ts-list-types", + "//packages/kbn-securitysolution-list-constants", + "//packages/kbn-securitysolution-utils", "@npm//@types/jest", "@npm//@types/lodash", "@npm//@types/node", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + config_file = ".babelrc.browser" +) ts_config( name = "tsconfig", @@ -55,24 +71,26 @@ ts_config( ) ts_project( - name = "tsc", - srcs = SRCS, + name = "tsc_types", args = ["--pretty"], + srcs = SRCS, + deps = TYPES_DEPS, declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", root_dir = "src", source_map = True, tsconfig = ":tsconfig", - deps = DEPS, ) js_library( name = PKG_BASE_NAME, - package_name = PKG_REQUIRE_NAME, srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], - deps = DEPS + [":tsc"], + ) pkg_npm( diff --git a/packages/kbn-securitysolution-list-utils/package.json b/packages/kbn-securitysolution-list-utils/package.json index efd1401ab43328..0a6671e8377438 100644 --- a/packages/kbn-securitysolution-list-utils/package.json +++ b/packages/kbn-securitysolution-list-utils/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "description": "security solution list utilities", "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target/index.js", - "types": "./target/index.d.ts", + "browser": "./target_web/index.js", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", "private": true } diff --git a/packages/kbn-securitysolution-list-utils/tsconfig.json b/packages/kbn-securitysolution-list-utils/tsconfig.json index da48f4af29eadc..fa50bd7981214f 100644 --- a/packages/kbn-securitysolution-list-utils/tsconfig.json +++ b/packages/kbn-securitysolution-list-utils/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "outDir": "target", + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-securitysolution-list-utils/src", diff --git a/packages/kbn-storybook/src/webpack.config.ts b/packages/kbn-storybook/src/webpack.config.ts index 97fbf40468429d..e3cfc149bbea24 100644 --- a/packages/kbn-storybook/src/webpack.config.ts +++ b/packages/kbn-storybook/src/webpack.config.ts @@ -55,12 +55,13 @@ export default function ({ config: storybookConfig }: { config: Configuration }) { loader: 'sass-loader', options: { - prependData(loaderContext: any) { + additionalData(content: string, loaderContext: any) { return `@import ${stringifyRequest( loaderContext, resolve(REPO_ROOT, 'src/core/public/core_app/styles/_globals_v7light.scss') - )};\n`; + )};\n${content}`; }, + implementation: require('node-sass'), sassOptions: { includePaths: [resolve(REPO_ROOT, 'node_modules')], }, diff --git a/scripts/update_vscode_config.js b/scripts/update_vscode_config.js new file mode 100644 index 00000000000000..10ed9fa200b7b9 --- /dev/null +++ b/scripts/update_vscode_config.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +require('../src/setup_node_env'); +require('@kbn/dev-utils').runUpdateVscodeConfigCli(); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 88075b66ad045b..4b1aaf9eb19c11 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -276,6 +276,7 @@ export class DocLinksService { alerting: { guide: `${KIBANA_DOCS}create-and-manage-rules.html`, actionTypes: `${KIBANA_DOCS}action-types.html`, + apmRules: `${KIBANA_DOCS}apm-alerts.html`, emailAction: `${KIBANA_DOCS}email-action-type.html`, emailActionConfig: `${KIBANA_DOCS}email-action-type.html`, generalSettings: `${KIBANA_DOCS}alert-action-settings-kb.html#general-alert-action-settings`, diff --git a/src/core/server/logging/integration_tests/rolling_file_appender.test.ts b/src/core/server/logging/integration_tests/rolling_file_appender.test.ts index b560748026ace1..83533e29ad12e6 100644 --- a/src/core/server/logging/integration_tests/rolling_file_appender.test.ts +++ b/src/core/server/logging/integration_tests/rolling_file_appender.test.ts @@ -60,7 +60,8 @@ describe('RollingFileAppender', () => { const message = (index: number) => `some message of around 40 bytes number ${index}`; const expectedFileContent = (indices: number[]) => indices.map(message).join('\n') + '\n'; - describe('`size-limit` policy with `numeric` strategy', () => { + // FLAKY: https://github.com/elastic/kibana/issues/108633 + describe.skip('`size-limit` policy with `numeric` strategy', () => { it('rolls the log file in the correct order', async () => { root = createRoot({ type: 'rolling-file', diff --git a/src/core/server/saved_objects/service/lib/integration_tests/repository_with_proxy.test.ts b/src/core/server/saved_objects/service/lib/integration_tests/repository_with_proxy.test.ts new file mode 100644 index 00000000000000..8428e7be91ae87 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/integration_tests/repository_with_proxy.test.ts @@ -0,0 +1,463 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Hapi from '@hapi/hapi'; +import h2o2 from '@hapi/h2o2'; +import { URL } from 'url'; +import { ISavedObjectsRepository } from '../repository'; +import { SavedObject } from '../../../types'; +import { InternalCoreSetup, InternalCoreStart } from '../../../../internal_types'; +import { Root } from '../../../../root'; +import * as kbnTestServer from '../../../../../test_helpers/kbn_server'; +import { + declareGetRoute, + declareDeleteRoute, + declarePostBulkRoute, + declarePostMgetRoute, + declareGetSearchRoute, + declarePostSearchRoute, + declarePostUpdateRoute, + declarePostPitRoute, + declarePostUpdateByQueryRoute, + declarePassthroughRoute, + setProxyInterrupt, +} from './repository_with_proxy_utils'; + +let esServer: kbnTestServer.TestElasticsearchUtils; +let hapiServer: Hapi.Server; + +const registerSOTypes = (setup: InternalCoreSetup) => { + setup.savedObjects.registerType({ + name: 'my_type', + hidden: false, + mappings: { + dynamic: false, + properties: { + title: { type: 'text' }, + }, + }, + namespaceType: 'single', + }); + setup.savedObjects.registerType({ + name: 'my_other_type', + hidden: false, + mappings: { + dynamic: false, + properties: { + title: { type: 'text' }, + }, + }, + namespaceType: 'single', + }); +}; + +describe('404s from proxies', () => { + let root: Root; + let start: InternalCoreStart; + + beforeAll(async () => { + setProxyInterrupt(null); + + const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + }); + esServer = await startES(); + + const { hostname: esHostname, port: esPort } = new URL(esServer.hosts[0]); + + // inspired by https://github.com/elastic/kibana/pull/88919 + const proxyPort = process.env.TEST_PROXY_SERVER_PORT + ? parseInt(process.env.TEST_PROXY_SERVER_PORT, 10) + : 5698; + + // Setup custom hapi hapiServer with h2o2 plugin for proxying + hapiServer = Hapi.server({ + port: proxyPort, + }); + + await hapiServer.register(h2o2); + // register specific routes to modify the response and a catch-all to relay the request/response as-is + + declareGetRoute(hapiServer, esHostname, esPort); + declareDeleteRoute(hapiServer, esHostname, esPort); + declarePostUpdateRoute(hapiServer, esHostname, esPort); + + declareGetSearchRoute(hapiServer, esHostname, esPort); + declarePostSearchRoute(hapiServer, esHostname, esPort); + declarePostBulkRoute(hapiServer, esHostname, esPort); + declarePostMgetRoute(hapiServer, esHostname, esPort); + declarePostPitRoute(hapiServer, esHostname, esPort); + declarePostUpdateByQueryRoute(hapiServer, esHostname, esPort); + + declarePassthroughRoute(hapiServer, esHostname, esPort); + + await hapiServer.start(); + + // Setup kibana configured to use proxy as ES backend + root = kbnTestServer.createRootWithCorePlugins({ + elasticsearch: { + hosts: [`http://${esHostname}:${proxyPort}`], + }, + migrations: { + skip: false, + }, + }); + await root.preboot(); + const setup = await root.setup(); + registerSOTypes(setup); + + start = await root.start(); + }); + + afterAll(async () => { + await root.shutdown(); + await hapiServer.stop({ timeout: 1000 }); + await esServer.stop(); + }); + + describe('requests when a proxy relays request/responses with the correct product header', () => { + let repository: ISavedObjectsRepository; + let myOtherType: SavedObject; + const myOtherTypeDocs: SavedObject[] = []; + + beforeAll(async () => { + repository = start.savedObjects.createInternalRepository(); + + myOtherType = await repository.create( + 'my_other_type', + { title: 'my_other_type1' }, + { overwrite: false, references: [] } + ); + for (let i = 1; i < 11; i++) { + myOtherTypeDocs.push({ + type: 'my_other_type', + id: `myOtherTypeId${i}`, + attributes: { title: `MyOtherTypeTitle${i}` }, + references: [], + }); + } + await repository.bulkCreate(myOtherTypeDocs, { + overwrite: true, + namespace: 'default', + }); + }); + + beforeEach(() => { + setProxyInterrupt(null); + }); + + it('does not alter a Not Found response if the document does not exist and the proxy returns the correct product header', async () => { + let customErr: any; + try { + await repository.get('my_other_type', '123'); + } catch (err) { + customErr = err; + } + expect(customErr?.output?.statusCode).toBe(404); + expect(customErr?.output?.payload?.message).toBe( + 'Saved object [my_other_type/123] not found' + ); + }); + + it('returns a document if it exists and if the proxy passes through the product header', async () => { + const myOtherTypeDoc = await repository.get('my_other_type', `${myOtherType.id}`); + expect(myOtherTypeDoc.type).toBe('my_other_type'); + }); + + it('handles `update` requests that are successful', async () => { + const docToUpdate = await repository.create( + 'my_other_type', + { title: 'original title' }, + { overwrite: false, references: [] } + ); + + const updatedDoc = await repository.update('my_other_type', `${docToUpdate.id}`, { + title: 'updated title', + }); + expect(updatedDoc.type).toBe('my_other_type'); + expect(updatedDoc.attributes.title).toBe('updated title'); + }); + + it('handles `bulkCreate` requests when the proxy relays request/responses correctly', async () => { + const bulkObjects = [ + { + type: 'my_other_type', + id: 'my_other_type1', + attributes: { + title: 'bulkType1', + }, + references: [], + }, + { + type: 'my_other_type', + id: 'my_other_type2', + attributes: { + title: 'bulkType2', + }, + references: [], + }, + ]; + const bulkResponse = await repository.bulkCreate(bulkObjects, { + namespace: 'default', + overwrite: true, + }); + expect(bulkResponse.saved_objects.length).toEqual(2); + }); + + it('returns matches from `find` when the proxy passes through the response and product header', async () => { + const type = 'my_other_type'; + const result = await repository.find({ type }); + expect(result.saved_objects.length).toBeGreaterThan(0); + }); + + it('handles `delete` requests that are successful', async () => { + let deleteErr: any; + const docToDelete = await repository.create( + 'my_other_type', + { title: 'delete me please' }, + { id: 'docToDelete1', overwrite: true, references: [] } + ); + const deleteResult = await repository.delete('my_other_type', 'docToDelete1', { + namespace: 'default', + }); + expect(deleteResult).toStrictEqual({}); + try { + await repository.get('my_other_type', 'docToDelete1'); + } catch (err) { + deleteErr = err; + } + expect(deleteErr?.output?.statusCode).toBe(404); + expect(deleteErr?.output?.payload?.message).toBe( + `Saved object [my_other_type/${docToDelete.id}] not found` + ); + }); + + it('handles `bulkGet` requests that are successful when the proxy passes through the product header', async () => { + const docsToGet = myOtherTypeDocs; + const docsFound = await repository.bulkGet( + docsToGet.map((doc) => ({ id: doc.id, type: 'my_other_type' })) + ); + expect(docsFound.saved_objects.length).toBeGreaterThan(0); + }); + + it('handles `resolve` requests that are successful with an exact match', async () => { + const resolvedExactMatch = await repository.resolve('my_other_type', `${myOtherType.id}`); + expect(resolvedExactMatch.outcome).toBe('exactMatch'); + }); + + it('handles `openPointInTime` requests when the proxy passes through the product header', async () => { + const openPitResult = await repository.openPointInTimeForType('my_other_type'); + expect(Object.keys(openPitResult)).toContain('id'); + }); + + it('handles `checkConflicts` requests that are successful when the proxy passes through the product header', async () => { + const checkConflictsResult = await repository.checkConflicts( + [ + { id: myOtherTypeDocs[0].id, type: myOtherTypeDocs[0].type }, + { id: 'myOtherType456', type: 'my_other_type' }, + ], + { namespace: 'default' } + ); + expect(checkConflictsResult.errors.length).toEqual(1); + expect(checkConflictsResult.errors[0].error.error).toStrictEqual('Conflict'); + }); + + // this test must come last, it deletes all saved objects in the default space + it('handles `deleteByNamespace` requests when the proxy passes through the product header', async () => { + const deleteByNamespaceResult = await repository.deleteByNamespace('default'); + expect(Object.keys(deleteByNamespaceResult)).toEqual( + expect.arrayContaining(['total', 'updated', 'deleted']) + ); + }); + }); + + describe('requests when a proxy returns Not Found with an incorrect product header', () => { + let repository: ISavedObjectsRepository; + const myTypeDocs: SavedObject[] = []; + + const genericNotFoundEsUnavailableError = (err: any, type?: string, id?: string) => { + expect(err?.output?.statusCode).toBe(503); + if (type && id) { + expect(err?.output?.payload?.message).toBe( + `x-elastic-product not present or not recognized: Saved object [${type}/${id}] not found` + ); + } else { + expect(err?.output?.payload?.message).toBe( + `x-elastic-product not present or not recognized: Not Found` + ); + } + }; + + beforeAll(async () => { + setProxyInterrupt(null); // allow saved object creation + repository = start.savedObjects.createInternalRepository(); + + for (let i = 1; i < 11; i++) { + myTypeDocs.push({ + type: 'my_type', + id: `myTypeId${i}`, + attributes: { title: `MyTypeTitle${i}` }, + references: [], + }); + } + await repository.bulkCreate( + [ + ...myTypeDocs, + { + type: 'my_type', + id: 'myTypeToUpdate', + attributes: { title: 'myTypeToUpdateTitle' }, + references: [], + }, + ], + { + overwrite: true, + namespace: 'default', + } + ); + }); + beforeEach(() => { + setProxyInterrupt(null); // switch to non-proxied handler + }); + + it('returns an EsUnavailable error if the document exists but the proxy cannot find the es node (mimics allocator changes)', async () => { + let myError; + try { + await repository.get('my_type', 'myTypeId1'); + } catch (err) { + myError = err; + } + expect(genericNotFoundEsUnavailableError(myError, 'my_type', 'myTypeId1')); + }); + + it('returns an EsUnavailable error on `update` requests that are interrupted', async () => { + let updateError; + try { + await repository.update('my_type', 'myTypeToUpdate', { + title: 'updated title', + }); + expect(false).toBe(true); // Should not get here (we expect the call to throw) + } catch (err) { + updateError = err; + } + expect(genericNotFoundEsUnavailableError(updateError)); + }); + + it('returns an EsUnavailable error on `bulkCreate` requests with a 404 proxy response and wrong product header', async () => { + setProxyInterrupt('bulkCreate'); + let bulkCreateError: any; + const bulkObjects = [ + { + type: 'my_type', + id: '1', + attributes: { + title: 'bulkType1', + }, + references: [], + }, + { + type: 'my_type', + id: '2', + attributes: { + title: 'bulkType2', + }, + references: [], + }, + ]; + try { + await repository.bulkCreate(bulkObjects, { namespace: 'default', overwrite: true }); + } catch (err) { + bulkCreateError = err; + } + expect(genericNotFoundEsUnavailableError(bulkCreateError)); + }); + + it('returns an EsUnavailable error on `find` requests with a 404 proxy response and wrong product header', async () => { + setProxyInterrupt('find'); + let findErr: any; + try { + await repository.find({ type: 'my_type' }); + } catch (err) { + findErr = err; + } + expect(genericNotFoundEsUnavailableError(findErr)); + expect(findErr?.output?.payload?.error).toBe('Service Unavailable'); + }); + + it('returns an EsUnavailable error on `delete` requests with a 404 proxy response and wrong product header', async () => { + let deleteErr: any; + try { + await repository.delete('my_type', 'myTypeId1', { namespace: 'default' }); + } catch (err) { + deleteErr = err; + } + expect(genericNotFoundEsUnavailableError(deleteErr, 'my_type', 'myTypeId1')); + }); + + it('returns an EsUnavailable error on `resolve` requests with a 404 proxy response and wrong product header for an exact match', async () => { + let testResolveErr: any; + try { + await repository.resolve('my_type', 'myTypeId1'); + } catch (err) { + testResolveErr = err; + } + expect(genericNotFoundEsUnavailableError(testResolveErr, 'my_type', 'myTypeId1')); + }); + + it('returns an EsUnavailable error on `bulkGet` requests with a 404 proxy response and wrong product header', async () => { + const docsToGet = myTypeDocs; + let bulkGetError: any; + setProxyInterrupt('bulkGetMyType'); + try { + await repository.bulkGet(docsToGet.map((doc) => ({ id: doc.id, type: 'my_type' }))); + } catch (err) { + bulkGetError = err; + } + expect(genericNotFoundEsUnavailableError(bulkGetError)); + }); + + it('returns an EsUnavailable error on `openPointInTimeForType` requests with a 404 proxy response and wrong product header', async () => { + setProxyInterrupt('openPit'); + let openPitErr: any; + try { + await repository.openPointInTimeForType('my_other_type'); + } catch (err) { + openPitErr = err; + } + expect(genericNotFoundEsUnavailableError(openPitErr)); + }); + + it('returns an EsUnavailable error on `checkConflicts` requests with a 404 proxy response and wrong product header', async () => { + setProxyInterrupt('checkConficts'); + let checkConflictsErr: any; + try { + await repository.checkConflicts( + [ + { id: myTypeDocs[0].id, type: myTypeDocs[0].type }, + { id: 'myType456', type: 'my_type' }, + ], + { namespace: 'default' } + ); + } catch (err) { + checkConflictsErr = err; + } + expect(genericNotFoundEsUnavailableError(checkConflictsErr)); + }); + + it('returns an EsUnavailable error on `deleteByNamespace` requests with a 404 proxy response and wrong product header', async () => { + setProxyInterrupt('deleteByNamespace'); + let deleteByNamespaceErr: any; + try { + await repository.deleteByNamespace('default'); + } catch (err) { + deleteByNamespaceErr = err; + } + expect(genericNotFoundEsUnavailableError(deleteByNamespaceErr)); + }); + }); +}); diff --git a/src/core/server/saved_objects/service/lib/integration_tests/repository_with_proxy_utils.ts b/src/core/server/saved_objects/service/lib/integration_tests/repository_with_proxy_utils.ts new file mode 100644 index 00000000000000..cb0b2bd835bb9c --- /dev/null +++ b/src/core/server/saved_objects/service/lib/integration_tests/repository_with_proxy_utils.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import Hapi from '@hapi/hapi'; +import { IncomingMessage } from 'http'; +import { kibanaPackageJson as pkg } from '@kbn/utils'; + +// proxy setup +const defaultProxyOptions = (hostname: string, port: string) => ({ + host: hostname, + port, + protocol: 'http' as 'http', + passThrough: true, +}); + +let proxyInterrupt: string | null | undefined = null; + +export const setProxyInterrupt = ( + testArg: + | 'bulkCreate' + | 'bulkGetMyType' + | 'checkConficts' + | 'find' + | 'openPit' + | 'deleteByNamespace' + | null +) => (proxyInterrupt = testArg); + +// passes the req/response directly as is +const relayHandler = (h: Hapi.ResponseToolkit, hostname: string, port: string) => { + return h.proxy(defaultProxyOptions(hostname, port)); +}; + +const proxyResponseHandler = (h: Hapi.ResponseToolkit, hostname: string, port: string) => { + return h.proxy({ + ...defaultProxyOptions(hostname, port), + // eslint-disable-next-line @typescript-eslint/no-shadow + onResponse: async (err, res, request, h, settings, ttl) => proxyOnResponseHandler(res, h), + }); +}; + +// mimics a 404 'unexpected' response from the proxy +const proxyOnResponseHandler = async (res: IncomingMessage, h: Hapi.ResponseToolkit) => { + return h + .response(res) + .header('x-elastic-product', 'somethingitshouldnotbe', { override: true }) + .code(404); +}; + +const kbnIndex = `.kibana_${pkg.version}`; + +// GET /.kibana_8.0.0/_doc/{type*} route (repository.get calls) +export const declareGetRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'GET', + path: `/${kbnIndex}/_doc/{type*}`, + options: { + handler: (req, h) => { + if (req.params.type === 'my_type:myTypeId1' || req.params.type === 'my_type:myType_123') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); +// DELETE /.kibana_8.0.0/_doc/{type*} route (repository.delete calls) +export const declareDeleteRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'DELETE', + path: `/${kbnIndex}/_doc/{_id*}`, + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (req.params._id === 'my_type:myTypeId1') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); + +// POST _bulk route +export const declarePostBulkRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'POST', + path: '/_bulk', + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (proxyInterrupt === 'bulkCreate') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); +// POST _mget route (repository.bulkGet calls) +export const declarePostMgetRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'POST', + path: '/_mget', + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (proxyInterrupt === 'bulkGetMyType' || proxyInterrupt === 'checkConficts') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); +// GET _search route +export const declareGetSearchRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'GET', + path: `/${kbnIndex}/_search`, + options: { + handler: (req, h) => { + const payload = req.payload; + if (!payload) { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); +// POST _search route (`find` calls) +export const declarePostSearchRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'POST', + path: `/${kbnIndex}/_search`, + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (proxyInterrupt === 'find') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); +// POST _update +export const declarePostUpdateRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'POST', + path: `/${kbnIndex}/_update/{_id*}`, + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (req.params._id === 'my_type:myTypeToUpdate') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); +// POST _pit +export const declarePostPitRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: 'POST', + path: `/${kbnIndex}/_pit`, + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (proxyInterrupt === 'openPit') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); +// POST _update_by_query +export const declarePostUpdateByQueryRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string +) => + hapiServer.route({ + method: 'POST', + path: `/${kbnIndex}/_update_by_query`, + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + if (proxyInterrupt === 'deleteByNamespace') { + return proxyResponseHandler(h, hostname, port); + } else { + return relayHandler(h, hostname, port); + } + }, + }, + }); + +// catch-all passthrough route +export const declarePassthroughRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => + hapiServer.route({ + method: '*', + path: '/{any*}', + options: { + payload: { + output: 'data', + parse: false, + }, + handler: (req, h) => { + return relayHandler(h, hostname, port); + }, + }, + }); diff --git a/src/core/server/saved_objects/service/lib/repository_es_client.ts b/src/core/server/saved_objects/service/lib/repository_es_client.ts index 6a601b1ed0c839..4e8592fa94ccae 100644 --- a/src/core/server/saved_objects/service/lib/repository_es_client.ts +++ b/src/core/server/saved_objects/service/lib/repository_es_client.ts @@ -39,6 +39,7 @@ export function createRepositoryEsClient(client: ElasticsearchClient): Repositor (client[key] as Function)(params, { maxRetries: 0, ...options }) ); } catch (e) { + // retry failures are caught here, as are 404's that aren't ignored (e.g update calls) throw decorateEsError(e); } }, diff --git a/src/dev/build/tasks/patch_native_modules_task.ts b/src/dev/build/tasks/patch_native_modules_task.ts index 7fb4ffed6fb97f..be2571c029f9da 100644 --- a/src/dev/build/tasks/patch_native_modules_task.ts +++ b/src/dev/build/tasks/patch_native_modules_task.ts @@ -31,17 +31,17 @@ interface Package { const packages: Package[] = [ { name: 're2', - version: '1.15.4', + version: '1.16.0', destinationPath: 'node_modules/re2/build/Release/re2.node', extractMethod: 'gunzip', archives: { 'darwin-x64': { - url: 'https://github.com/uhop/node-re2/releases/download/1.15.4/darwin-x64-83.gz', - sha256: 'b45cd8296fd6eb2a091399c20111af43093ba30c99ed9e5d969278f5ff69ba8f', + url: 'https://github.com/uhop/node-re2/releases/download/1.16.0/darwin-x64-83.gz', + sha256: 'ef49febcba972b488727ce329ea9d2b57590bb44001ed494f2aa1397c0ebc32b', }, 'linux-x64': { - url: 'https://github.com/uhop/node-re2/releases/download/1.15.4/linux-x64-83.gz', - sha256: '1bbc3f90f0ba105772b37c04e3a718f69544b4df01dda00435c2b8e50b2ad0d9', + url: 'https://github.com/uhop/node-re2/releases/download/1.16.0/linux-x64-83.gz', + sha256: '160217dd83eb7093b758e905ce09cb45182864c7df858bf2525a68924a23c509', }, // ARM build is currently done manually as Github Actions used in upstream project @@ -50,17 +50,18 @@ const packages: Package[] = [ // From a AWS Graviton instance: // * checkout the node-re2 project, // * install Node using the same minor used by Kibana + // * git submodule update --init --recursive to download re2 // * npm install, which will also create a build // * gzip -c build/Release/re2.node > linux-arm64-83.gz // * upload to kibana-ci-proxy-cache bucket 'linux-arm64': { url: - 'https://storage.googleapis.com/kibana-ci-proxy-cache/node-re2/uhop/node-re2/releases/download/1.15.4/linux-arm64-83.gz', - sha256: '4eb524ca9a79dea9c07342e487fbe91591166fdbc022ae987104840df948a4e9', + 'https://storage.googleapis.com/kibana-ci-proxy-cache/node-re2/uhop/node-re2/releases/download/1.16.0/linux-arm64-83.gz', + sha256: '114505c60dbf57ad30556937ac5f49213c6676ad79d92706b96949d3a63f53b4', }, 'win32-x64': { - url: 'https://github.com/uhop/node-re2/releases/download/1.15.4/win32-x64-83.gz', - sha256: 'efe939d3cda1d64ee3ee3e60a20613b95166d55632e702c670763ea7e69fca06', + url: 'https://github.com/uhop/node-re2/releases/download/1.16.0/win32-x64-83.gz', + sha256: '92ad420a6bfcedeb58dadf807a2f2901b05251d1edd3950051699929eda23073', }, }, }, diff --git a/src/plugins/console/kibana.json b/src/plugins/console/kibana.json index 9452f43647a194..69c7176ff6a473 100644 --- a/src/plugins/console/kibana.json +++ b/src/plugins/console/kibana.json @@ -7,7 +7,7 @@ "name": "Stack Management", "githubTeam": "kibana-stack-management" }, - "requiredPlugins": ["devTools"], + "requiredPlugins": ["devTools", "share"], "optionalPlugins": ["usageCollection", "home"], "requiredBundles": ["esUiShared", "kibanaReact", "kibanaUtils", "home"] } diff --git a/src/plugins/console/public/index.ts b/src/plugins/console/public/index.ts index 767673f45e02d9..8c4a107108565d 100644 --- a/src/plugins/console/public/index.ts +++ b/src/plugins/console/public/index.ts @@ -10,6 +10,8 @@ import './index.scss'; import { ConsoleUIPlugin } from './plugin'; +export type { ConsoleUILocatorParams } from './plugin'; + export { ConsoleUIPlugin as Plugin }; export function plugin() { diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts index d791e4e2231ee9..e3791df6a2db69 100644 --- a/src/plugins/console/public/plugin.ts +++ b/src/plugins/console/public/plugin.ts @@ -7,15 +7,20 @@ */ import { i18n } from '@kbn/i18n'; +import { SerializableRecord } from '@kbn/utility-types'; import { Plugin, CoreSetup } from 'src/core/public'; import { FeatureCatalogueCategory } from '../../home/public'; import { AppSetupUIPluginDependencies } from './types'; +export interface ConsoleUILocatorParams extends SerializableRecord { + loadFrom?: string; +} + export class ConsoleUIPlugin implements Plugin { public setup( { notifications, getStartServices, http }: CoreSetup, - { devTools, home, usageCollection }: AppSetupUIPluginDependencies + { devTools, home, share, usageCollection }: AppSetupUIPluginDependencies ) { if (home) { home.featureCatalogue.register({ @@ -60,6 +65,19 @@ export class ConsoleUIPlugin implements Plugin({ + id: 'CONSOLE_APP_LOCATOR', + getLocation: async ({ loadFrom }) => { + return { + app: 'dev_tools', + path: `#/console${loadFrom ? `?load_from=${loadFrom}` : ''}`, + state: { loadFrom }, + }; + }, + }); + + return { locator }; } public start() {} diff --git a/src/plugins/console/public/types/plugin_dependencies.ts b/src/plugins/console/public/types/plugin_dependencies.ts index b4508a83b09b5c..444776f47ea13f 100644 --- a/src/plugins/console/public/types/plugin_dependencies.ts +++ b/src/plugins/console/public/types/plugin_dependencies.ts @@ -9,9 +9,11 @@ import { HomePublicPluginSetup } from '../../../home/public'; import { DevToolsSetup } from '../../../dev_tools/public'; import { UsageCollectionSetup } from '../../../usage_collection/public'; +import { SharePluginSetup } from '../../../share/public'; export interface AppSetupUIPluginDependencies { home?: HomePublicPluginSetup; devTools: DevToolsSetup; + share: SharePluginSetup; usageCollection?: UsageCollectionSetup; } diff --git a/src/plugins/console/tsconfig.json b/src/plugins/console/tsconfig.json index ee6fbfebc77a26..1597ce812edc54 100644 --- a/src/plugins/console/tsconfig.json +++ b/src/plugins/console/tsconfig.json @@ -14,6 +14,7 @@ { "path": "../home/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, - { "path": "../usage_collection/tsconfig.json" }, + { "path": "../share/tsconfig.json" }, + { "path": "../usage_collection/tsconfig.json" } ] } diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index f08cc273a00af1..2c339d1408237d 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -10,7 +10,13 @@ export const DEFAULT_QUERY_LANGUAGE = 'kuery'; export const KIBANA_USER_QUERY_LANGUAGE_KEY = 'kibana.userQueryLanguage'; /** @public **/ -export const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; +export const DATA_VIEW_SAVED_OBJECT_TYPE = 'index-pattern'; + +/** + * @deprecated Use DATA_VIEW_SAVED_OBJECT_TYPE. All index pattern interfaces were renamed. + */ + +export const INDEX_PATTERN_SAVED_OBJECT_TYPE = DATA_VIEW_SAVED_OBJECT_TYPE; export type ValueSuggestionsMethod = 'terms_enum' | 'terms_agg'; diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index 2bc383db6f5304..a36788f949390f 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -22,4 +22,4 @@ export * from './exports'; * @removeBy 8.1 */ -export { IndexPatternAttributes } from './types'; +export { IndexPatternAttributes, DataViewAttributes } from './types'; diff --git a/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts b/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts index 6c059dc44a1967..d35b09e39aa76c 100644 --- a/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts +++ b/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export class DuplicateIndexPatternError extends Error { +export class DuplicateDataViewError extends Error { constructor(message: string) { super(message); this.name = 'DuplicateIndexPatternError'; diff --git a/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts b/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts index 1c50f0704910a8..8fe9e40e0ac6c7 100644 --- a/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts +++ b/src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts @@ -8,8 +8,8 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { IndexPatternsContract } from '../index_patterns'; -import { IndexPatternSpec } from '..'; +import { DataViewsContract } from '../index_patterns'; +import { DataViewSpec } from '..'; import { SavedObjectReference } from '../../../../../core/types'; const name = 'indexPatternLoad'; @@ -17,7 +17,7 @@ const type = 'index_pattern'; export interface IndexPatternExpressionType { type: typeof type; - value: IndexPatternSpec; + value: DataViewSpec; } type Input = null; @@ -29,7 +29,7 @@ interface Arguments { /** @internal */ export interface IndexPatternLoadStartDependencies { - indexPatterns: IndexPatternsContract; + indexPatterns: DataViewsContract; } export type IndexPatternLoadExpressionFunctionDefinition = ExpressionFunctionDefinition< diff --git a/src/plugins/data/common/index_patterns/fields/field_list.ts b/src/plugins/data/common/index_patterns/fields/field_list.ts index 40503e6dbc1b17..78cc390c8c13fe 100644 --- a/src/plugins/data/common/index_patterns/fields/field_list.ts +++ b/src/plugins/data/common/index_patterns/fields/field_list.ts @@ -8,24 +8,22 @@ import { findIndex } from 'lodash'; import { IFieldType } from './types'; -import { IndexPatternField } from './index_pattern_field'; -import { FieldSpec, IndexPatternFieldMap } from '../types'; -import { IndexPattern } from '../index_patterns'; +import { DataViewField } from './index_pattern_field'; +import { FieldSpec, DataViewFieldMap } from '../types'; +import { DataView } from '../index_patterns'; -type FieldMap = Map; +type FieldMap = Map; -export interface IIndexPatternFieldList extends Array { +export interface IIndexPatternFieldList extends Array { add(field: FieldSpec): void; - getAll(): IndexPatternField[]; - getByName(name: IndexPatternField['name']): IndexPatternField | undefined; - getByType(type: IndexPatternField['type']): IndexPatternField[]; + getAll(): DataViewField[]; + getByName(name: DataViewField['name']): DataViewField | undefined; + getByType(type: DataViewField['type']): DataViewField[]; remove(field: IFieldType): void; removeAll(): void; replaceAll(specs: FieldSpec[]): void; update(field: FieldSpec): void; - toSpec(options?: { - getFormatterForField?: IndexPattern['getFormatterForField']; - }): IndexPatternFieldMap; + toSpec(options?: { getFormatterForField?: DataView['getFormatterForField'] }): DataViewFieldMap; } // extending the array class and using a constructor doesn't work well @@ -35,11 +33,11 @@ export const fieldList = ( specs: FieldSpec[] = [], shortDotsEnable = false ): IIndexPatternFieldList => { - class FldList extends Array implements IIndexPatternFieldList { + class FldList extends Array implements IIndexPatternFieldList { private byName: FieldMap = new Map(); - private groups: Map = new Map(); - private setByName = (field: IndexPatternField) => this.byName.set(field.name, field); - private setByGroup = (field: IndexPatternField) => { + private groups: Map = new Map(); + private setByName = (field: DataViewField) => this.byName.set(field.name, field); + private setByGroup = (field: DataViewField) => { if (typeof this.groups.get(field.type) === 'undefined') { this.groups.set(field.type, new Map()); } @@ -53,12 +51,12 @@ export const fieldList = ( } public readonly getAll = () => [...this.byName.values()]; - public readonly getByName = (name: IndexPatternField['name']) => this.byName.get(name); - public readonly getByType = (type: IndexPatternField['type']) => [ + public readonly getByName = (name: DataViewField['name']) => this.byName.get(name); + public readonly getByType = (type: DataViewField['type']) => [ ...(this.groups.get(type) || new Map()).values(), ]; public readonly add = (field: FieldSpec) => { - const newField = new IndexPatternField({ ...field, shortDotsEnable }); + const newField = new DataViewField({ ...field, shortDotsEnable }); this.push(newField); this.setByName(newField); this.setByGroup(newField); @@ -73,7 +71,7 @@ export const fieldList = ( }; public readonly update = (field: FieldSpec) => { - const newField = new IndexPatternField(field); + const newField = new DataViewField(field); const index = this.findIndex((f) => f.name === newField.name); this.splice(index, 1, newField); this.setByName(newField); @@ -95,10 +93,10 @@ export const fieldList = ( public toSpec({ getFormatterForField, }: { - getFormatterForField?: IndexPattern['getFormatterForField']; + getFormatterForField?: DataView['getFormatterForField']; } = {}) { return { - ...this.reduce((collector, field) => { + ...this.reduce((collector, field) => { collector[field.name] = field.toSpec({ getFormatterForField }); return collector; }, {}), diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts index 0c7a668087da83..fae0e14b95c05f 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts @@ -6,15 +6,17 @@ * Side Public License, v 1. */ +/* eslint-disable max-classes-per-file */ + +import { KbnFieldType, getKbnFieldType, castEsToKbnFieldTypeName } from '@kbn/field-types'; import type { RuntimeField } from '../types'; -import { KbnFieldType, getKbnFieldType, castEsToKbnFieldTypeName } from '../../kbn_field_types'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import type { IFieldType } from './types'; -import { FieldSpec, IndexPattern } from '../..'; +import { FieldSpec, DataView } from '../..'; import { shortenDottedString } from '../../utils'; /** @public */ -export class IndexPatternField implements IFieldType { +export class DataViewField implements IFieldType { readonly spec: FieldSpec; // not writable or serialized private readonly kbnFieldType: KbnFieldType; @@ -182,7 +184,7 @@ export class IndexPatternField implements IFieldType { public toSpec({ getFormatterForField, }: { - getFormatterForField?: IndexPattern['getFormatterForField']; + getFormatterForField?: DataView['getFormatterForField']; } = {}): FieldSpec { return { count: this.count, @@ -205,3 +207,8 @@ export class IndexPatternField implements IFieldType { }; } } + +/** + * @deprecated Use DataViewField instead. All index pattern interfaces were renamed. + */ +export class IndexPatternField extends DataViewField {} diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts index 8c8413f6894bfe..2c5934a8e7b3f1 100644 --- a/src/plugins/data/common/index_patterns/fields/types.ts +++ b/src/plugins/data/common/index_patterns/fields/types.ts @@ -5,14 +5,14 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { IndexPatternFieldBase } from '@kbn/es-query'; -import { FieldSpec, IndexPattern } from '../..'; +import { DataViewFieldBase } from '@kbn/es-query'; +import { FieldSpec, DataView } from '../..'; /** * @deprecated Use {@link IndexPatternField} * @removeBy 8.1 */ -export interface IFieldType extends IndexPatternFieldBase { +export interface IFieldType extends DataViewFieldBase { count?: number; // esTypes might be undefined on old index patterns that have not been refreshed since we added // this prop. It is also undefined on scripted fields. @@ -26,5 +26,5 @@ export interface IFieldType extends IndexPatternFieldBase { displayName?: string; customLabel?: string; format?: any; - toSpec?: (options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }) => FieldSpec; + toSpec?: (options?: { getFormatterForField?: DataView['getFormatterForField'] }) => FieldSpec; } diff --git a/src/plugins/data/common/index_patterns/fields/utils.ts b/src/plugins/data/common/index_patterns/fields/utils.ts index 1ec59b0a2ce08b..9e05bebc746f0a 100644 --- a/src/plugins/data/common/index_patterns/fields/utils.ts +++ b/src/plugins/data/common/index_patterns/fields/utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getFilterableKbnTypeNames } from '../../kbn_field_types'; +import { getFilterableKbnTypeNames } from '@kbn/field-types'; import { IFieldType } from './types'; const filterableTypes = getFilterableKbnTypeNames(); diff --git a/src/plugins/data/common/index_patterns/index.ts b/src/plugins/data/common/index_patterns/index.ts index f493b417b47ef0..99639676bdabfc 100644 --- a/src/plugins/data/common/index_patterns/index.ts +++ b/src/plugins/data/common/index_patterns/index.ts @@ -9,7 +9,17 @@ export * from './constants'; export * from './fields'; export * from './types'; -export { IndexPatternsService, IndexPatternsContract } from './index_patterns'; -export type { IndexPattern, IndexPatternListItem } from './index_patterns'; +export { + IndexPatternsService, + IndexPatternsContract, + DataViewsService, + DataViewsContract, +} from './index_patterns'; +export type { + IndexPattern, + IndexPatternListItem, + DataView, + DataViewListItem, +} from './index_patterns'; export * from './errors'; export * from './expressions'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts b/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts index a58a349a469756..a647f306ca7f08 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts @@ -6,18 +6,18 @@ * Side Public License, v 1. */ -import { IndexPattern } from './index_pattern'; +import { DataView } from './index_pattern'; -export interface PatternCache { - get: (id: string) => Promise | undefined; - set: (id: string, value: Promise) => Promise; +export interface DataViewCache { + get: (id: string) => Promise | undefined; + set: (id: string, value: Promise) => Promise; clear: (id: string) => void; clearAll: () => void; } -export function createIndexPatternCache(): PatternCache { +export function createDataViewCache(): DataViewCache { const vals: Record = {}; - const cache: PatternCache = { + const cache: DataViewCache = { get: (id: string) => { return vals[id]; }, diff --git a/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts index 61ec1c5a4c090a..da20053bf1fe7e 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts @@ -7,12 +7,12 @@ */ import { includes } from 'lodash'; -import { IndexPatternsContract } from './index_patterns'; +import { DataViewsContract } from './index_patterns'; import { UiSettingsCommon } from '../types'; -export type EnsureDefaultIndexPattern = () => Promise | undefined; +export type EnsureDefaultDataView = () => Promise | undefined; -export const createEnsureDefaultIndexPattern = ( +export const createEnsureDefaultDataView = ( uiSettings: UiSettingsCommon, onRedirectNoIndexPattern: () => Promise | void ) => { @@ -20,7 +20,7 @@ export const createEnsureDefaultIndexPattern = ( * Checks whether a default index pattern is set and exists and defines * one otherwise. */ - return async function ensureDefaultIndexPattern(this: IndexPatternsContract) { + return async function ensureDefaultDataView(this: DataViewsContract) { const patterns = await this.getIds(); let defaultId = await uiSettings.get('defaultIndex'); let defined = !!defaultId; diff --git a/src/plugins/data/common/index_patterns/index_patterns/flatten_hit.ts b/src/plugins/data/common/index_patterns/index_patterns/flatten_hit.ts index 7cd88c8a87c196..bcdcca3a4daac6 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/flatten_hit.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/flatten_hit.ts @@ -7,12 +7,12 @@ */ import _ from 'lodash'; -import { IndexPattern } from './index_pattern'; +import { DataView } from './index_pattern'; // Takes a hit, merges it with any stored/scripted fields, and with the metaFields // returns a flattened version -function flattenHit(indexPattern: IndexPattern, hit: Record, deep: boolean) { +function flattenHit(indexPattern: DataView, hit: Record, deep: boolean) { const flat = {} as Record; // recursively merge _source @@ -104,11 +104,7 @@ function decorateFlattenedWrapper(hit: Record, metaFields: Record, deep = false) { const decorateFlattened = decorateFlattenedWrapper(hit, metaFields); const cached = cache.get(hit); diff --git a/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts index fe872ae9298999..9ae630e8de651f 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts @@ -7,7 +7,7 @@ */ import _ from 'lodash'; -import { IndexPattern } from './index_pattern'; +import { DataView } from './index_pattern'; import { FieldFormatsContentType } from '../../../../field_formats/common'; const formattedCache = new WeakMap(); @@ -15,7 +15,7 @@ const partialFormattedCache = new WeakMap(); // Takes a hit, merges it with any stored/scripted fields, and with the metaFields // returns a formatted version -export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any) { +export function formatHitProvider(indexPattern: DataView, defaultFormat: any) { function convert( hit: Record, val: any, 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 48bcdf6982b672..e08d1e62bae06a 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 @@ -6,22 +6,24 @@ * Side Public License, v 1. */ +/* eslint-disable max-classes-per-file */ + import _, { each, reject } from 'lodash'; -import { FieldAttrs, FieldAttrSet, IndexPatternAttributes } from '../..'; +import { castEsToKbnFieldTypeName } from '@kbn/field-types'; +import { FieldAttrs, FieldAttrSet, DataViewAttributes } from '../..'; import type { RuntimeField } from '../types'; import { DuplicateField } from '../../../../kibana_utils/common'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; -import { IndexPatternField, IIndexPatternFieldList, fieldList } from '../fields'; +import { DataViewField, IIndexPatternFieldList, fieldList } from '../fields'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { FieldFormatsStartCommon, FieldFormat } from '../../../../field_formats/common'; -import { IndexPatternSpec, TypeMeta, SourceFilter, IndexPatternFieldMap } from '../types'; +import { DataViewSpec, TypeMeta, SourceFilter, DataViewFieldMap } from '../types'; import { SerializedFieldFormat } from '../../../../expressions/common'; -import { castEsToKbnFieldTypeName } from '../../kbn_field_types'; -interface IndexPatternDeps { - spec?: IndexPatternSpec; +interface DataViewDeps { + spec?: DataViewSpec; fieldFormats: FieldFormatsStartCommon; shortDotsEnable?: boolean; metaFields?: string[]; @@ -41,7 +43,7 @@ interface SavedObjectBody { type FormatFieldFn = (hit: Record, fieldName: string) => any; -export class IndexPattern implements IIndexPattern { +export class DataView implements IIndexPattern { public id?: string; public title: string = ''; public fieldFormatMap: Record; @@ -49,7 +51,7 @@ export class IndexPattern implements IIndexPattern { * Only used by rollup indices, used by rollup specific endpoint to load field list */ public typeMeta?: TypeMeta; - public fields: IIndexPatternFieldList & { toSpec: () => IndexPatternFieldMap }; + public fields: IIndexPatternFieldList & { toSpec: () => DataViewFieldMap }; public timeFieldName: string | undefined; /** * @deprecated Used by time range index patterns @@ -84,12 +86,7 @@ export class IndexPattern implements IIndexPattern { */ public readonly allowNoIndex: boolean = false; - constructor({ - spec = {}, - fieldFormats, - shortDotsEnable = false, - metaFields = [], - }: IndexPatternDeps) { + constructor({ spec = {}, fieldFormats, shortDotsEnable = false, metaFields = [] }: DataViewDeps) { // set dependencies this.fieldFormats = fieldFormats; // set config @@ -206,7 +203,7 @@ export class IndexPattern implements IIndexPattern { /** * Create static representation of index pattern */ - public toSpec(): IndexPatternSpec { + public toSpec(): DataViewSpec { return { id: this.id, version: this.version, @@ -311,7 +308,7 @@ export class IndexPattern implements IIndexPattern { return this.fields.getByName(this.timeFieldName); } - getFieldByName(name: string): IndexPatternField | undefined { + getFieldByName(name: string): DataViewField | undefined { if (!this.fields || !this.fields.getByName) return undefined; return this.fields.getByName(name); } @@ -323,7 +320,7 @@ export class IndexPattern implements IIndexPattern { /** * Returns index pattern as saved object body for saving */ - getAsSavedObjectBody(): IndexPatternAttributes { + getAsSavedObjectBody(): DataViewAttributes { const fieldFormatMap = _.isEmpty(this.fieldFormatMap) ? undefined : JSON.stringify(this.fieldFormatMap); @@ -349,9 +346,7 @@ export class IndexPattern implements IIndexPattern { * Provide a field, get its formatter * @param field */ - getFormatterForField( - field: IndexPatternField | IndexPatternField['spec'] | IFieldType - ): FieldFormat { + getFormatterForField(field: DataViewField | DataViewField['spec'] | IFieldType): FieldFormat { const fieldFormat = this.getFormatterForFieldNoDefault(field.name); if (fieldFormat) { return fieldFormat; @@ -490,3 +485,8 @@ export class IndexPattern implements IIndexPattern { delete this.fieldFormatMap[fieldName]; }; } + +/** + * @deprecated Use DataView instead. All index pattern interfaces were renamed. + */ +export class IndexPattern extends DataView {} diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index d255abc52aac60..5f389d36e3bb67 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -7,7 +7,7 @@ */ import { defaults } from 'lodash'; -import { IndexPatternsService, IndexPattern } from '.'; +import { DataViewsService, DataView } from '.'; import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; import { UiSettingsCommon, SavedObjectsClientCommon, SavedObject } from '../types'; @@ -47,7 +47,7 @@ const savedObject = { }; describe('IndexPatterns', () => { - let indexPatterns: IndexPatternsService; + let indexPatterns: DataViewsService; let savedObjectsClient: SavedObjectsClientCommon; let SOClientGetDelay = 0; @@ -85,7 +85,7 @@ describe('IndexPatterns', () => { }; }); - indexPatterns = new IndexPatternsService({ + indexPatterns = new DataViewsService({ uiSettings: ({ get: () => Promise.resolve(false), getAll: () => {}, @@ -207,7 +207,7 @@ describe('IndexPatterns', () => { indexPatterns.refreshFields = jest.fn(); const indexPattern = await indexPatterns.create({ title }, true); - expect(indexPattern).toBeInstanceOf(IndexPattern); + expect(indexPattern).toBeInstanceOf(DataView); expect(indexPattern.title).toBe(title); expect(indexPatterns.refreshFields).not.toBeCalled(); @@ -235,7 +235,7 @@ describe('IndexPatterns', () => { indexPatterns.createSavedObject = jest.fn(() => Promise.resolve(({ id: 'id', - } as unknown) as IndexPattern) + } as unknown) as DataView) ); indexPatterns.setDefault = jest.fn(); await indexPatterns.createAndSave({ title }); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 74f11badbb4115..a72224d1c3fe81 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -6,78 +6,80 @@ * Side Public License, v 1. */ +/* eslint-disable max-classes-per-file */ + import { i18n } from '@kbn/i18n'; import { PublicMethodsOf } from '@kbn/utility-types'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE, SavedObjectsClientCommon } from '../..'; +import { castEsToKbnFieldTypeName } from '@kbn/field-types'; +import { DATA_VIEW_SAVED_OBJECT_TYPE, SavedObjectsClientCommon } from '../..'; -import { createIndexPatternCache } from '.'; +import { createDataViewCache } from '.'; import type { RuntimeField } from '../types'; -import { IndexPattern } from './index_pattern'; -import { - createEnsureDefaultIndexPattern, - EnsureDefaultIndexPattern, -} from './ensure_default_index_pattern'; +import { DataView } from './index_pattern'; +import { createEnsureDefaultDataView, EnsureDefaultDataView } from './ensure_default_index_pattern'; import { OnNotification, OnError, UiSettingsCommon, - IIndexPatternsApiClient, + IDataViewsApiClient, GetFieldsOptions, - IndexPatternSpec, - IndexPatternAttributes, + DataViewSpec, + DataViewAttributes, FieldAttrs, FieldSpec, - IndexPatternFieldMap, + DataViewFieldMap, TypeMeta, } from '../types'; import { FieldFormatsStartCommon, FORMATS_UI_SETTINGS } from '../../../../field_formats/common/'; import { UI_SETTINGS, SavedObject } from '../../../common'; import { SavedObjectNotFound } from '../../../../kibana_utils/common'; -import { IndexPatternMissingIndices } from '../lib'; +import { DataViewMissingIndices } from '../lib'; import { findByTitle } from '../utils'; -import { DuplicateIndexPatternError } from '../errors'; -import { castEsToKbnFieldTypeName } from '../../kbn_field_types'; +import { DuplicateDataViewError } from '../errors'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; -export type IndexPatternSavedObjectAttrs = Pick< - IndexPatternAttributes, - 'title' | 'type' | 'typeMeta' ->; +export type IndexPatternSavedObjectAttrs = Pick; export type IndexPatternListSavedObjectAttrs = Pick< - IndexPatternAttributes, + DataViewAttributes, 'title' | 'type' | 'typeMeta' >; -export interface IndexPatternListItem { +export interface DataViewListItem { id: string; title: string; type?: string; typeMeta?: TypeMeta; } +/** + * @deprecated Use DataViewListItem. All index pattern interfaces were renamed. + */ + +export type IndexPatternListItem = DataViewListItem; + interface IndexPatternsServiceDeps { uiSettings: UiSettingsCommon; savedObjectsClient: SavedObjectsClientCommon; - apiClient: IIndexPatternsApiClient; + apiClient: IDataViewsApiClient; fieldFormats: FieldFormatsStartCommon; onNotification: OnNotification; onError: OnError; onRedirectNoIndexPattern?: () => void; } -export class IndexPatternsService { +export class DataViewsService { private config: UiSettingsCommon; private savedObjectsClient: SavedObjectsClientCommon; private savedObjectsCache?: Array> | null; - private apiClient: IIndexPatternsApiClient; + private apiClient: IDataViewsApiClient; private fieldFormats: FieldFormatsStartCommon; private onNotification: OnNotification; private onError: OnError; - private indexPatternCache: ReturnType; + private indexPatternCache: ReturnType; - ensureDefaultIndexPattern: EnsureDefaultIndexPattern; + ensureDefaultIndexPattern: EnsureDefaultDataView; constructor({ uiSettings, @@ -94,12 +96,12 @@ export class IndexPatternsService { this.fieldFormats = fieldFormats; this.onNotification = onNotification; this.onError = onError; - this.ensureDefaultIndexPattern = createEnsureDefaultIndexPattern( + this.ensureDefaultIndexPattern = createEnsureDefaultDataView( uiSettings, onRedirectNoIndexPattern ); - this.indexPatternCache = createIndexPatternCache(); + this.indexPatternCache = createDataViewCache(); } /** @@ -107,7 +109,7 @@ export class IndexPatternsService { */ private async refreshSavedObjectsCache() { const so = await this.savedObjectsClient.find({ - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, fields: ['title', 'type', 'typeMeta'], perPage: 10000, }); @@ -148,9 +150,9 @@ export class IndexPatternsService { * @param size * @returns IndexPattern[] */ - find = async (search: string, size: number = 10): Promise => { + find = async (search: string, size: number = 10): Promise => { const savedObjects = await this.savedObjectsClient.find({ - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, fields: ['title'], search, searchFields: ['title'], @@ -261,7 +263,7 @@ export class IndexPatternsService { * @returns FieldSpec[] */ getFieldsForIndexPattern = async ( - indexPattern: IndexPattern | IndexPatternSpec, + indexPattern: DataView | DataViewSpec, options?: GetFieldsOptions ) => this.getFieldsForWildcard({ @@ -275,7 +277,7 @@ export class IndexPatternsService { * Refresh field list for a given index pattern * @param indexPattern */ - refreshFields = async (indexPattern: IndexPattern) => { + refreshFields = async (indexPattern: DataView) => { try { const fields = (await this.getFieldsForIndexPattern(indexPattern)) as FieldSpec[]; fields.forEach((field) => (field.isMapped = true)); @@ -286,7 +288,7 @@ export class IndexPatternsService { ); indexPattern.fields.replaceAll(fieldsWithSavedAttrs); } catch (err) { - if (err instanceof IndexPatternMissingIndices) { + if (err instanceof DataViewMissingIndices) { this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); } @@ -308,7 +310,7 @@ export class IndexPatternsService { * @returns Record */ private refreshFieldSpecMap = async ( - fields: IndexPatternFieldMap, + fields: DataViewFieldMap, id: string, title: string, options: GetFieldsOptions, @@ -331,7 +333,7 @@ export class IndexPatternsService { return this.fieldArrayToMap(updatedFieldList, fieldAttrs); } catch (err) { - if (err instanceof IndexPatternMissingIndices) { + if (err instanceof DataViewMissingIndices) { this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); return {}; } @@ -353,7 +355,7 @@ export class IndexPatternsService { * @returns Record */ fieldArrayToMap = (fields: FieldSpec[], fieldAttrs?: FieldAttrs) => - fields.reduce((collector, field) => { + fields.reduce((collector, field) => { collector[field.name] = { ...field, customLabel: fieldAttrs?.[field.name]?.customLabel, @@ -368,7 +370,7 @@ export class IndexPatternsService { * @returns IndexPatternSpec */ - savedObjectToSpec = (savedObject: SavedObject): IndexPatternSpec => { + savedObjectToSpec = (savedObject: SavedObject): DataViewSpec => { const { id, version, @@ -413,15 +415,15 @@ export class IndexPatternsService { }; }; - private getSavedObjectAndInit = async (id: string): Promise => { - const savedObject = await this.savedObjectsClient.get( - INDEX_PATTERN_SAVED_OBJECT_TYPE, + private getSavedObjectAndInit = async (id: string): Promise => { + const savedObject = await this.savedObjectsClient.get( + DATA_VIEW_SAVED_OBJECT_TYPE, id ); if (!savedObject.version) { throw new SavedObjectNotFound( - INDEX_PATTERN_SAVED_OBJECT_TYPE, + DATA_VIEW_SAVED_OBJECT_TYPE, id, 'management/kibana/indexPatterns' ); @@ -431,8 +433,8 @@ export class IndexPatternsService { }; private initFromSavedObject = async ( - savedObject: SavedObject - ): Promise => { + savedObject: SavedObject + ): Promise => { const spec = this.savedObjectToSpec(savedObject); const { title, type, typeMeta, runtimeFieldMap } = spec; spec.fieldAttrs = savedObject.attributes.fieldAttrs @@ -471,7 +473,7 @@ export class IndexPatternsService { } } } catch (err) { - if (err instanceof IndexPatternMissingIndices) { + if (err instanceof DataViewMissingIndices) { this.onNotification({ title: (err as any).message, color: 'danger', @@ -501,7 +503,7 @@ export class IndexPatternsService { * @param id */ - get = async (id: string): Promise => { + get = async (id: string): Promise => { const indexPatternPromise = this.indexPatternCache.get(id) || this.indexPatternCache.set(id, this.getSavedObjectAndInit(id)); @@ -520,11 +522,11 @@ export class IndexPatternsService { * @param skipFetchFields * @returns IndexPattern */ - async create(spec: IndexPatternSpec, skipFetchFields = false): Promise { + async create(spec: DataViewSpec, skipFetchFields = false): Promise { const shortDotsEnable = await this.config.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE); const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); - const indexPattern = new IndexPattern({ + const indexPattern = new DataView({ spec, fieldFormats: this.fieldFormats, shortDotsEnable, @@ -545,7 +547,7 @@ export class IndexPatternsService { * @param skipFetchFields Whether to skip field refresh step. */ - async createAndSave(spec: IndexPatternSpec, override = false, skipFetchFields = false) { + async createAndSave(spec: DataViewSpec, override = false, skipFetchFields = false) { const indexPattern = await this.create(spec, skipFetchFields); const createdIndexPattern = await this.createSavedObject(indexPattern, override); await this.setDefault(createdIndexPattern.id!); @@ -558,24 +560,24 @@ export class IndexPatternsService { * @param override Overwrite if existing index pattern exists */ - async createSavedObject(indexPattern: IndexPattern, override = false) { + async createSavedObject(indexPattern: DataView, override = false) { const dupe = await findByTitle(this.savedObjectsClient, indexPattern.title); if (dupe) { if (override) { await this.delete(dupe.id); } else { - throw new DuplicateIndexPatternError(`Duplicate index pattern: ${indexPattern.title}`); + throw new DuplicateDataViewError(`Duplicate index pattern: ${indexPattern.title}`); } } const body = indexPattern.getAsSavedObjectBody(); - const response: SavedObject = (await this.savedObjectsClient.create( - INDEX_PATTERN_SAVED_OBJECT_TYPE, + const response: SavedObject = (await this.savedObjectsClient.create( + DATA_VIEW_SAVED_OBJECT_TYPE, body, { id: indexPattern.id, } - )) as SavedObject; + )) as SavedObject; const createdIndexPattern = await this.initFromSavedObject(response); this.indexPatternCache.set(createdIndexPattern.id!, Promise.resolve(createdIndexPattern)); @@ -592,7 +594,7 @@ export class IndexPatternsService { */ async updateSavedObject( - indexPattern: IndexPattern, + indexPattern: DataView, saveAttempts: number = 0, ignoreErrors: boolean = false ): Promise { @@ -611,7 +613,7 @@ export class IndexPatternsService { }); return this.savedObjectsClient - .update(INDEX_PATTERN_SAVED_OBJECT_TYPE, indexPattern.id, body, { + .update(DATA_VIEW_SAVED_OBJECT_TYPE, indexPattern.id, body, { version: indexPattern.version, }) .then((resp) => { @@ -681,8 +683,18 @@ export class IndexPatternsService { */ async delete(indexPatternId: string) { this.indexPatternCache.clear(indexPatternId); - return this.savedObjectsClient.delete(INDEX_PATTERN_SAVED_OBJECT_TYPE, indexPatternId); + return this.savedObjectsClient.delete(DATA_VIEW_SAVED_OBJECT_TYPE, indexPatternId); } } -export type IndexPatternsContract = PublicMethodsOf; +/** + * @deprecated Use DataViewsService. All index pattern interfaces were renamed. + */ +export class IndexPatternsService extends DataViewsService {} + +export type DataViewsContract = PublicMethodsOf; + +/** + * @deprecated Use DataViewsContract. All index pattern interfaces were renamed. + */ +export type IndexPatternsContract = DataViewsContract; diff --git a/src/plugins/data/common/index_patterns/lib/errors.ts b/src/plugins/data/common/index_patterns/lib/errors.ts index 7a339e381defd8..20f422c5124e68 100644 --- a/src/plugins/data/common/index_patterns/lib/errors.ts +++ b/src/plugins/data/common/index_patterns/lib/errors.ts @@ -13,7 +13,7 @@ import { KbnError } from '../../../../kibana_utils/common/'; /** * Tried to call a method that relies on SearchSource having an indexPattern assigned */ -export class IndexPatternMissingIndices extends KbnError { +export class DataViewMissingIndices extends KbnError { constructor(message: string) { const defaultMessage = "IndexPattern's configured pattern does not match any indices"; diff --git a/src/plugins/data/common/index_patterns/lib/get_title.ts b/src/plugins/data/common/index_patterns/lib/get_title.ts index 69afad486a7450..efebbc302f22c4 100644 --- a/src/plugins/data/common/index_patterns/lib/get_title.ts +++ b/src/plugins/data/common/index_patterns/lib/get_title.ts @@ -7,14 +7,14 @@ */ import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../constants'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../constants'; export async function getTitle( client: SavedObjectsClientContract, indexPatternId: string ): Promise> { const savedObject = (await client.get( - INDEX_PATTERN_SAVED_OBJECT_TYPE, + DATA_VIEW_SAVED_OBJECT_TYPE, indexPatternId )) as SimpleSavedObject; diff --git a/src/plugins/data/common/index_patterns/lib/index.ts b/src/plugins/data/common/index_patterns/lib/index.ts index 57d8c50bda13c7..ae59c7d4178181 100644 --- a/src/plugins/data/common/index_patterns/lib/index.ts +++ b/src/plugins/data/common/index_patterns/lib/index.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -export { IndexPatternMissingIndices } from './errors'; +export { DataViewMissingIndices } from './errors'; export { getTitle } from './get_title'; export { isDefault } from './is_default'; export * from './types'; -export { validateIndexPattern } from './validate_index_pattern'; +export { validateDataView } from './validate_index_pattern'; diff --git a/src/plugins/data/common/index_patterns/lib/validate_index_pattern.test.ts b/src/plugins/data/common/index_patterns/lib/validate_index_pattern.test.ts index b033fd800e4c27..ed90da122484ef 100644 --- a/src/plugins/data/common/index_patterns/lib/validate_index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/lib/validate_index_pattern.test.ts @@ -8,24 +8,24 @@ import { CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY, ILLEGAL_CHARACTERS_VISIBLE } from './types'; -import { validateIndexPattern } from './validate_index_pattern'; +import { validateDataView } from './validate_index_pattern'; describe('Index Pattern Utils', () => { describe('Validation', () => { it('should not allow space in the pattern', () => { - const errors = validateIndexPattern('my pattern'); + const errors = validateDataView('my pattern'); expect(errors[CONTAINS_SPACES_KEY]).toBe(true); }); it('should not allow illegal characters', () => { ILLEGAL_CHARACTERS_VISIBLE.forEach((char) => { - const errors = validateIndexPattern(`pattern${char}`); + const errors = validateDataView(`pattern${char}`); expect(errors[ILLEGAL_CHARACTERS_KEY]).toEqual([char]); }); }); it('should return empty object when there are no errors', () => { - expect(validateIndexPattern('my-pattern-*')).toEqual({}); + expect(validateDataView('my-pattern-*')).toEqual({}); }); }); }); diff --git a/src/plugins/data/common/index_patterns/lib/validate_index_pattern.ts b/src/plugins/data/common/index_patterns/lib/validate_index_pattern.ts index cdafda2ee127fe..454d0bc1a0c6e2 100644 --- a/src/plugins/data/common/index_patterns/lib/validate_index_pattern.ts +++ b/src/plugins/data/common/index_patterns/lib/validate_index_pattern.ts @@ -8,7 +8,7 @@ import { ILLEGAL_CHARACTERS_VISIBLE, CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY } from './types'; -function indexPatternContainsSpaces(indexPattern: string): boolean { +function dataViewContainsSpaces(indexPattern: string): boolean { return indexPattern.includes(' '); } @@ -23,7 +23,7 @@ function findIllegalCharacters(indexPattern: string): string[] { return illegalCharacters; } -export function validateIndexPattern(indexPattern: string) { +export function validateDataView(indexPattern: string) { const errors: Record = {}; const illegalCharacters = findIllegalCharacters(indexPattern); @@ -32,7 +32,7 @@ export function validateIndexPattern(indexPattern: string) { errors[ILLEGAL_CHARACTERS_KEY] = illegalCharacters; } - if (indexPatternContainsSpaces(indexPattern)) { + if (dataViewContainsSpaces(indexPattern)) { errors[CONTAINS_SPACES_KEY] = true; } diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index c326e75aca4159..d1e822aea4e979 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ import type { estypes } from '@elastic/elasticsearch'; -import type { IndexPatternFieldBase, IFieldSubType, IndexPatternBase } from '@kbn/es-query'; +import type { DataViewFieldBase, IFieldSubType, DataViewBase } from '@kbn/es-query'; import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notifications'; // eslint-disable-next-line import type { SavedObject } from 'src/core/server'; import { IFieldType } from './fields'; import { RUNTIME_FIELD_TYPES } from './constants'; import { SerializedFieldFormat } from '../../../expressions/common'; -import { KBN_FIELD_TYPES, IndexPatternField } from '..'; +import { KBN_FIELD_TYPES, DataViewField } from '..'; import { FieldFormat } from '../../../field_formats/common'; export type FieldFormatMap = Record; @@ -31,7 +31,7 @@ export interface RuntimeField { * IIndexPattern allows for an IndexPattern OR an index pattern saved object * Use IndexPattern or IndexPatternSpec instead */ -export interface IIndexPattern extends IndexPatternBase { +export interface IIndexPattern extends DataViewBase { title: string; fields: IFieldType[]; /** @@ -44,15 +44,13 @@ export interface IIndexPattern extends IndexPatternBase { /** * Look up a formatter for a given field */ - getFormatterForField?: ( - field: IndexPatternField | IndexPatternField['spec'] | IFieldType - ) => FieldFormat; + getFormatterForField?: (field: DataViewField | DataViewField['spec'] | IFieldType) => FieldFormat; } /** * Interface for an index pattern saved object */ -export interface IndexPatternAttributes { +export interface DataViewAttributes { fields: string; title: string; type?: string; @@ -69,6 +67,11 @@ export interface IndexPatternAttributes { allowNoIndex?: boolean; } +/** + * @deprecated Use DataViewAttributes. All index pattern interfaces were renamed. + */ +export type IndexPatternAttributes = DataViewAttributes; + /** * @intenal * Storage of field attributes. Necessary since the field list isn't saved. @@ -133,12 +136,17 @@ export interface GetFieldsOptionsTimePattern { interval: string; } -export interface IIndexPatternsApiClient { +export interface IDataViewsApiClient { getFieldsForTimePattern: (options: GetFieldsOptionsTimePattern) => Promise; getFieldsForWildcard: (options: GetFieldsOptions) => Promise; hasUserIndexPattern: () => Promise; } +/** + * @deprecated Use IDataViewsApiClient. All index pattern interfaces were renamed. + */ +export type IIndexPatternsApiClient = IDataViewsApiClient; + export type { SavedObject }; export type AggregationRestrictions = Record< @@ -160,11 +168,19 @@ export interface TypeMeta { }; } -export enum IndexPatternType { +export enum DataViewType { DEFAULT = 'default', ROLLUP = 'rollup', } +/** + * @deprecated Use DataViewType. All index pattern interfaces were renamed. + */ +export enum IndexPatternType { + DEFAULT = DataViewType.DEFAULT, + ROLLUP = DataViewType.ROLLUP, +} + export type FieldSpecConflictDescriptions = Record; // This should become FieldSpec once types are cleaned up @@ -189,7 +205,7 @@ export interface FieldSpecExportFmt { * @public * Serialized version of IndexPatternField */ -export interface FieldSpec extends IndexPatternFieldBase { +export interface FieldSpec extends DataViewFieldBase { /** * Popularity count is used by discover */ @@ -208,13 +224,18 @@ export interface FieldSpec extends IndexPatternFieldBase { isMapped?: boolean; } -export type IndexPatternFieldMap = Record; +export type DataViewFieldMap = Record; + +/** + * @deprecated Use DataViewFieldMap. All index pattern interfaces were renamed. + */ +export type IndexPatternFieldMap = DataViewFieldMap; /** * Static index pattern format * Serialized data object, representing index pattern attributes and state */ -export interface IndexPatternSpec { +export interface DataViewSpec { /** * saved object id */ @@ -231,7 +252,7 @@ export interface IndexPatternSpec { intervalName?: string; timeFieldName?: string; sourceFilters?: SourceFilter[]; - fields?: IndexPatternFieldMap; + fields?: DataViewFieldMap; typeMeta?: TypeMeta; type?: string; fieldFormats?: Record; @@ -240,6 +261,11 @@ export interface IndexPatternSpec { allowNoIndex?: boolean; } +/** + * @deprecated Use DataViewSpec. All index pattern interfaces were renamed. + */ +export type IndexPatternSpec = DataViewSpec; + export interface SourceFilter { value: string; } diff --git a/src/plugins/data/common/index_patterns/utils.ts b/src/plugins/data/common/index_patterns/utils.ts index 925f646b83bb70..48a0776dc43a8f 100644 --- a/src/plugins/data/common/index_patterns/utils.ts +++ b/src/plugins/data/common/index_patterns/utils.ts @@ -9,7 +9,7 @@ import type { IndexPatternSavedObjectAttrs } from './index_patterns'; import type { SavedObjectsClientCommon } from '../types'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../constants'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../constants'; /** * Returns an object matching a given title @@ -21,7 +21,7 @@ import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../constants'; export async function findByTitle(client: SavedObjectsClientCommon, title: string) { if (title) { const savedObjects = await client.find({ - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, perPage: 10, search: `"${title}"`, searchFields: ['title'], diff --git a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts index f5cb7e95747183..496225ad9f00ba 100644 --- a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts +++ b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts @@ -9,12 +9,13 @@ import { i18n } from '@kbn/i18n'; import { Observable } from 'rxjs'; -import { Datatable, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import type { Datatable, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { buildExpressionFunction } from '../../../../../../plugins/expressions/common'; import { IndexPatternExpressionType } from '../../../index_patterns/expressions'; import { IndexPatternsContract } from '../../../index_patterns/index_patterns'; -import { AggsStart, AggExpressionType } from '../../aggs'; +import { AggsStart, AggExpressionType, aggCountFnName } from '../../aggs'; import { ISearchStartSearchSource } from '../../search_source'; import { KibanaContext } from '../kibana_context_type'; @@ -67,7 +68,7 @@ export const getEsaggsMeta: () => Omit aggs: { types: ['agg_type'], multi: true, - default: [], + default: `{${buildExpressionFunction(aggCountFnName, {}).toString()}}`, help: i18n.translate('data.search.functions.esaggs.aggConfigs.help', { defaultMessage: 'List of aggs configured with agg_type functions', }), diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 0602f51889a6cd..986e794c484880 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -43,7 +43,7 @@ import { ILLEGAL_CHARACTERS_VISIBLE, ILLEGAL_CHARACTERS, isDefault, - validateIndexPattern, + validateDataView, flattenHitWrapper, } from './index_patterns'; @@ -58,7 +58,7 @@ export const indexPatterns = { isDefault, isFilterable, isNestedField, - validate: validateIndexPattern, + validate: validateDataView, flattenHitWrapper, }; @@ -82,7 +82,7 @@ export { IndexPatternListItem, } from '../common'; -export { DuplicateIndexPatternError } from '../common/index_patterns/errors'; +export { DuplicateDataViewError } from '../common/index_patterns/errors'; /* * Autocomplete query suggestions: diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts index e23fc789656af0..7229ca5750a38f 100644 --- a/src/plugins/data/public/index_patterns/index.ts +++ b/src/plugins/data/public/index_patterns/index.ts @@ -11,7 +11,7 @@ export { CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_VISIBLE, ILLEGAL_CHARACTERS, - validateIndexPattern, + validateDataView, isDefault, } from '../../common/index_patterns/lib'; export { flattenHitWrapper, formatHitProvider, onRedirectNoIndexPattern } from './index_patterns'; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index d4e8e062451142..b3471f0630390c 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -7,7 +7,7 @@ */ import { HttpSetup } from 'src/core/public'; -import { IndexPatternMissingIndices } from '../../../common/index_patterns/lib'; +import { DataViewMissingIndices } from '../../../common/index_patterns/lib'; import { GetFieldsOptions, IIndexPatternsApiClient, @@ -30,7 +30,7 @@ export class IndexPatternsApiClient implements IIndexPatternsApiClient { }) .catch((resp: any) => { if (resp.body.statusCode === 404 && resp.body.attributes?.code === 'no_matching_indices') { - throw new IndexPatternMissingIndices(resp.body.message); + throw new DataViewMissingIndices(resp.body.message); } throw new Error(resp.body.message || resp.body.error || `${resp.body.statusCode} Response`); diff --git a/src/plugins/data/public/search/expressions/esaggs.test.ts b/src/plugins/data/public/search/expressions/esaggs.test.ts index 2e4bfc329352d9..e43069697f6eb5 100644 --- a/src/plugins/data/public/search/expressions/esaggs.test.ts +++ b/src/plugins/data/public/search/expressions/esaggs.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { omit } from 'lodash'; import { of as mockOf } from 'rxjs'; import type { MockedKeys } from '@kbn/utility-types/jest'; import type { ExecutionContext } from 'src/plugins/expressions/public'; @@ -90,6 +91,12 @@ describe('esaggs expression function - public', () => { ); }); + test('calls aggs.createAggConfigs with the empty aggs array when not provided', async () => { + await definition().fn(null, omit(args, 'aggs'), mockHandlers).toPromise(); + + expect(startDependencies.aggs.createAggConfigs).toHaveBeenCalledWith({}, []); + }); + test('calls getEsaggsMeta to retrieve meta', () => { const result = definition(); diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index cf3de20fea50e5..64b3f3c8dd7de4 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -44,7 +44,7 @@ export function getFunctionDefinition({ const indexPattern = await indexPatterns.create(args.index.value, true); const aggConfigs = aggs.createAggConfigs( indexPattern, - args.aggs!.map((agg) => agg.value) + args.aggs?.map((agg) => agg.value) ?? [] ); aggConfigs.hierarchical = args.metricsAtAllLevels; diff --git a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts index fb76647a945be3..eeb146aee46a3b 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts @@ -12,7 +12,7 @@ import { IIndexPatternsApiClient, GetFieldsOptionsTimePattern, } from '../../common/index_patterns/types'; -import { IndexPatternMissingIndices } from '../../common/index_patterns/lib'; +import { DataViewMissingIndices } from '../../common/index_patterns/lib'; import { IndexPatternsFetcher } from './fetcher'; import { hasUserIndexPattern } from './has_user_index_pattern'; @@ -44,7 +44,7 @@ export class IndexPatternsApiServer implements IIndexPatternsApiClient { err.output.payload.statusCode === 404 && err.output.payload.code === 'no_matching_indices' ) { - throw new IndexPatternMissingIndices(pattern); + throw new DataViewMissingIndices(pattern); } else { throw err; } diff --git a/src/plugins/data/server/search/expressions/esaggs.test.ts b/src/plugins/data/server/search/expressions/esaggs.test.ts index e7e12e9449af37..37c5f90f00f4d8 100644 --- a/src/plugins/data/server/search/expressions/esaggs.test.ts +++ b/src/plugins/data/server/search/expressions/esaggs.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { omit } from 'lodash'; import { of as mockOf } from 'rxjs'; import type { MockedKeys } from '@kbn/utility-types/jest'; import { KibanaRequest } from 'src/core/server'; @@ -98,6 +99,12 @@ describe('esaggs expression function - server', () => { ); }); + test('calls aggs.createAggConfigs with the empty aggs array when not provided', async () => { + await definition().fn(null, omit(args, 'aggs'), mockHandlers).toPromise(); + + expect(startDependencies.aggs.createAggConfigs).toHaveBeenCalledWith({}, []); + }); + test('calls getEsaggsMeta to retrieve meta', () => { const result = definition(); diff --git a/src/plugins/data/server/search/expressions/esaggs.ts b/src/plugins/data/server/search/expressions/esaggs.ts index 3a39276c8ed41b..058538fad4981c 100644 --- a/src/plugins/data/server/search/expressions/esaggs.ts +++ b/src/plugins/data/server/search/expressions/esaggs.ts @@ -56,7 +56,7 @@ export function getFunctionDefinition({ const indexPattern = await indexPatterns.create(args.index.value, true); const aggConfigs = aggs.createAggConfigs( indexPattern, - args.aggs!.map((agg) => agg.value) + args.aggs?.map((agg) => agg.value) ?? [] ); aggConfigs.hierarchical = args.metricsAtAllLevels; diff --git a/src/plugins/home/kibana.json b/src/plugins/home/kibana.json index ccf8d39307f34c..b3bd915bee1436 100644 --- a/src/plugins/home/kibana.json +++ b/src/plugins/home/kibana.json @@ -7,9 +7,7 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["data", "urlForwarding"], + "requiredPlugins": ["data", "share", "urlForwarding"], "optionalPlugins": ["usageCollection", "telemetry"], - "requiredBundles": [ - "kibanaReact" - ] + "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/home/public/application/components/manage_data/__snapshots__/manage_data.test.tsx.snap b/src/plugins/home/public/application/components/manage_data/__snapshots__/manage_data.test.tsx.snap index 85727f76b974ac..401af74231e5d9 100644 --- a/src/plugins/home/public/application/components/manage_data/__snapshots__/manage_data.test.tsx.snap +++ b/src/plugins/home/public/application/components/manage_data/__snapshots__/manage_data.test.tsx.snap @@ -151,7 +151,7 @@ exports[`ManageData render 1`] = ` className="kbnOverviewPageHeader__actionButton" data-test-subj="homeDevTools" flush="both" - href="/app/dev_tools#/console" + href="" iconType="wrench" > { jest.mock('../../kibana_services', () => ({ getServices: () => ({ + share: { url: { locators: { get: () => ({ useUrl: () => '' }) } } }, trackUiMetric: jest.fn(), }), })); diff --git a/src/plugins/home/public/application/components/manage_data/manage_data.tsx b/src/plugins/home/public/application/components/manage_data/manage_data.tsx index fb60b7790aa5e6..746afad9d5b563 100644 --- a/src/plugins/home/public/application/components/manage_data/manage_data.tsx +++ b/src/plugins/home/public/application/components/manage_data/manage_data.tsx @@ -32,9 +32,13 @@ interface Props { } export const ManageData: FC = ({ addBasePath, application, features }) => { - if (features.length) { - const { trackUiMetric } = getServices(); + const { share, trackUiMetric } = getServices(); + const consoleHref = share.url.locators.get('CONSOLE_APP_LOCATOR')?.useUrl({}); + const managementHref = share.url.locators + .get('MANAGEMENT_APP_LOCATOR') + ?.useUrl({ sectionId: '' }); + if (features.length) { const { management: isManagementEnabled, dev_tools: isDevToolsEnabled, @@ -67,7 +71,7 @@ export const ManageData: FC = ({ addBasePath, application, features }) => className="kbnOverviewPageHeader__actionButton" flush="both" iconType="wrench" - href={addBasePath('/app/dev_tools#/console')} + href={consoleHref} > = ({ addBasePath, application, features }) => className="kbnOverviewPageHeader__actionButton" flush="both" iconType="gear" - href={addBasePath('/app/management')} + href={managementHref} > { beforeEach(() => { @@ -26,6 +28,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { + share: mockShare, urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); @@ -45,6 +48,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { + share: mockShare, urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); @@ -56,6 +60,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { + share: {} as SharePluginSetup, urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); @@ -67,6 +72,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { + share: mockShare, urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index 225b1bfe503097..f6a1566b267ac6 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -34,6 +34,7 @@ import { UsageCollectionSetup } from '../../usage_collection/public'; import { UrlForwardingSetup, UrlForwardingStart } from '../../url_forwarding/public'; import { AppNavLinkStatus } from '../../../core/public'; import { PLUGIN_ID, HOME_APP_BASE_PATH } from '../common/constants'; +import { SharePluginSetup } from '../../share/public'; export interface HomePluginStartDependencies { data: DataPublicPluginStart; @@ -42,6 +43,7 @@ export interface HomePluginStartDependencies { } export interface HomePluginSetupDependencies { + share: SharePluginSetup; usageCollection?: UsageCollectionSetup; urlForwarding: UrlForwardingSetup; } @@ -63,7 +65,7 @@ export class HomePublicPlugin public setup( core: CoreSetup, - { urlForwarding, usageCollection }: HomePluginSetupDependencies + { share, urlForwarding, usageCollection }: HomePluginSetupDependencies ): HomePublicPluginSetup { core.application.register({ id: PLUGIN_ID, @@ -78,6 +80,7 @@ export class HomePublicPlugin { telemetry, data, urlForwarding: urlForwardingStart }, ] = await core.getStartServices(); setServices({ + share, trackUiMetric, kibanaVersion: this.initializerContext.env.packageInfo.version, http: coreStart.http, diff --git a/src/plugins/home/tsconfig.json b/src/plugins/home/tsconfig.json index 9324978b227d51..f43c40e35349db 100644 --- a/src/plugins/home/tsconfig.json +++ b/src/plugins/home/tsconfig.json @@ -7,18 +7,14 @@ "declarationMap": true, "isolatedModules": true }, - "include": [ - "common/**/*", - "public/**/*", - "server/**/*", - "config.ts", - ], + "include": ["common/**/*", "public/**/*", "server/**/*", "config.ts"], "references": [ { "path": "../../core/tsconfig.json" }, { "path": "../data/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, + { "path": "../share/tsconfig.json" }, { "path": "../url_forwarding/tsconfig.json" }, { "path": "../usage_collection/tsconfig.json" }, - { "path": "../telemetry/tsconfig.json" }, + { "path": "../telemetry/tsconfig.json" } ] } diff --git a/src/plugins/inspector/kibana.json b/src/plugins/inspector/kibana.json index 66c6617924a7e4..28327e8d3b1488 100644 --- a/src/plugins/inspector/kibana.json +++ b/src/plugins/inspector/kibana.json @@ -8,5 +8,6 @@ "githubTeam": "kibana-app-services" }, "extraPublicDirs": ["common", "common/adapters/request"], - "requiredBundles": ["kibanaReact"] + "requiredBundles": ["kibanaReact"], + "requiredPlugins": ["share"] } diff --git a/src/plugins/inspector/public/mocks.ts b/src/plugins/inspector/public/mocks.ts index 0ef519f9a2b1b9..25f339825409b4 100644 --- a/src/plugins/inspector/public/mocks.ts +++ b/src/plugins/inspector/public/mocks.ts @@ -10,6 +10,7 @@ import { Setup as PluginSetup, Start as PluginStart } from '.'; import { InspectorViewRegistry } from './view_registry'; import { plugin as pluginInitializer } from '.'; import { coreMock } from '../../../core/public/mocks'; +import type { SharePluginStart } from '../../share/public'; export type Setup = jest.Mocked; export type Start = jest.Mocked; @@ -48,6 +49,7 @@ const createPlugin = async () => { const coreStart = coreMock.createStart(); const plugin = pluginInitializer(pluginInitializerContext); const setup = await plugin.setup(coreSetup); + const share = {} as SharePluginStart; return { pluginInitializerContext, @@ -55,7 +57,7 @@ const createPlugin = async () => { coreStart, plugin, setup, - doStart: async () => await plugin.start(coreStart), + doStart: async () => await plugin.start(coreStart, { share }), }; }; diff --git a/src/plugins/inspector/public/plugin.tsx b/src/plugins/inspector/public/plugin.tsx index fca51adf0f65d8..e561a9719b3fbc 100644 --- a/src/plugins/inspector/public/plugin.tsx +++ b/src/plugins/inspector/public/plugin.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import * as React from 'react'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { toMountPoint } from '../../kibana_react/public'; +import { SharePluginStart } from '../../share/public'; import { InspectorViewRegistry } from './view_registry'; import { InspectorOptions, InspectorSession } from './types'; import { InspectorPanel } from './ui/inspector_panel'; @@ -17,6 +18,10 @@ import { Adapters } from '../common'; import { getRequestsViewDescription } from './views'; +export interface InspectorPluginStartDeps { + share: SharePluginStart; +} + export interface Setup { registerView: InspectorViewRegistry['register']; @@ -70,7 +75,7 @@ export class InspectorPublicPlugin implements Plugin { }; } - public start(core: CoreStart) { + public start(core: CoreStart, startDeps: InspectorPluginStartDeps) { const isAvailable: Start['isAvailable'] = (adapters) => this.views!.getVisible(adapters).length > 0; @@ -99,6 +104,7 @@ export class InspectorPublicPlugin implements Plugin { application: core.application, http: core.http, uiSettings: core.uiSettings, + share: startDeps.share, }} /> ), diff --git a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap index cafe65242bd1c3..0dafd8073e9c83 100644 --- a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap +++ b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap @@ -12,8 +12,76 @@ exports[`InspectorPanel should render as expected 1`] = ` } dependencies={ Object { - "application": Object {}, + "application": Object { + "applications$": BehaviorSubject { + "_isScalar": false, + "_value": Map {}, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "capabilities": Object { + "catalogue": Object {}, + "management": Object {}, + "navLinks": Object {}, + }, + "currentAppId$": Observable { + "_isScalar": false, + "source": Subject { + "_isScalar": false, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + }, + }, + "getUrlForApp": [MockFunction], + "navigateToApp": [MockFunction], + "navigateToUrl": [MockFunction], + }, "http": Object {}, + "share": Object {}, "uiSettings": Object {}, } } @@ -145,8 +213,76 @@ exports[`InspectorPanel should render as expected 1`] = ` - } - > - +
-

- View 1 -

- - + } + > + +

+ View 1 +

+
+
+
+ diff --git a/src/plugins/inspector/public/ui/inspector_panel.test.tsx b/src/plugins/inspector/public/ui/inspector_panel.test.tsx index c40dd309c3edaf..879cd3d181951b 100644 --- a/src/plugins/inspector/public/ui/inspector_panel.test.tsx +++ b/src/plugins/inspector/public/ui/inspector_panel.test.tsx @@ -12,15 +12,23 @@ import { InspectorPanel } from './inspector_panel'; import { InspectorViewDescription } from '../types'; import { Adapters } from '../../common'; import type { ApplicationStart, HttpSetup, IUiSettingsClient } from 'kibana/public'; +import { SharePluginStart } from '../../../share/public'; +import { applicationServiceMock } from '../../../../core/public/mocks'; describe('InspectorPanel', () => { let adapters: Adapters; let views: InspectorViewDescription[]; - const dependencies = { - application: {}, + const dependencies = ({ + application: applicationServiceMock.createStartContract(), http: {}, + share: {}, uiSettings: {}, - } as { application: ApplicationStart; http: HttpSetup; uiSettings: IUiSettingsClient }; + } as unknown) as { + application: ApplicationStart; + http: HttpSetup; + share: SharePluginStart; + uiSettings: IUiSettingsClient; + }; beforeEach(() => { adapters = { diff --git a/src/plugins/inspector/public/ui/inspector_panel.tsx b/src/plugins/inspector/public/ui/inspector_panel.tsx index 34ab6d15941b21..af59516a50e438 100644 --- a/src/plugins/inspector/public/ui/inspector_panel.tsx +++ b/src/plugins/inspector/public/ui/inspector_panel.tsx @@ -22,7 +22,8 @@ import { ApplicationStart, HttpStart, IUiSettingsClient } from 'kibana/public'; import { InspectorViewDescription } from '../types'; import { Adapters } from '../../common'; import { InspectorViewChooser } from './inspector_view_chooser'; -import { KibanaContextProvider } from '../../../kibana_react/public'; +import { KibanaContextProvider, RedirectAppLinks } from '../../../kibana_react/public'; +import { SharePluginStart } from '../../../share/public'; function hasAdaptersChanged(oldAdapters: Adapters, newAdapters: Adapters) { return ( @@ -44,6 +45,7 @@ interface InspectorPanelProps { application: ApplicationStart; http: HttpStart; uiSettings: IUiSettingsClient; + share: SharePluginStart; }; } @@ -133,7 +135,9 @@ export class InspectorPanel extends Component - {this.renderSelectedPanel()} + + {this.renderSelectedPanel()} + ); diff --git a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx index a49dae164c994f..12946866f3ec00 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx @@ -6,18 +6,13 @@ * Side Public License, v 1. */ -// Since we're not using `RedirectAppLinks`, we need to use `navigateToUrl` when -// handling the click of the Open in Dev Tools link. We want to have both an -// `onClick` handler and an `href` attribute so it will work on click without a -// page reload, and on right-click to open in new tab. -/* eslint-disable @elastic/eui/href-or-on-click */ - import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { XJsonLang } from '@kbn/monaco'; import { compressToEncodedURIComponent } from 'lz-string'; -import React, { MouseEvent, useCallback } from 'react'; +import React from 'react'; import { CodeEditor, useKibana } from '../../../../../../kibana_react/public'; +import { InspectorPluginStartDeps } from '../../../../plugin'; interface RequestCodeViewerProps { indexPattern?: string; @@ -36,24 +31,14 @@ const openInDevToolsLabel = i18n.translate('inspector.requests.openInDevToolsLab * @internal */ export const RequestCodeViewer = ({ indexPattern, json }: RequestCodeViewerProps) => { - const { services } = useKibana(); - const prepend = services.http?.basePath?.prepend; - const navigateToUrl = services.application?.navigateToUrl; + const { services } = useKibana(); const canShowDevTools = services.application?.capabilities?.dev_tools.show; const devToolsDataUri = compressToEncodedURIComponent(`GET ${indexPattern}/_search\n${json}`); - const devToolsUrl = `/app/dev_tools#/console?load_from=data:text/plain,${devToolsDataUri}`; + const devToolsHref = services.share.url.locators + .get('CONSOLE_APP_LOCATOR') + ?.useUrl({ loadFrom: `data:text/plain,${devToolsDataUri}` }); const shouldShowDevToolsLink = !!(indexPattern && canShowDevTools); - const handleDevToolsLinkClick = useCallback( - (event: MouseEvent) => { - event.preventDefault(); - if (navigateToUrl && prepend) { - navigateToUrl(prepend(devToolsUrl)); - } - }, - [devToolsUrl, navigateToUrl, prepend] - ); - return ( {openInDevToolsLabel} diff --git a/src/plugins/inspector/tsconfig.json b/src/plugins/inspector/tsconfig.json index 4554a90821d483..fd82c73d087cc3 100644 --- a/src/plugins/inspector/tsconfig.json +++ b/src/plugins/inspector/tsconfig.json @@ -9,6 +9,7 @@ "include": ["common/**/*", "public/**/*", "index.ts"], "references": [ { "path": "../../core/tsconfig.json" }, - { "path": "../kibana_react/tsconfig.json" } + { "path": "../kibana_react/tsconfig.json" }, + { "path": "../share/tsconfig.json" } ] } diff --git a/src/plugins/interactive_setup/common/index.ts b/src/plugins/interactive_setup/common/index.ts index f736d1e230122e..ab8c00cfa5a8ed 100644 --- a/src/plugins/interactive_setup/common/index.ts +++ b/src/plugins/interactive_setup/common/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export type { InteractiveSetupViewState, EnrollmentToken } from './types'; +export type { InteractiveSetupViewState, EnrollmentToken, Certificate, PingResult } from './types'; export { ElasticsearchConnectionStatus } from './elasticsearch_connection_status'; diff --git a/src/plugins/interactive_setup/common/types.ts b/src/plugins/interactive_setup/common/types.ts index 4df7c8eaa97246..de3f54dbf9a28a 100644 --- a/src/plugins/interactive_setup/common/types.ts +++ b/src/plugins/interactive_setup/common/types.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import type { PeerCertificate } from 'tls'; + import type { ElasticsearchConnectionStatus } from './elasticsearch_connection_status'; /** @@ -43,3 +45,24 @@ export interface EnrollmentToken { */ key: string; } + +export interface Certificate { + issuer: Partial; + valid_from: PeerCertificate['valid_from']; + valid_to: PeerCertificate['valid_to']; + subject: Partial; + fingerprint256: PeerCertificate['fingerprint256']; + raw: string; +} + +export interface PingResult { + /** + * Indicates whether the cluster requires authentication. + */ + authRequired: boolean; + + /** + * Full certificate chain of cluster at requested address. Only present if cluster uses HTTPS. + */ + certificateChain?: Certificate[]; +} diff --git a/src/plugins/interactive_setup/public/app.scss b/src/plugins/interactive_setup/public/app.scss new file mode 100644 index 00000000000000..119a2377dd7d22 --- /dev/null +++ b/src/plugins/interactive_setup/public/app.scss @@ -0,0 +1,26 @@ +.interactiveSetup { + @include kibanaFullScreenGraphics; +} + +.interactiveSetup__header { + position: relative; + z-index: 10; + padding: $euiSizeXL; +} + +.interactiveSetup__logo { + @include kibanaCircleLogo; + @include euiBottomShadowMedium; + + margin-bottom: $euiSizeXL; +} + +.interactiveSetup__content { + position: relative; + z-index: 10; + margin: auto; + margin-bottom: $euiSizeXL; + max-width: map-get($euiBreakpoints, 's') - $euiSizeXL; + padding-left: $euiSizeXL; + padding-right: $euiSizeXL; +} diff --git a/src/plugins/interactive_setup/public/app.tsx b/src/plugins/interactive_setup/public/app.tsx index 2b6b7089539723..0c206cb4fa215b 100644 --- a/src/plugins/interactive_setup/public/app.tsx +++ b/src/plugins/interactive_setup/public/app.tsx @@ -6,22 +6,76 @@ * Side Public License, v 1. */ -import { EuiPageTemplate, EuiPanel, EuiText } from '@elastic/eui'; -import React from 'react'; +import './app.scss'; + +import { EuiIcon, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React, { useState } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { ClusterAddressForm } from './cluster_address_form'; +import type { ClusterConfigurationFormProps } from './cluster_configuration_form'; +import { ClusterConfigurationForm } from './cluster_configuration_form'; +import { EnrollmentTokenForm } from './enrollment_token_form'; +import { ProgressIndicator } from './progress_indicator'; + +export const App: FunctionComponent = () => { + const [page, setPage] = useState<'token' | 'manual' | 'success'>('token'); + const [cluster, setCluster] = useState< + Omit + >(); -export const App = () => { return ( - - - Kibana server is not ready yet. - - +
+
+ + + + + +

+ +

+
+ +
+
+ + + + {page === 'success' && ( + window.location.replace(window.location.href)} /> + )} + +
+
); }; diff --git a/src/plugins/interactive_setup/public/cluster_address_form.test.tsx b/src/plugins/interactive_setup/public/cluster_address_form.test.tsx new file mode 100644 index 00000000000000..e063205a90433c --- /dev/null +++ b/src/plugins/interactive_setup/public/cluster_address_form.test.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fireEvent, render, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { coreMock } from 'src/core/public/mocks'; + +import { ClusterAddressForm } from './cluster_address_form'; +import { Providers } from './plugin'; + +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => `id-${Math.random()}`, +})); + +describe('ClusterAddressForm', () => { + jest.setTimeout(20_000); + + it('calls enrollment API when submitting form', async () => { + const coreStart = coreMock.createStart(); + coreStart.http.post.mockResolvedValue({}); + + const onSuccess = jest.fn(); + + const { findByRole, findByLabelText } = render( + + + + ); + fireEvent.change(await findByLabelText('Address'), { + target: { value: 'https://localhost' }, + }); + fireEvent.click(await findByRole('button', { name: 'Check address', hidden: true })); + + await waitFor(() => { + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/interactive_setup/ping', { + body: JSON.stringify({ + host: 'https://localhost:9200', + }), + }); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + it('validates form', async () => { + const coreStart = coreMock.createStart(); + const onSuccess = jest.fn(); + + const { findAllByText, findByRole, findByLabelText } = render( + + + + ); + + fireEvent.change(await findByLabelText('Address'), { + target: { value: 'localhost' }, + }); + + fireEvent.click(await findByRole('button', { name: 'Check address', hidden: true })); + + await findAllByText(/Enter a valid address including protocol/i); + + expect(coreStart.http.post).not.toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/interactive_setup/public/cluster_address_form.tsx b/src/plugins/interactive_setup/public/cluster_address_form.tsx new file mode 100644 index 00000000000000..ba7b1d46182a17 --- /dev/null +++ b/src/plugins/interactive_setup/public/cluster_address_form.tsx @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiSpacer, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { IHttpFetchError } from 'kibana/public'; + +import type { PingResult } from '../common'; +import type { ValidationErrors } from './use_form'; +import { useForm } from './use_form'; +import { useHttp } from './use_http'; + +export interface ClusterAddressFormValues { + host: string; +} + +export interface ClusterAddressFormProps { + defaultValues?: ClusterAddressFormValues; + onCancel?(): void; + onSuccess?(result: PingResult, values: ClusterAddressFormValues): void; +} + +export const ClusterAddressForm: FunctionComponent = ({ + defaultValues = { + host: 'https://localhost:9200', + }, + onCancel, + onSuccess, +}) => { + const http = useHttp(); + + const [form, eventHandlers] = useForm({ + defaultValues, + validate: async (values) => { + const errors: ValidationErrors = {}; + + if (!values.host) { + errors.host = i18n.translate('interactiveSetup.clusterAddressForm.hostRequiredError', { + defaultMessage: 'Enter an address.', + }); + } else { + try { + const url = new URL(values.host); + if (!url.protocol || !url.hostname) { + throw new Error(); + } + } catch (error) { + errors.host = i18n.translate('interactiveSetup.clusterAddressForm.hostInvalidError', { + defaultMessage: 'Enter a valid address including protocol.', + }); + } + } + + return errors; + }, + onSubmit: async (values) => { + const url = new URL(values.host); + const host = `${url.protocol}//${url.hostname}:${url.port || 9200}`; + + const result = await http.post('/internal/interactive_setup/ping', { + body: JSON.stringify({ host }), + }); + + onSuccess?.(result, { host }); + }, + }); + + return ( + + {form.submitError && ( + <> + + {(form.submitError as IHttpFetchError).body?.message} + + + + )} + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/plugins/interactive_setup/public/cluster_configuration_form.test.tsx b/src/plugins/interactive_setup/public/cluster_configuration_form.test.tsx new file mode 100644 index 00000000000000..93f3fa11a1ce6e --- /dev/null +++ b/src/plugins/interactive_setup/public/cluster_configuration_form.test.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fireEvent, render, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { coreMock } from 'src/core/public/mocks'; + +import { ClusterConfigurationForm } from './cluster_configuration_form'; +import { Providers } from './plugin'; + +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => `id-${Math.random()}`, +})); + +describe('ClusterConfigurationForm', () => { + jest.setTimeout(20_000); + + it('calls enrollment API when submitting form', async () => { + const coreStart = coreMock.createStart(); + coreStart.http.post.mockResolvedValue({}); + + const onSuccess = jest.fn(); + + const { findByRole, findByLabelText } = render( + + + + ); + fireEvent.change(await findByLabelText('Username'), { + target: { value: 'kibana_system' }, + }); + fireEvent.change(await findByLabelText('Password'), { + target: { value: 'changeme' }, + }); + fireEvent.click(await findByLabelText('Certificate authority')); + fireEvent.click(await findByRole('button', { name: 'Connect to cluster', hidden: true })); + + await waitFor(() => { + expect(coreStart.http.post).toHaveBeenLastCalledWith( + '/internal/interactive_setup/configure', + { + body: JSON.stringify({ + host: 'https://localhost:9200', + username: 'kibana_system', + password: 'changeme', + caCert: 'cert', + }), + } + ); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + it('validates form', async () => { + const coreStart = coreMock.createStart(); + const onSuccess = jest.fn(); + + const { findAllByText, findByRole, findByLabelText } = render( + + + + ); + + fireEvent.click(await findByRole('button', { name: 'Connect to cluster', hidden: true })); + + await findAllByText(/Enter a password/i); + await findAllByText(/Confirm that you recognize and trust this certificate/i); + + fireEvent.change(await findByLabelText('Username'), { + target: { value: 'elastic' }, + }); + + await findAllByText(/User 'elastic' can't be used as Kibana system user/i); + + expect(coreStart.http.post).not.toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/interactive_setup/public/cluster_configuration_form.tsx b/src/plugins/interactive_setup/public/cluster_configuration_form.tsx new file mode 100644 index 00000000000000..cd3541fe0318f0 --- /dev/null +++ b/src/plugins/interactive_setup/public/cluster_configuration_form.tsx @@ -0,0 +1,322 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiCheckableCard, + EuiFieldPassword, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { IHttpFetchError } from 'kibana/public'; + +import type { Certificate } from '../common'; +import { TextTruncate } from './text_truncate'; +import type { ValidationErrors } from './use_form'; +import { useForm } from './use_form'; +import { useHtmlId } from './use_html_id'; +import { useHttp } from './use_http'; + +export interface ClusterConfigurationFormValues { + username: string; + password: string; + caCert: string; +} + +export interface ClusterConfigurationFormProps { + host: string; + authRequired: boolean; + certificateChain?: Certificate[]; + defaultValues?: ClusterConfigurationFormValues; + onCancel?(): void; + onSuccess?(): void; +} + +export const ClusterConfigurationForm: FunctionComponent = ({ + host, + authRequired, + certificateChain, + defaultValues = { + username: 'kibana_system', + password: '', + caCert: '', + }, + onCancel, + onSuccess, +}) => { + const http = useHttp(); + + const [form, eventHandlers] = useForm({ + defaultValues, + validate: async (values) => { + const errors: ValidationErrors = {}; + + if (authRequired) { + if (!values.username) { + errors.username = i18n.translate( + 'interactiveSetup.clusterConfigurationForm.usernameRequiredError', + { + defaultMessage: 'Enter a username.', + } + ); + } else if (values.username === 'elastic') { + errors.username = i18n.translate( + 'interactiveSetup.clusterConfigurationForm.usernameReservedError', + { + defaultMessage: "User 'elastic' can't be used as Kibana system user.", + } + ); + } + + if (!values.password) { + errors.password = i18n.translate( + 'interactiveSetup.clusterConfigurationForm.passwordRequiredError', + { + defaultMessage: `Enter a password.`, + } + ); + } + } + + if (certificateChain && !values.caCert) { + errors.caCert = i18n.translate( + 'interactiveSetup.clusterConfigurationForm.caCertConfirmationRequiredError', + { + defaultMessage: 'Confirm that you recognize and trust this certificate.', + } + ); + } + + return errors; + }, + onSubmit: async (values) => { + await http.post('/internal/interactive_setup/configure', { + body: JSON.stringify({ + host, + username: values.username, + password: values.password, + caCert: values.caCert, + }), + }); + onSuccess?.(); + }, + }); + + const trustCaCertId = useHtmlId('clusterConfigurationForm', 'trustCaCert'); + + return ( + + {form.submitError && ( + <> + + {(form.submitError as IHttpFetchError).body?.message} + + + + )} + + + + + + + + {host} + + + + + + {authRequired ? ( + <> + + + + + + + + + ) : ( + <> + +

+ +

+

+ + + +

+
+ + + )} + + {certificateChain && certificateChain.length > 0 && ( + <> + + { + const intermediateCa = certificateChain[Math.min(1, certificateChain.length - 1)]; + form.setValue('caCert', form.values.caCert ? '' : intermediateCa.raw); + form.setTouched('caCert'); + }} + > + + + + + + )} + + + + + + + + + + + + + +
+ ); +}; + +export interface CertificatePanelProps { + certificate: Certificate; +} + +export const CertificatePanel: FunctionComponent = ({ certificate }) => { + return ( + + + + + + + +

{certificate.subject.O || certificate.subject.CN}

+
+ + + + + + +
+
+
+ ); +}; diff --git a/src/plugins/interactive_setup/public/enrollment_token_form.test.tsx b/src/plugins/interactive_setup/public/enrollment_token_form.test.tsx new file mode 100644 index 00000000000000..d2f08eac1fac52 --- /dev/null +++ b/src/plugins/interactive_setup/public/enrollment_token_form.test.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fireEvent, render, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { coreMock } from 'src/core/public/mocks'; + +import type { EnrollmentToken } from '../common'; +import { decodeEnrollmentToken, EnrollmentTokenForm } from './enrollment_token_form'; +import { Providers } from './plugin'; + +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => `id-${Math.random()}`, +})); + +const token: EnrollmentToken = { + ver: '8.0.0', + adr: ['localhost:9200'], + fgr: + 'AA:C8:2C:2E:09:58:F4:FE:A1:D2:AB:7F:13:70:C2:7D:EB:FD:A2:23:88:13:E4:DA:3A:D0:59:D0:09:00:07:36', + key: 'JH-36HoBo4EYIoVhHh2F:uEo4dksARMq_BSHaAHUr8Q', +}; + +describe('EnrollmentTokenForm', () => { + jest.setTimeout(20_000); + + it('calls enrollment API when submitting form', async () => { + const coreStart = coreMock.createStart(); + coreStart.http.post.mockResolvedValue({}); + + const onSuccess = jest.fn(); + + const { findByRole, findByLabelText } = render( + + + + ); + fireEvent.change(await findByLabelText('Enrollment token'), { + target: { value: btoa(JSON.stringify(token)) }, + }); + fireEvent.click(await findByRole('button', { name: 'Connect to cluster', hidden: true })); + + await waitFor(() => { + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/interactive_setup/enroll', { + body: JSON.stringify({ + hosts: [`https://${token.adr[0]}`], + apiKey: btoa(token.key), + caFingerprint: token.fgr, + }), + }); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + it('validates form', async () => { + const coreStart = coreMock.createStart(); + const onSuccess = jest.fn(); + + const { findAllByText, findByRole, findByLabelText } = render( + + + + ); + + fireEvent.click(await findByRole('button', { name: 'Connect to cluster', hidden: true })); + + await findAllByText(/Enter an enrollment token/i); + + fireEvent.change(await findByLabelText('Enrollment token'), { + target: { value: 'invalid' }, + }); + + await findAllByText(/Enter a valid enrollment token/i); + }); +}); + +describe('decodeEnrollmentToken', () => { + it('should decode a valid token', () => { + expect(decodeEnrollmentToken(btoa(JSON.stringify(token)))).toEqual({ + adr: ['https://localhost:9200'], + fgr: + 'AA:C8:2C:2E:09:58:F4:FE:A1:D2:AB:7F:13:70:C2:7D:EB:FD:A2:23:88:13:E4:DA:3A:D0:59:D0:09:00:07:36', + key: 'SkgtMzZIb0JvNEVZSW9WaEhoMkY6dUVvNGRrc0FSTXFfQlNIYUFIVXI4UQ==', + ver: '8.0.0', + }); + }); + + it('should not decode an invalid token', () => { + expect(decodeEnrollmentToken(JSON.stringify(token))).toBeUndefined(); + expect( + decodeEnrollmentToken( + btoa( + JSON.stringify({ + ver: [''], + adr: null, + fgr: false, + key: undefined, + }) + ) + ) + ).toBeUndefined(); + expect(decodeEnrollmentToken(btoa(JSON.stringify({})))).toBeUndefined(); + expect(decodeEnrollmentToken(btoa(JSON.stringify([])))).toBeUndefined(); + expect(decodeEnrollmentToken(btoa(JSON.stringify(null)))).toBeUndefined(); + expect(decodeEnrollmentToken(btoa(JSON.stringify('')))).toBeUndefined(); + }); +}); diff --git a/src/plugins/interactive_setup/public/enrollment_token_form.tsx b/src/plugins/interactive_setup/public/enrollment_token_form.tsx new file mode 100644 index 00000000000000..3b5c751874a10a --- /dev/null +++ b/src/plugins/interactive_setup/public/enrollment_token_form.tsx @@ -0,0 +1,204 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiIcon, + EuiSpacer, + EuiText, + EuiTextArea, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { IHttpFetchError } from 'kibana/public'; + +import type { EnrollmentToken } from '../common'; +import { TextTruncate } from './text_truncate'; +import type { ValidationErrors } from './use_form'; +import { useForm } from './use_form'; +import { useHttp } from './use_http'; + +export interface EnrollmentTokenFormValues { + token: string; +} + +export interface EnrollmentTokenFormProps { + defaultValues?: EnrollmentTokenFormValues; + onCancel?(): void; + onSuccess?(): void; +} + +export const EnrollmentTokenForm: FunctionComponent = ({ + defaultValues = { + token: '', + }, + onCancel, + onSuccess, +}) => { + const http = useHttp(); + const [form, eventHandlers] = useForm({ + defaultValues, + validate: (values) => { + const errors: ValidationErrors = {}; + + if (!values.token) { + errors.token = i18n.translate('interactiveSetup.enrollmentTokenForm.tokenRequiredError', { + defaultMessage: 'Enter an enrollment token.', + }); + } else { + const decoded = decodeEnrollmentToken(values.token); + if (!decoded) { + errors.token = i18n.translate('interactiveSetup.enrollmentTokenForm.tokenInvalidError', { + defaultMessage: 'Enter a valid enrollment token.', + }); + } + } + + return errors; + }, + onSubmit: async (values) => { + const decoded = decodeEnrollmentToken(values.token)!; + await http.post('/internal/interactive_setup/enroll', { + body: JSON.stringify({ + hosts: decoded.adr, + apiKey: decoded.key, + caFingerprint: decoded.fgr, + }), + }); + onSuccess?.(); + }, + }); + + const enrollmentToken = decodeEnrollmentToken(form.values.token); + + return ( + + {form.submitError && ( + <> + + {(form.submitError as IHttpFetchError).body?.message} + + + + )} + + } + fullWidth + > + + + + + + + + + + + + + + + + + + ); +}; + +interface EnrollmentTokenDetailsProps { + token: EnrollmentToken; +} + +const EnrollmentTokenDetails: FunctionComponent = ({ token }) => ( + + + + + + + + + + {token.adr[0]} + + + + + + + + + +); + +export function decodeEnrollmentToken(enrollmentToken: string) { + try { + const json = JSON.parse(atob(enrollmentToken)) as EnrollmentToken; + if ( + !Array.isArray(json.adr) || + json.adr.some((adr) => typeof adr !== 'string') || + typeof json.fgr !== 'string' || + typeof json.key !== 'string' || + typeof json.ver !== 'string' + ) { + return; + } + return { + ...json, + adr: json.adr.map((host) => `https://${host}`), + key: btoa(json.key), + }; + } catch (error) {} // eslint-disable-line no-empty +} diff --git a/src/plugins/interactive_setup/public/index.ts b/src/plugins/interactive_setup/public/index.ts index 153bc92a0dd087..7855b90b810d21 100644 --- a/src/plugins/interactive_setup/public/index.ts +++ b/src/plugins/interactive_setup/public/index.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -import { UserSetupPlugin } from './plugin'; +import { InteractiveSetupPlugin } from './plugin'; -export const plugin = () => new UserSetupPlugin(); +export const plugin = () => new InteractiveSetupPlugin(); diff --git a/src/plugins/interactive_setup/public/plugin.tsx b/src/plugins/interactive_setup/public/plugin.tsx index 375f04e5047d51..00fd38d3e78a45 100644 --- a/src/plugins/interactive_setup/public/plugin.tsx +++ b/src/plugins/interactive_setup/public/plugin.tsx @@ -6,21 +6,30 @@ * Side Public License, v 1. */ +import type { FunctionComponent } from 'react'; import React from 'react'; import ReactDOM from 'react-dom'; -import type { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { I18nProvider } from '@kbn/i18n/react'; +import type { CoreSetup, CoreStart, HttpSetup, Plugin } from 'src/core/public'; import { App } from './app'; +import { HttpProvider } from './use_http'; -export class UserSetupPlugin implements Plugin { +export class InteractiveSetupPlugin implements Plugin { public setup(core: CoreSetup) { core.application.register({ id: 'interactiveSetup', - title: 'Interactive Setup', + title: 'Configure Elastic to get started', + appRoute: '/', chromeless: true, mount: (params) => { - ReactDOM.render(, params.element); + ReactDOM.render( + + + , + params.element + ); return () => ReactDOM.unmountComponentAtNode(params.element); }, }); @@ -28,3 +37,13 @@ export class UserSetupPlugin implements Plugin { public start(core: CoreStart) {} } + +export interface ProvidersProps { + http: HttpSetup; +} + +export const Providers: FunctionComponent = ({ http, children }) => ( + + {children} + +); diff --git a/src/plugins/interactive_setup/public/progress_indicator.tsx b/src/plugins/interactive_setup/public/progress_indicator.tsx new file mode 100644 index 00000000000000..a6d499f6a57124 --- /dev/null +++ b/src/plugins/interactive_setup/public/progress_indicator.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { EuiStepProps } from '@elastic/eui'; +import { EuiPanel, EuiSteps } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React, { useEffect } from 'react'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import useTimeoutFn from 'react-use/lib/useTimeoutFn'; + +import { i18n } from '@kbn/i18n'; + +import { useHttp } from './use_http'; + +export interface ProgressIndicatorProps { + onSuccess?(): void; +} + +export const ProgressIndicator: FunctionComponent = ({ onSuccess }) => { + const http = useHttp(); + const [status, checkStatus] = useAsyncFn(async () => { + let isAvailable: boolean | undefined = false; + let isPastPreboot: boolean | undefined = false; + try { + const { response } = await http.get('/api/status', { asResponse: true }); + isAvailable = response ? response.status < 500 : undefined; + isPastPreboot = response?.headers.get('content-type')?.includes('application/json'); + } catch ({ response }) { + isAvailable = response ? response.status < 500 : undefined; + isPastPreboot = response?.headers.get('content-type')?.includes('application/json'); + } + return isAvailable === true && isPastPreboot === true + ? 'complete' + : isAvailable === false + ? 'unavailable' + : isAvailable === true && isPastPreboot === false + ? 'preboot' + : 'unknown'; + }); + + const [, cancelPolling, resetPolling] = useTimeoutFn(checkStatus, 1000); + + useEffect(() => { + if (status.value === 'complete') { + cancelPolling(); + onSuccess?.(); + } else if (status.loading === false) { + resetPolling(); + } + }, [status.loading, status.value]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + + + + ); +}; + +type Optional = Omit & Partial; + +export interface LoadingStepsProps { + currentStepId?: string; + steps: Array>; +} + +export const LoadingSteps: FunctionComponent = ({ currentStepId, steps }) => { + const currentStepIndex = steps.findIndex((step) => step.id === currentStepId); + return ( + ({ + status: + i <= currentStepIndex + ? 'complete' + : steps[i - 1]?.id === currentStepId + ? 'loading' + : 'incomplete', + children: null, + ...step, + }))} + /> + ); +}; diff --git a/src/plugins/interactive_setup/public/text_truncate.tsx b/src/plugins/interactive_setup/public/text_truncate.tsx new file mode 100644 index 00000000000000..32736e80211aec --- /dev/null +++ b/src/plugins/interactive_setup/public/text_truncate.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiToolTip } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React, { useLayoutEffect, useRef, useState } from 'react'; + +export const TextTruncate: FunctionComponent = ({ children }) => { + const textRef = useRef(null); + const [showTooltip, setShowTooltip] = useState(false); + + useLayoutEffect(() => { + if (textRef.current) { + const { clientWidth, scrollWidth } = textRef.current; + setShowTooltip(scrollWidth > clientWidth); + } + }, [children]); + + const truncated = ( + + {children} + + ); + + if (showTooltip) { + return ( + + {truncated} + + ); + } + + return truncated; +}; diff --git a/src/plugins/interactive_setup/public/use_form.ts b/src/plugins/interactive_setup/public/use_form.ts new file mode 100644 index 00000000000000..8ed1d89ea087e9 --- /dev/null +++ b/src/plugins/interactive_setup/public/use_form.ts @@ -0,0 +1,209 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { set } from '@elastic/safer-lodash-set'; +import { cloneDeep, cloneDeepWith, get } from 'lodash'; +import type { ChangeEventHandler, FocusEventHandler, ReactEventHandler } from 'react'; +import { useRef } from 'react'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; + +export type FormReturnTuple = [FormState, FormProps]; + +export interface FormProps { + onSubmit: ReactEventHandler; + onChange: ChangeEventHandler; + onBlur: FocusEventHandler; +} + +export interface FormOptions { + onSubmit: SubmitCallback; + validate: ValidateCallback; + defaultValues: Values; +} + +/** + * Returns state and {@link HTMLFormElement} event handlers useful for creating + * forms with inline validation. + * + * @see {@link useFormState} if you don't want to use {@link HTMLFormElement}. + * + * @example + * ```typescript + * const [form, eventHandlers] = useForm({ + * onSubmit: (values) => apiClient.create(values), + * validate: (values) => !values.email ? { email: 'Required' } : {} + * }); + * + * + * + * Submit + * + * ``` + */ +export function useForm( + options: FormOptions +): FormReturnTuple { + const form = useFormState(options); + + const eventHandlers: FormProps = { + onSubmit: (event) => { + event.preventDefault(); + form.submit(); + }, + onChange: (event) => { + const { name, type, checked, value } = event.target; + if (name) { + form.setValue(name, type === 'checkbox' ? checked : value); + } + }, + onBlur: (event) => { + const { name } = event.target; + if (name) { + form.setTouched(event.target.name); + } + }, + }; + + return [form, eventHandlers]; +} + +export type FormValues = Record; +export type SubmitCallback = (values: Values) => Promise; +export type ValidateCallback = ( + values: Values +) => ValidationErrors | Promise>; +export type ValidationErrors = DeepMap; +export type TouchedFields = DeepMap; + +export interface FormState { + setValue(name: string, value: any, revalidate?: boolean): Promise; + setError(name: string, message: string): void; + setTouched(name: string, touched?: boolean, revalidate?: boolean): Promise; + reset(values: Values): void; + submit(): Promise; + validate(): Promise>; + values: Values; + errors: ValidationErrors; + touched: TouchedFields; + isValidating: boolean; + isSubmitting: boolean; + submitError: Error | undefined; + isInvalid: boolean; + isSubmitted: boolean; +} + +/** + * Returns state useful for creating forms with inline validation. + * + * @example + * ```typescript + * const form = useFormState({ + * onSubmit: (values) => apiClient.create(values), + * validate: (values) => !values.toggle ? { toggle: 'Required' } : {} + * }); + * + * form.setValue('toggle', e.target.checked)} + * onBlur={() => form.setTouched('toggle')} + * isInvalid={!!form.errors.toggle} + * /> + * + * Submit + * + * ``` + */ +export function useFormState({ + onSubmit, + validate, + defaultValues, +}: FormOptions): FormState { + const valuesRef = useRef(defaultValues); + const errorsRef = useRef>({}); + const touchedRef = useRef>({}); + const submitCountRef = useRef(0); + + const [validationState, validateForm] = useAsyncFn(async (formValues: Values) => { + const nextErrors = await validate(formValues); + errorsRef.current = nextErrors; + if (Object.keys(nextErrors).length === 0) { + submitCountRef.current = 0; + } + return nextErrors; + }, []); + + const [submitState, submitForm] = useAsyncFn(async (formValues: Values) => { + const nextErrors = await validateForm(formValues); + touchedRef.current = mapDeep(formValues, true); + submitCountRef.current += 1; + if (Object.keys(nextErrors).length === 0) { + return onSubmit(formValues); + } + }, []); + + return { + setValue: async (name, value, revalidate = true) => { + const nextValues = setDeep(valuesRef.current, name, value); + valuesRef.current = nextValues; + if (revalidate) { + await validateForm(nextValues); + } + }, + setTouched: async (name, touched = true, revalidate = true) => { + touchedRef.current = setDeep(touchedRef.current, name, touched); + if (revalidate) { + await validateForm(valuesRef.current); + } + }, + setError: (name, message) => { + errorsRef.current = setDeep(errorsRef.current, name, message); + touchedRef.current = setDeep(touchedRef.current, name, true); + }, + reset: (nextValues) => { + valuesRef.current = nextValues; + errorsRef.current = {}; + touchedRef.current = {}; + submitCountRef.current = 0; + }, + submit: () => submitForm(valuesRef.current), + validate: () => validateForm(valuesRef.current), + values: valuesRef.current, + errors: errorsRef.current, + touched: touchedRef.current, + isValidating: validationState.loading, + isSubmitting: submitState.loading, + submitError: submitState.error, + isInvalid: Object.keys(errorsRef.current).length > 0, + isSubmitted: submitCountRef.current > 0, + }; +} + +type DeepMap = { + [K in keyof T]?: T[K] extends any[] + ? T[K][number] extends object + ? Array> + : TValue + : T[K] extends object + ? DeepMap + : TValue; +}; + +function mapDeep(values: T, value: V): DeepMap { + return cloneDeepWith(values, (v) => { + if (typeof v !== 'object' && v !== null) { + return value; + } + }); +} + +function setDeep(values: T, name: string, value: V): T { + if (get(values, name) !== value) { + return set(cloneDeep(values), name, value); + } + return values; +} diff --git a/src/plugins/interactive_setup/public/use_html_id.ts b/src/plugins/interactive_setup/public/use_html_id.ts new file mode 100644 index 00000000000000..d2b568bf263199 --- /dev/null +++ b/src/plugins/interactive_setup/public/use_html_id.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { htmlIdGenerator } from '@elastic/eui'; +import { useMemo } from 'react'; + +/** + * Generates an ID that can be used for HTML elements. + * + * @param prefix Prefix of the id to be generated + * @param suffix Suffix of the id to be generated + * + * @example + * ```typescript + * const titleId = useHtmlId('changePasswordForm', 'title'); + * + * + *

Change password

+ *
+ * ``` + */ +export function useHtmlId(prefix?: string, suffix?: string) { + return useMemo(() => htmlIdGenerator(prefix)(suffix), [prefix, suffix]); +} diff --git a/src/plugins/interactive_setup/public/use_http.ts b/src/plugins/interactive_setup/public/use_http.ts new file mode 100644 index 00000000000000..6d2a9f03d4c73a --- /dev/null +++ b/src/plugins/interactive_setup/public/use_http.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import constate from 'constate'; + +import type { HttpSetup } from 'src/core/public'; + +export const [HttpProvider, useHttp] = constate(({ http }: { http: HttpSetup }) => { + return http; +}); diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.mock.ts b/src/plugins/interactive_setup/server/elasticsearch_service.mock.ts index 8bc7e4307e76f8..9b59ab59672614 100644 --- a/src/plugins/interactive_setup/server/elasticsearch_service.mock.ts +++ b/src/plugins/interactive_setup/server/elasticsearch_service.mock.ts @@ -16,5 +16,7 @@ export const elasticsearchServiceMock = { ElasticsearchConnectionStatus.Configured ), enroll: jest.fn(), + authenticate: jest.fn(), + ping: jest.fn(), }), }; diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts index 546ab7ea8f9c09..ce4893112ebad5 100644 --- a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts +++ b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts @@ -7,6 +7,7 @@ */ import { errors } from '@elastic/elasticsearch'; +import tls from 'tls'; import { nextTick } from '@kbn/test/jest'; import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; @@ -17,6 +18,10 @@ import type { ElasticsearchServiceSetup } from './elasticsearch_service'; import { ElasticsearchService } from './elasticsearch_service'; import { interactiveSetupMock } from './mocks'; +jest.mock('tls'); + +const tlsConnectMock = tls.connect as jest.MockedFunction; + describe('ElasticsearchService', () => { let service: ElasticsearchService; let mockElasticsearchPreboot: ReturnType; @@ -33,17 +38,21 @@ describe('ElasticsearchService', () => { let mockAuthenticateClient: ReturnType< typeof elasticsearchServiceMock.createCustomClusterClient >; + let mockPingClient: ReturnType; let setupContract: ElasticsearchServiceSetup; beforeEach(() => { mockConnectionStatusClient = elasticsearchServiceMock.createCustomClusterClient(); mockEnrollClient = elasticsearchServiceMock.createCustomClusterClient(); mockAuthenticateClient = elasticsearchServiceMock.createCustomClusterClient(); + mockPingClient = elasticsearchServiceMock.createCustomClusterClient(); mockElasticsearchPreboot.createClient.mockImplementation((type) => { switch (type) { case 'enroll': return mockEnrollClient; case 'authenticate': return mockAuthenticateClient; + case 'ping': + return mockPingClient; default: return mockConnectionStatusClient; } @@ -414,7 +423,7 @@ some weird+ca/with caFingerprint: 'DE:AD:BE:EF', }) ).resolves.toEqual({ - ca: expectedCa, + caCert: expectedCa, host: 'host2', serviceAccountToken: { name: 'some-name', @@ -478,6 +487,133 @@ some weird+ca/with expect(mockAuthenticateClient.close).toHaveBeenCalledTimes(1); }); }); + + describe('#authenticate()', () => { + it('fails if ping call fails', async () => { + mockAuthenticateClient.asInternalUser.ping.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + + await expect( + setupContract.authenticate({ host: 'http://localhost:9200' }) + ).rejects.toMatchInlineSnapshot(`[ConnectionError: some-message]`); + }); + + it('succeeds if ping call succeeds', async () => { + mockAuthenticateClient.asInternalUser.ping.mockResolvedValue( + interactiveSetupMock.createApiResponse({ statusCode: 200, body: true }) + ); + + await expect( + setupContract.authenticate({ host: 'http://localhost:9200' }) + ).resolves.toEqual(undefined); + }); + }); + + describe('#ping()', () => { + it('fails if host is not reachable', async () => { + mockPingClient.asInternalUser.ping.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + + await expect(setupContract.ping('http://localhost:9200')).rejects.toMatchInlineSnapshot( + `[ConnectionError: some-message]` + ); + }); + + it('fails if host is not supported', async () => { + mockPingClient.asInternalUser.ping.mockRejectedValue( + new errors.ProductNotSupportedError(interactiveSetupMock.createApiResponse({ body: {} })) + ); + + await expect(setupContract.ping('http://localhost:9200')).rejects.toMatchInlineSnapshot( + `[ProductNotSupportedError: The client noticed that the server is not Elasticsearch and we do not support this unknown product.]` + ); + }); + + it('succeeds if host does not require authentication', async () => { + mockPingClient.asInternalUser.ping.mockResolvedValue( + interactiveSetupMock.createApiResponse({ statusCode: 200, body: true }) + ); + + await expect(setupContract.ping('http://localhost:9200')).resolves.toEqual({ + authRequired: false, + certificateChain: undefined, + }); + }); + + it('succeeds if host requires authentication', async () => { + mockPingClient.asInternalUser.ping.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: {} }) + ) + ); + + await expect(setupContract.ping('http://localhost:9200')).resolves.toEqual({ + authRequired: true, + certificateChain: undefined, + }); + }); + + it('succeeds if host requires SSL', async () => { + mockPingClient.asInternalUser.ping.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: {} }) + ) + ); + + tlsConnectMock.mockReturnValue(({ + once: jest.fn((event, fn) => { + if (event === 'secureConnect') { + fn(); + } + }), + getPeerCertificate: jest.fn().mockReturnValue({ raw: Buffer.from('cert') }), + destroy: jest.fn(), + } as unknown) as tls.TLSSocket); + + await expect(setupContract.ping('https://localhost:9200')).resolves.toEqual({ + authRequired: true, + certificateChain: [ + expect.objectContaining({ + raw: 'Y2VydA==', + }), + ], + }); + + expect(tlsConnectMock).toHaveBeenCalledWith({ + host: 'localhost', + port: 9200, + rejectUnauthorized: false, + }); + }); + + it('fails if peer certificate cannot be fetched', async () => { + mockPingClient.asInternalUser.ping.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: {} }) + ) + ); + + tlsConnectMock.mockReturnValue(({ + once: jest.fn((event, fn) => { + if (event === 'error') { + fn(new Error('some-message')); + } + }), + } as unknown) as tls.TLSSocket); + + await expect(setupContract.ping('https://localhost:9200')).rejects.toMatchInlineSnapshot( + `[Error: some-message]` + ); + }); + }); }); describe('#stop()', () => { @@ -489,7 +625,7 @@ some weird+ca/with const mockConnectionStatusClient = elasticsearchServiceMock.createCustomClusterClient(); mockElasticsearchPreboot.createClient.mockImplementation((type) => { switch (type) { - case 'ping': + case 'connectionStatus': return mockConnectionStatusClient; default: throw new Error(`Unexpected client type: ${type}`); diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.ts b/src/plugins/interactive_setup/server/elasticsearch_service.ts index c88ac0f0798c9a..edfe203df8e48f 100644 --- a/src/plugins/interactive_setup/server/elasticsearch_service.ts +++ b/src/plugins/interactive_setup/server/elasticsearch_service.ts @@ -19,9 +19,9 @@ import { shareReplay, takeWhile, } from 'rxjs/operators'; +import tls from 'tls'; import type { - ElasticsearchClientConfig, ElasticsearchServicePreboot, ICustomClusterClient, Logger, @@ -29,14 +29,22 @@ import type { } from 'src/core/server'; import { ElasticsearchConnectionStatus } from '../common'; -import { getDetailedErrorMessage } from './errors'; +import type { Certificate, PingResult } from '../common'; +import { getDetailedErrorMessage, getErrorStatusCode } from './errors'; -interface EnrollParameters { +export interface EnrollParameters { apiKey: string; hosts: string[]; caFingerprint: string; } +export interface AuthenticateParameters { + host: string; + username?: string; + password?: string; + caCert?: string; +} + export interface ElasticsearchServiceSetupDeps { /** * Core Elasticsearch service preboot contract; @@ -63,6 +71,16 @@ export interface ElasticsearchServiceSetup { * to point to exactly same Elasticsearch node, potentially available via different network interfaces. */ enroll: (params: EnrollParameters) => Promise; + + /** + * Tries to authenticate specified user with cluster. + */ + authenticate: (params: AuthenticateParameters) => Promise; + + /** + * Tries to connect to specified cluster and fetches certificate chain. + */ + ping: (host: string) => Promise; } /** @@ -76,13 +94,20 @@ export interface EnrollResult { /** * PEM CA certificate for the Elasticsearch HTTP certificates. */ - ca: string; + caCert: string; /** * Service account token for the "elastic/kibana" service account. */ serviceAccountToken: { name: string; value: string }; } +export interface AuthenticateResult { + host: string; + username?: string; + password?: string; + caCert?: string; +} + export class ElasticsearchService { /** * Elasticsearch client used to check Elasticsearch connection status. @@ -95,7 +120,7 @@ export class ElasticsearchService { connectionCheckInterval, }: ElasticsearchServiceSetupDeps): ElasticsearchServiceSetup { const connectionStatusClient = (this.connectionStatusClient = elasticsearch.createClient( - 'ping' + 'connectionStatus' )); return { @@ -120,6 +145,8 @@ export class ElasticsearchService { shareReplay({ refCount: true, bufferSize: 1 }) ), enroll: this.enroll.bind(this, elasticsearch), + authenticate: this.authenticate.bind(this, elasticsearch), + ping: this.ping.bind(this, elasticsearch), }; } @@ -145,11 +172,8 @@ export class ElasticsearchService { private async enroll( elasticsearch: ElasticsearchServicePreboot, { apiKey, hosts, caFingerprint }: EnrollParameters - ): Promise { + ) { const scopeableRequest: ScopeableRequest = { headers: { authorization: `ApiKey ${apiKey}` } }; - const elasticsearchConfig: Partial = { - ssl: { verificationMode: 'none' }, - }; // We should iterate through all provided hosts until we find an accessible one. for (const host of hosts) { @@ -158,9 +182,9 @@ export class ElasticsearchService { ); const enrollClient = elasticsearch.createClient('enroll', { - ...elasticsearchConfig, hosts: [host], caFingerprint, + ssl: { verificationMode: 'none' }, }); let enrollmentResponse; @@ -176,51 +200,51 @@ export class ElasticsearchService { // that enrollment will fail for any other host and we should bail out. if (err instanceof errors.ConnectionError || err instanceof errors.TimeoutError) { this.logger.error( - `Unable to connect to "${host}" host, will proceed to the next host if available: ${getDetailedErrorMessage( + `Unable to connect to host "${host}", will proceed to the next host if available: ${getDetailedErrorMessage( err )}` ); continue; } - this.logger.error(`Failed to enroll with "${host}" host: ${getDetailedErrorMessage(err)}`); + this.logger.error(`Failed to enroll with host "${host}": ${getDetailedErrorMessage(err)}`); throw err; } finally { await enrollClient.close(); } this.logger.debug( - `Successfully enrolled with "${host}" host, token name: ${enrollmentResponse.body.token.name}, CA certificate: ${enrollmentResponse.body.http_ca}` + `Successfully enrolled with host "${host}", token name: ${enrollmentResponse.body.token.name}, CA certificate: ${enrollmentResponse.body.http_ca}` ); - const enrollResult = { + const enrollResult: EnrollResult = { host, - ca: ElasticsearchService.createPemCertificate(enrollmentResponse.body.http_ca), + caCert: ElasticsearchService.createPemCertificate(enrollmentResponse.body.http_ca), serviceAccountToken: enrollmentResponse.body.token, }; - // Now try to use retrieved password and CA certificate to authenticate to this host. + // Now try to use retrieved service account and CA certificate to authenticate to this host. const authenticateClient = elasticsearch.createClient('authenticate', { caFingerprint, hosts: [host], serviceAccountToken: enrollResult.serviceAccountToken.value, - ssl: { certificateAuthorities: [enrollResult.ca] }, + ssl: { certificateAuthorities: [enrollResult.caCert] }, }); this.logger.debug( - `Verifying if "${enrollmentResponse.body.token.name}" token can authenticate to "${host}" host.` + `Verifying if "${enrollmentResponse.body.token.name}" token can authenticate to host "${host}".` ); try { await authenticateClient.asInternalUser.security.authenticate(); this.logger.debug( - `Successfully authenticated "${enrollmentResponse.body.token.name}" token to "${host}" host.` + `Successfully authenticated "${enrollmentResponse.body.token.name}" token to host "${host}".` ); } catch (err) { this.logger.error( `Failed to authenticate "${ enrollmentResponse.body.token.name - }" token to "${host}" host: ${getDetailedErrorMessage(err)}.` + }" token to host "${host}": ${getDetailedErrorMessage(err)}.` ); throw err; } finally { @@ -233,7 +257,114 @@ export class ElasticsearchService { throw new Error('Unable to connect to any of the provided hosts.'); } - private static createPemCertificate(derCaString: string) { + private async authenticate( + elasticsearch: ElasticsearchServicePreboot, + { host, username, password, caCert }: AuthenticateParameters + ) { + const client = elasticsearch.createClient('authenticate', { + hosts: [host], + username, + password, + ssl: caCert ? { certificateAuthorities: [caCert] } : undefined, + }); + + try { + // Using `ping` instead of `authenticate` allows us to verify clusters with both + // security enabled and disabled. + await client.asInternalUser.ping(); + } catch (error) { + this.logger.error( + `Failed to authenticate with host "${host}": ${getDetailedErrorMessage(error)}` + ); + throw error; + } finally { + await client.close(); + } + } + + private async ping(elasticsearch: ElasticsearchServicePreboot, host: string) { + const client = elasticsearch.createClient('ping', { + hosts: [host], + username: '', + password: '', + ssl: { verificationMode: 'none' }, + }); + + let authRequired = false; + try { + await client.asInternalUser.ping(); + } catch (error) { + if ( + error instanceof errors.ConnectionError || + error instanceof errors.TimeoutError || + error instanceof errors.ProductNotSupportedError + ) { + this.logger.error(`Unable to connect to host "${host}": ${getDetailedErrorMessage(error)}`); + throw error; + } + + authRequired = getErrorStatusCode(error) === 401; + } finally { + await client.close(); + } + + let certificateChain: Certificate[] | undefined; + const { protocol, hostname, port } = new URL(host); + if (protocol === 'https:') { + try { + const cert = await ElasticsearchService.fetchPeerCertificate(hostname, port); + certificateChain = ElasticsearchService.flattenCertificateChain(cert).map( + ElasticsearchService.getCertificate + ); + } catch (error) { + this.logger.error( + `Failed to fetch peer certificate from host "${host}": ${getDetailedErrorMessage(error)}` + ); + throw error; + } + } + + return { + authRequired, + certificateChain, + }; + } + + private static fetchPeerCertificate(host: string, port: string | number) { + return new Promise((resolve, reject) => { + const socket = tls.connect({ host, port: Number(port), rejectUnauthorized: false }); + socket.once('secureConnect', () => { + const cert = socket.getPeerCertificate(true); + socket.destroy(); + resolve(cert); + }); + socket.once('error', reject); + }); + } + + private static flattenCertificateChain( + cert: tls.DetailedPeerCertificate, + accumulator: tls.DetailedPeerCertificate[] = [] + ) { + accumulator.push(cert); + if (cert.issuerCertificate && cert.fingerprint256 !== cert.issuerCertificate.fingerprint256) { + ElasticsearchService.flattenCertificateChain(cert.issuerCertificate, accumulator); + } + return accumulator; + } + + private static getCertificate(cert: tls.DetailedPeerCertificate): Certificate { + return { + issuer: cert.issuer, + valid_from: cert.valid_from, + valid_to: cert.valid_to, + subject: cert.subject, + fingerprint256: cert.fingerprint256, + raw: cert.raw.toString('base64'), + }; + } + + public static createPemCertificate(derCaString: string) { // Use `X509Certificate` class once we upgrade to Node v16. return `-----BEGIN CERTIFICATE-----\n${derCaString .replace(/_/g, '/') diff --git a/src/plugins/interactive_setup/server/index.ts b/src/plugins/interactive_setup/server/index.ts index 018c6875b3c048..2f9a2cf3adec0c 100644 --- a/src/plugins/interactive_setup/server/index.ts +++ b/src/plugins/interactive_setup/server/index.ts @@ -14,7 +14,7 @@ import type { } from 'src/core/server'; import { ConfigSchema } from './config'; -import { UserSetupPlugin } from './plugin'; +import { InteractiveSetupPlugin } from './plugin'; export const config: PluginConfigDescriptor> = { schema: ConfigSchema, @@ -22,4 +22,4 @@ export const config: PluginConfigDescriptor> = { export const plugin: PluginInitializer = ( initializerContext: PluginInitializerContext -) => new UserSetupPlugin(initializerContext); +) => new InteractiveSetupPlugin(initializerContext); diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.test.ts b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts index 7ae98157ba1563..7dc119b87f20ae 100644 --- a/src/plugins/interactive_setup/server/kibana_config_writer.test.ts +++ b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts @@ -74,7 +74,7 @@ describe('KibanaConfigWriter', () => { await expect( kibanaConfigWriter.writeConfig({ - ca: 'ca-content', + caCert: 'ca-content', host: '', serviceAccountToken: { name: '', value: '' }, }) @@ -90,7 +90,7 @@ describe('KibanaConfigWriter', () => { await expect( kibanaConfigWriter.writeConfig({ - ca: 'ca-content', + caCert: 'ca-content', host: 'some-host', serviceAccountToken: { name: 'some-token', value: 'some-value' }, }) @@ -103,7 +103,7 @@ describe('KibanaConfigWriter', () => { '/some/path/kibana.yml', ` -# This section was automatically generated during setup (service account token name is "some-token"). +# This section was automatically generated during setup. elasticsearch.hosts: [some-host] elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt] @@ -112,10 +112,10 @@ elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt] ); }); - it('can successfully write CA certificate and elasticsearch config to the disk', async () => { + it('can successfully write CA certificate and elasticsearch config with service token', async () => { await expect( kibanaConfigWriter.writeConfig({ - ca: 'ca-content', + caCert: 'ca-content', host: 'some-host', serviceAccountToken: { name: 'some-token', value: 'some-value' }, }) @@ -128,11 +128,62 @@ elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt] '/some/path/kibana.yml', ` -# This section was automatically generated during setup (service account token name is "some-token"). +# This section was automatically generated during setup. elasticsearch.hosts: [some-host] elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt] +` + ); + }); + + it('can successfully write CA certificate and elasticsearch config with credentials', async () => { + await expect( + kibanaConfigWriter.writeConfig({ + caCert: 'ca-content', + host: 'some-host', + username: 'username', + password: 'password', + }) + ).resolves.toBeUndefined(); + + expect(mockWriteFile).toHaveBeenCalledTimes(1); + expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content'); + expect(mockAppendFile).toHaveBeenCalledTimes(1); + expect(mockAppendFile).toHaveBeenCalledWith( + '/some/path/kibana.yml', + ` + +# This section was automatically generated during setup. +elasticsearch.hosts: [some-host] +elasticsearch.password: password +elasticsearch.username: username +elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt] + +` + ); + }); + + it('can successfully write elasticsearch config without CA certificate', async () => { + await expect( + kibanaConfigWriter.writeConfig({ + host: 'some-host', + username: 'username', + password: 'password', + }) + ).resolves.toBeUndefined(); + + expect(mockWriteFile).not.toHaveBeenCalled(); + expect(mockAppendFile).toHaveBeenCalledTimes(1); + expect(mockAppendFile).toHaveBeenCalledWith( + '/some/path/kibana.yml', + ` + +# This section was automatically generated during setup. +elasticsearch.hosts: [some-host] +elasticsearch.password: password +elasticsearch.username: username + ` ); }); diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.ts b/src/plugins/interactive_setup/server/kibana_config_writer.ts index b3178d9a909bd3..a59aa7640caa6e 100644 --- a/src/plugins/interactive_setup/server/kibana_config_writer.ts +++ b/src/plugins/interactive_setup/server/kibana_config_writer.ts @@ -15,11 +15,19 @@ import type { Logger } from 'src/core/server'; import { getDetailedErrorMessage } from './errors'; -export interface WriteConfigParameters { +export type WriteConfigParameters = { host: string; - ca: string; - serviceAccountToken: { name: string; value: string }; -} + caCert?: string; +} & ( + | { + username: string; + password: string; + } + | { + serviceAccountToken: { name: string; value: string }; + } + | {} +); export class KibanaConfigWriter { constructor(private readonly configPath: string, private readonly logger: Logger) {} @@ -54,31 +62,37 @@ export class KibanaConfigWriter { public async writeConfig(params: WriteConfigParameters) { const caPath = path.join(path.dirname(this.configPath), `ca_${Date.now()}.crt`); - this.logger.debug(`Writing CA certificate to ${caPath}.`); - try { - await fs.writeFile(caPath, params.ca); - this.logger.debug(`Successfully wrote CA certificate to ${caPath}.`); - } catch (err) { - this.logger.error( - `Failed to write CA certificate to ${caPath}: ${getDetailedErrorMessage(err)}.` - ); - throw err; + if (params.caCert) { + this.logger.debug(`Writing CA certificate to ${caPath}.`); + try { + await fs.writeFile(caPath, params.caCert); + this.logger.debug(`Successfully wrote CA certificate to ${caPath}.`); + } catch (err) { + this.logger.error( + `Failed to write CA certificate to ${caPath}: ${getDetailedErrorMessage(err)}.` + ); + throw err; + } + } + + const config: Record = { 'elasticsearch.hosts': [params.host] }; + if ('serviceAccountToken' in params) { + config['elasticsearch.serviceAccountToken'] = params.serviceAccountToken.value; + } else if ('username' in params) { + config['elasticsearch.password'] = params.password; + config['elasticsearch.username'] = params.username; + } + if (params.caCert) { + config['elasticsearch.ssl.certificateAuthorities'] = [caPath]; } this.logger.debug(`Writing Elasticsearch configuration to ${this.configPath}.`); try { await fs.appendFile( this.configPath, - `\n\n# This section was automatically generated during setup (service account token name is "${ - params.serviceAccountToken.name - }").\n${yaml.safeDump( - { - 'elasticsearch.hosts': [params.host], - 'elasticsearch.serviceAccountToken': params.serviceAccountToken.value, - 'elasticsearch.ssl.certificateAuthorities': [caPath], - }, - { flowLevel: 1 } - )}\n` + `\n\n# This section was automatically generated during setup.\n${yaml.safeDump(config, { + flowLevel: 1, + })}\n` ); this.logger.debug(`Successfully wrote Elasticsearch configuration to ${this.configPath}.`); } catch (err) { diff --git a/src/plugins/interactive_setup/server/plugin.ts b/src/plugins/interactive_setup/server/plugin.ts index 06ece32ba9c4e3..91a151e17b697b 100644 --- a/src/plugins/interactive_setup/server/plugin.ts +++ b/src/plugins/interactive_setup/server/plugin.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import chalk from 'chalk'; import type { Subscription } from 'rxjs'; import type { TypeOf } from '@kbn/config-schema'; @@ -17,13 +18,11 @@ import { ElasticsearchService } from './elasticsearch_service'; import { KibanaConfigWriter } from './kibana_config_writer'; import { defineRoutes } from './routes'; -export class UserSetupPlugin implements PrebootPlugin { +export class InteractiveSetupPlugin implements PrebootPlugin { readonly #logger: Logger; + readonly #elasticsearch: ElasticsearchService; #elasticsearchConnectionStatusSubscription?: Subscription; - readonly #elasticsearch = new ElasticsearchService( - this.initializerContext.logger.get('elasticsearch') - ); #configSubscription?: Subscription; #config?: ConfigType; @@ -36,6 +35,9 @@ export class UserSetupPlugin implements PrebootPlugin { constructor(private readonly initializerContext: PluginInitializerContext) { this.#logger = this.initializerContext.logger.get(); + this.#elasticsearch = new ElasticsearchService( + this.initializerContext.logger.get('elasticsearch') + ); } public setup(core: CorePreboot) { @@ -90,6 +92,14 @@ export class UserSetupPlugin implements PrebootPlugin { this.#logger.debug( 'Starting interactive setup mode since Kibana cannot to connect to Elasticsearch at http://localhost:9200.' ); + const serverInfo = core.http.getServerInfo(); + const url = `${serverInfo.protocol}://${serverInfo.hostname}:${serverInfo.port}`; + this.#logger.info(` + +${chalk.whiteBright.bold(`${chalk.cyanBright('i')} Kibana has not been configured.`)} + +Go to ${chalk.cyanBright.underline(url)} to get started. +`); } } ); diff --git a/src/plugins/interactive_setup/server/routes/configure.test.ts b/src/plugins/interactive_setup/server/routes/configure.test.ts new file mode 100644 index 00000000000000..d6b7404fce5161 --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/configure.test.ts @@ -0,0 +1,276 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors } from '@elastic/elasticsearch'; + +import type { ObjectType } from '@kbn/config-schema'; +import type { IRouter, RequestHandler, RequestHandlerContext, RouteConfig } from 'src/core/server'; +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { ElasticsearchConnectionStatus } from '../../common'; +import { interactiveSetupMock } from '../mocks'; +import { defineConfigureRoute } from './configure'; +import { routeDefinitionParamsMock } from './index.mock'; + +describe('Configure routes', () => { + let router: jest.Mocked; + let mockRouteParams: ReturnType; + let mockContext: RequestHandlerContext; + beforeEach(() => { + mockRouteParams = routeDefinitionParamsMock.create(); + router = mockRouteParams.router; + + mockContext = ({} as unknown) as RequestHandlerContext; + + defineConfigureRoute(mockRouteParams); + }); + + describe('#configure', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + + beforeEach(() => { + const [configureRouteConfig, configureRouteHandler] = router.post.mock.calls.find( + ([{ path }]) => path === '/internal/interactive_setup/configure' + )!; + + routeConfig = configureRouteConfig; + routeHandler = configureRouteHandler; + }); + + it('correctly defines route.', () => { + expect(routeConfig.options).toEqual({ authRequired: false }); + + const bodySchema = (routeConfig.validate as any).body as ObjectType; + expect(() => bodySchema.validate({})).toThrowErrorMatchingInlineSnapshot( + `"[host]: expected value of type [string] but got [undefined]."` + ); + expect(() => bodySchema.validate({ host: '' })).toThrowErrorMatchingInlineSnapshot( + `"[host]: \\"host\\" is not allowed to be empty"` + ); + expect(() => + bodySchema.validate({ host: 'localhost:9200' }) + ).toThrowErrorMatchingInlineSnapshot(`"[host]: expected URI with scheme [http|https]."`); + expect(() => bodySchema.validate({ host: 'http://localhost:9200' })).not.toThrowError(); + expect(() => + bodySchema.validate({ host: 'http://localhost:9200', username: 'elastic' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[username]: value of \\"elastic\\" is forbidden. This is a superuser account that can obfuscate privilege-related issues. You should use the \\"kibana_system\\" user instead."` + ); + expect(() => + bodySchema.validate({ host: 'http://localhost:9200', username: 'kibana_system' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[password]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodySchema.validate({ host: 'http://localhost:9200', password: 'password' }) + ).toThrowErrorMatchingInlineSnapshot(`"[password]: a value wasn't expected to be present"`); + expect(() => + bodySchema.validate({ + host: 'http://localhost:9200', + username: 'kibana_system', + password: '', + }) + ).not.toThrowError(); + expect(() => + bodySchema.validate({ host: 'https://localhost:9200' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[caCert]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodySchema.validate({ host: 'https://localhost:9200', caCert: 'der' }) + ).not.toThrowError(); + }); + + it('fails if setup is not on hold.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(false); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host1' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 400, + options: { body: 'Cannot process request outside of preboot stage.' }, + payload: 'Cannot process request outside of preboot stage.', + }); + + expect(mockRouteParams.elasticsearch.authenticate).not.toHaveBeenCalled(); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if Elasticsearch connection is already configured.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.Configured + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host1' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 400, + options: { + body: { + message: 'Elasticsearch connection is already configured.', + attributes: { type: 'elasticsearch_connection_configured' }, + }, + }, + payload: { + message: 'Elasticsearch connection is already configured.', + attributes: { type: 'elasticsearch_connection_configured' }, + }, + }); + + expect(mockRouteParams.elasticsearch.authenticate).not.toHaveBeenCalled(); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if Kibana config is not writable.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(false); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host1' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 500, + options: { + body: { + message: 'Kibana process does not have enough permissions to write to config file.', + attributes: { type: 'kibana_config_not_writable' }, + }, + statusCode: 500, + }, + payload: { + message: 'Kibana process does not have enough permissions to write to config file.', + attributes: { type: 'kibana_config_not_writable' }, + }, + }); + + expect(mockRouteParams.elasticsearch.authenticate).not.toHaveBeenCalled(); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if authenticate call fails.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(true); + mockRouteParams.elasticsearch.authenticate.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ + statusCode: 401, + body: { message: 'some-secret-message' }, + }) + ) + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host1' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 500, + options: { + body: { message: 'Failed to configure.', attributes: { type: 'configure_failure' } }, + statusCode: 500, + }, + payload: { message: 'Failed to configure.', attributes: { type: 'configure_failure' } }, + }); + + expect(mockRouteParams.elasticsearch.authenticate).toHaveBeenCalledTimes(1); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if cannot write configuration to the disk.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(true); + mockRouteParams.kibanaConfigWriter.writeConfig.mockRejectedValue( + new Error('Some error with sensitive path') + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host1' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 500, + options: { + body: { + message: 'Failed to save configuration.', + attributes: { type: 'kibana_config_failure' }, + }, + statusCode: 500, + }, + payload: { + message: 'Failed to save configuration.', + attributes: { type: 'kibana_config_failure' }, + }, + }); + + expect(mockRouteParams.elasticsearch.authenticate).toHaveBeenCalledTimes(1); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).toHaveBeenCalledTimes(1); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('can successfully authenticate and save configuration to the disk.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(true); + mockRouteParams.kibanaConfigWriter.writeConfig.mockResolvedValue(); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host', username: 'username', password: 'password', caCert: 'der' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 204, + options: {}, + payload: undefined, + }); + + expect(mockRouteParams.elasticsearch.authenticate).toHaveBeenCalledTimes(1); + expect(mockRouteParams.elasticsearch.authenticate).toHaveBeenCalledWith({ + host: 'host', + username: 'username', + password: 'password', + caCert: '-----BEGIN CERTIFICATE-----\nder\n-----END CERTIFICATE-----\n', + }); + + expect(mockRouteParams.kibanaConfigWriter.writeConfig).toHaveBeenCalledTimes(1); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).toHaveBeenCalledWith({ + host: 'host', + username: 'username', + password: 'password', + caCert: '-----BEGIN CERTIFICATE-----\nder\n-----END CERTIFICATE-----\n', + }); + + expect(mockRouteParams.preboot.completeSetup).toHaveBeenCalledTimes(1); + expect(mockRouteParams.preboot.completeSetup).toHaveBeenCalledWith({ + shouldReloadConfig: true, + }); + }); + }); +}); diff --git a/src/plugins/interactive_setup/server/routes/configure.ts b/src/plugins/interactive_setup/server/routes/configure.ts new file mode 100644 index 00000000000000..a34af0296ea047 --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/configure.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { first } from 'rxjs/operators'; + +import { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '.'; +import { ElasticsearchConnectionStatus } from '../../common'; +import type { AuthenticateParameters } from '../elasticsearch_service'; +import { ElasticsearchService } from '../elasticsearch_service'; +import type { WriteConfigParameters } from '../kibana_config_writer'; + +export function defineConfigureRoute({ + router, + logger, + kibanaConfigWriter, + elasticsearch, + preboot, +}: RouteDefinitionParams) { + router.post( + { + path: '/internal/interactive_setup/configure', + validate: { + query: schema.object({ + code: schema.maybe(schema.string()), + }), + body: schema.object({ + host: schema.uri({ scheme: ['http', 'https'] }), + username: schema.maybe( + schema.string({ + validate: (value: string) => { + if (value === 'elastic') { + return ( + 'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' + + 'privilege-related issues. You should use the "kibana_system" user instead.' + ); + } + }, + }) + ), + password: schema.conditional( + schema.siblingRef('username'), + schema.string(), + schema.string(), + schema.never() + ), + caCert: schema.conditional( + schema.siblingRef('host'), + schema.uri({ scheme: 'https' }), + schema.string(), + schema.never() + ), + }), + }, + options: { authRequired: false }, + }, + async (context, request, response) => { + if (!preboot.isSetupOnHold()) { + logger.error(`Invalid request to [path=${request.url.pathname}] outside of preboot stage`); + return response.badRequest({ body: 'Cannot process request outside of preboot stage.' }); + } + + const connectionStatus = await elasticsearch.connectionStatus$.pipe(first()).toPromise(); + if (connectionStatus === ElasticsearchConnectionStatus.Configured) { + logger.error( + `Invalid request to [path=${request.url.pathname}], Elasticsearch connection is already configured.` + ); + return response.badRequest({ + body: { + message: 'Elasticsearch connection is already configured.', + attributes: { type: 'elasticsearch_connection_configured' }, + }, + }); + } + + // The most probable misconfiguration case is when Kibana process isn't allowed to write to the + // Kibana configuration file. We'll still have to handle possible filesystem access errors + // when we actually write to the disk, but this preliminary check helps us to avoid unnecessary + // enrollment call and communicate that to the user early. + const isConfigWritable = await kibanaConfigWriter.isConfigWritable(); + if (!isConfigWritable) { + logger.error('Kibana process does not have enough permissions to write to config file'); + return response.customError({ + statusCode: 500, + body: { + message: 'Kibana process does not have enough permissions to write to config file.', + attributes: { type: 'kibana_config_not_writable' }, + }, + }); + } + + const configToWrite: WriteConfigParameters & AuthenticateParameters = { + host: request.body.host, + username: request.body.username, + password: request.body.password, + caCert: request.body.caCert + ? ElasticsearchService.createPemCertificate(request.body.caCert) + : undefined, + }; + + try { + await elasticsearch.authenticate(configToWrite); + } catch { + // For security reasons, we shouldn't leak to the user whether Elasticsearch node couldn't process enrollment + // request or we just couldn't connect to any of the provided hosts. + return response.customError({ + statusCode: 500, + body: { message: 'Failed to configure.', attributes: { type: 'configure_failure' } }, + }); + } + + try { + await kibanaConfigWriter.writeConfig(configToWrite); + } catch { + // For security reasons, we shouldn't leak any filesystem related errors. + return response.customError({ + statusCode: 500, + body: { + message: 'Failed to save configuration.', + attributes: { type: 'kibana_config_failure' }, + }, + }); + } + + preboot.completeSetup({ shouldReloadConfig: true }); + + return response.noContent(); + } + ); +} diff --git a/src/plugins/interactive_setup/server/routes/enroll.ts b/src/plugins/interactive_setup/server/routes/enroll.ts index 6a2da28787a4da..41291246802e65 100644 --- a/src/plugins/interactive_setup/server/routes/enroll.ts +++ b/src/plugins/interactive_setup/server/routes/enroll.ts @@ -12,6 +12,7 @@ import { schema } from '@kbn/config-schema'; import { ElasticsearchConnectionStatus } from '../../common'; import type { EnrollResult } from '../elasticsearch_service'; +import type { WriteConfigParameters } from '../kibana_config_writer'; import type { RouteDefinitionParams } from './'; /** @@ -81,9 +82,9 @@ export function defineEnrollRoutes({ .match(/.{1,2}/g) ?.join(':') ?? ''; - let enrollResult: EnrollResult; + let configToWrite: WriteConfigParameters & EnrollResult; try { - enrollResult = await elasticsearch.enroll({ + configToWrite = await elasticsearch.enroll({ apiKey: request.body.apiKey, hosts: request.body.hosts, caFingerprint: colonFormattedCaFingerprint, @@ -98,7 +99,7 @@ export function defineEnrollRoutes({ } try { - await kibanaConfigWriter.writeConfig(enrollResult); + await kibanaConfigWriter.writeConfig(configToWrite); } catch { // For security reasons, we shouldn't leak any filesystem related errors. return response.customError({ diff --git a/src/plugins/interactive_setup/server/routes/index.ts b/src/plugins/interactive_setup/server/routes/index.ts index 752c5828ecb59f..75c383176e7e9d 100644 --- a/src/plugins/interactive_setup/server/routes/index.ts +++ b/src/plugins/interactive_setup/server/routes/index.ts @@ -12,7 +12,9 @@ import type { IBasePath, IRouter, Logger, PrebootServicePreboot } from 'src/core import type { ConfigType } from '../config'; import type { ElasticsearchServiceSetup } from '../elasticsearch_service'; import type { KibanaConfigWriter } from '../kibana_config_writer'; +import { defineConfigureRoute } from './configure'; import { defineEnrollRoutes } from './enroll'; +import { definePingRoute } from './ping'; /** * Describes parameters used to define HTTP routes. @@ -31,4 +33,6 @@ export interface RouteDefinitionParams { export function defineRoutes(params: RouteDefinitionParams) { defineEnrollRoutes(params); + defineConfigureRoute(params); + definePingRoute(params); } diff --git a/src/plugins/interactive_setup/server/routes/ping.test.ts b/src/plugins/interactive_setup/server/routes/ping.test.ts new file mode 100644 index 00000000000000..295ad3b612992f --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/ping.test.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors } from '@elastic/elasticsearch'; + +import type { ObjectType } from '@kbn/config-schema'; +import type { IRouter, RequestHandler, RequestHandlerContext, RouteConfig } from 'src/core/server'; +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { interactiveSetupMock } from '../mocks'; +import { routeDefinitionParamsMock } from './index.mock'; +import { definePingRoute } from './ping'; + +describe('Configure routes', () => { + let router: jest.Mocked; + let mockRouteParams: ReturnType; + let mockContext: RequestHandlerContext; + beforeEach(() => { + mockRouteParams = routeDefinitionParamsMock.create(); + router = mockRouteParams.router; + + mockContext = ({} as unknown) as RequestHandlerContext; + + definePingRoute(mockRouteParams); + }); + + describe('#ping', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + + beforeEach(() => { + const [configureRouteConfig, configureRouteHandler] = router.post.mock.calls.find( + ([{ path }]) => path === '/internal/interactive_setup/ping' + )!; + + routeConfig = configureRouteConfig; + routeHandler = configureRouteHandler; + }); + + it('correctly defines route.', () => { + expect(routeConfig.options).toEqual({ authRequired: false }); + + const bodySchema = (routeConfig.validate as any).body as ObjectType; + expect(() => bodySchema.validate({})).toThrowErrorMatchingInlineSnapshot( + `"[host]: expected value of type [string] but got [undefined]."` + ); + expect(() => bodySchema.validate({ host: '' })).toThrowErrorMatchingInlineSnapshot( + `"[host]: \\"host\\" is not allowed to be empty"` + ); + expect(() => + bodySchema.validate({ host: 'localhost:9200' }) + ).toThrowErrorMatchingInlineSnapshot(`"[host]: expected URI with scheme [http|https]."`); + expect(() => bodySchema.validate({ host: 'http://localhost:9200' })).not.toThrowError(); + }); + + it('fails if setup is not on hold.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(false); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 400, + options: { body: 'Cannot process request outside of preboot stage.' }, + payload: 'Cannot process request outside of preboot stage.', + }); + + expect(mockRouteParams.elasticsearch.authenticate).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if ping call fails.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.ping.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ + statusCode: 401, + body: { message: 'some-secret-message' }, + }) + ) + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 500, + options: { + body: { message: 'Failed to ping cluster.', attributes: { type: 'ping_failure' } }, + statusCode: 500, + }, + payload: { message: 'Failed to ping cluster.', attributes: { type: 'ping_failure' } }, + }); + + expect(mockRouteParams.elasticsearch.ping).toHaveBeenCalledTimes(1); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('can successfully ping.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { host: 'host' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 200, + options: {}, + payload: undefined, + }); + + expect(mockRouteParams.elasticsearch.ping).toHaveBeenCalledTimes(1); + expect(mockRouteParams.elasticsearch.ping).toHaveBeenCalledWith('host'); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/plugins/interactive_setup/server/routes/ping.ts b/src/plugins/interactive_setup/server/routes/ping.ts new file mode 100644 index 00000000000000..50ed7514bab699 --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/ping.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '.'; +import type { PingResult } from '../../common/types'; + +export function definePingRoute({ router, logger, elasticsearch, preboot }: RouteDefinitionParams) { + router.post( + { + path: '/internal/interactive_setup/ping', + validate: { + body: schema.object({ + host: schema.uri({ scheme: ['http', 'https'] }), + }), + }, + options: { authRequired: false }, + }, + async (context, request, response) => { + if (!preboot.isSetupOnHold()) { + logger.error(`Invalid request to [path=${request.url.pathname}] outside of preboot stage`); + return response.badRequest({ body: 'Cannot process request outside of preboot stage.' }); + } + + let result: PingResult; + try { + result = await elasticsearch.ping(request.body.host); + } catch { + return response.customError({ + statusCode: 500, + body: { message: 'Failed to ping cluster.', attributes: { type: 'ping_failure' } }, + }); + } + + return response.ok({ body: result }); + } + ); +} diff --git a/src/plugins/kibana_overview/kibana.json b/src/plugins/kibana_overview/kibana.json index 1b052a239ba314..2924e71ed8282a 100644 --- a/src/plugins/kibana_overview/kibana.json +++ b/src/plugins/kibana_overview/kibana.json @@ -7,7 +7,7 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["navigation", "data", "home"], + "requiredPlugins": ["navigation", "data", "home", "share"], "optionalPlugins": ["newsfeed", "usageCollection"], "requiredBundles": ["kibanaReact", "newsfeed"] } diff --git a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap index b00ad256511702..6da2f95fa394dc 100644 --- a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap +++ b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap @@ -225,6 +225,9 @@ exports[`Overview render 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], @@ -254,6 +257,10 @@ exports[`Overview render 1`] = ` ], ], "results": Array [ + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -525,6 +532,9 @@ exports[`Overview without features 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], @@ -552,9 +562,15 @@ exports[`Overview without features 1`] = ` Array [ "/plugins/kibanaReact/assets/solutions_solution_4.svg", ], + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], @@ -584,6 +600,10 @@ exports[`Overview without features 1`] = ` ], ], "results": Array [ + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -620,10 +640,18 @@ exports[`Overview without features 1`] = ` "type": "return", "value": "/plugins/kibanaReact/assets/solutions_solution_4.svg", }, + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", }, + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -772,6 +800,9 @@ exports[`Overview without solutions 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], @@ -799,11 +830,18 @@ exports[`Overview without solutions 1`] = ` Array [ "/plugins/kibanaReact/assets/solutions_solution_4.svg", ], + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], ], "results": Array [ + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -840,6 +878,10 @@ exports[`Overview without solutions 1`] = ` "type": "return", "value": "/plugins/kibanaReact/assets/solutions_solution_4.svg", }, + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -855,6 +897,9 @@ exports[`Overview without solutions 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], @@ -882,11 +927,18 @@ exports[`Overview without solutions 1`] = ` Array [ "/plugins/kibanaReact/assets/solutions_solution_4.svg", ], + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], ], "results": Array [ + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -923,6 +975,10 @@ exports[`Overview without solutions 1`] = ` "type": "return", "value": "/plugins/kibanaReact/assets/solutions_solution_4.svg", }, + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -944,6 +1000,9 @@ exports[`Overview without solutions 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], @@ -971,11 +1030,18 @@ exports[`Overview without solutions 1`] = ` Array [ "/plugins/kibanaReact/assets/solutions_solution_4.svg", ], + Array [ + "/app/home#/tutorial_directory", + ], Array [ "home#/tutorial_directory", ], ], "results": Array [ + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", @@ -1012,6 +1078,10 @@ exports[`Overview without solutions 1`] = ` "type": "return", "value": "/plugins/kibanaReact/assets/solutions_solution_4.svg", }, + Object { + "type": "return", + "value": "/app/home#/tutorial_directory", + }, Object { "type": "return", "value": "home#/tutorial_directory", diff --git a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx index d7e922a40d6f01..ca9b404ed98103 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx @@ -17,6 +17,7 @@ jest.mock('../../../../../../src/plugins/kibana_react/public', () => ({ services: { http: { basePath: { prepend: jest.fn((path: string) => (path ? path : 'path')) } }, data: { indexPatterns: {} }, + share: { url: { locators: { get: () => ({ useUrl: () => '' }) } } }, uiSettings: { get: jest.fn() }, docLinks: { links: { diff --git a/src/plugins/kibana_overview/public/components/overview/overview.tsx b/src/plugins/kibana_overview/public/components/overview/overview.tsx index 68a469c753ce96..b49be0670b5824 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.tsx @@ -54,12 +54,19 @@ interface Props { export const Overview: FC = ({ newsFetchResult, solutions, features }) => { const [isNewKibanaInstance, setNewKibanaInstance] = useState(false); const { - services: { http, docLinks, data, uiSettings, application }, + services: { http, docLinks, data, share, uiSettings, application }, } = useKibana(); const addBasePath = http.basePath.prepend; const indexPatternService = data.indexPatterns; const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + // Home does not have a locator implemented, so hard-code it here. + const addDataHref = addBasePath('/app/home#/tutorial_directory'); + const devToolsHref = share.url.locators.get('CONSOLE_APP_LOCATOR')?.useUrl({}); + const managementHref = share.url.locators + .get('MANAGEMENT_APP_LOCATOR') + ?.useUrl({ sectionId: '' }); + const getFeaturesByCategory = (category: string) => features .filter((feature) => feature.showOnHomePage && feature.category === category) @@ -135,9 +142,13 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => iconType: 'logoKibana', pageTitle: , rightSideItems: overviewPageActions({ - addBasePath, + addDataHref, application, + devToolsHref, hidden: isNewKibanaInstance, + managementHref, + showDevToolsLink: !!devTools, + showManagementLink: !!manageDataFeatures, }), }} noDataConfig={isNewKibanaInstance ? noDataConfig : undefined} diff --git a/src/plugins/kibana_overview/public/types.ts b/src/plugins/kibana_overview/public/types.ts index 0459650a507423..032706c9e76a04 100644 --- a/src/plugins/kibana_overview/public/types.ts +++ b/src/plugins/kibana_overview/public/types.ts @@ -11,6 +11,7 @@ import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { NewsfeedPublicPluginStart } from 'src/plugins/newsfeed/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { SharePluginStart } from '../../share/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface KibanaOverviewPluginSetup {} @@ -28,4 +29,5 @@ export interface AppPluginStartDependencies { data: DataPublicPluginStart; navigation: NavigationPublicPluginStart; newsfeed?: NewsfeedPublicPluginStart; + share: SharePluginStart; } diff --git a/src/plugins/kibana_react/public/overview_page/overview_page_actions/__snapshots__/overview_page_actions.test.tsx.snap b/src/plugins/kibana_react/public/overview_page/overview_page_actions/__snapshots__/overview_page_actions.test.tsx.snap index 91e03bcd7cf14c..7492f9e1b620f2 100644 --- a/src/plugins/kibana_react/public/overview_page/overview_page_actions/__snapshots__/overview_page_actions.test.tsx.snap +++ b/src/plugins/kibana_react/public/overview_page/overview_page_actions/__snapshots__/overview_page_actions.test.tsx.snap @@ -18,56 +18,12 @@ Array [ className="kbnOverviewPageHeader__actionButton" data-test-subj="homeAddData" flush="both" - href="/app/home#/tutorial_directory" + href="" iconType="plusInCircle" > Add data , - - - Manage - - , - - - Dev tools - - , ] `; @@ -91,7 +47,7 @@ Array [ className="kbnOverviewPageHeader__actionButton" data-test-subj="homeAddData" flush="both" - href="/app/home#/tutorial_directory" + href="" iconType="plusInCircle" > Add data diff --git a/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.test.tsx b/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.test.tsx index f324d56196144e..34abf9439c1c44 100644 --- a/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.test.tsx +++ b/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.test.tsx @@ -15,8 +15,6 @@ jest.mock('../../app_links', () => ({ afterAll(() => jest.clearAllMocks()); -const addBasePathMock = jest.fn((path: string) => (path ? path : 'path')); - const applicationStartMock = ({ capabilities: { navLinks: { management: true, dev_tools: true } }, } as unknown) as ApplicationStart; @@ -24,7 +22,7 @@ const applicationStartMock = ({ describe('overviewPageActions', () => { test('only add data button', () => { const array = overviewPageActions({ - addBasePath: addBasePathMock, + addDataHref: '', application: applicationStartMock, }); expect(array).toMatchSnapshot(); @@ -32,7 +30,7 @@ describe('overviewPageActions', () => { test('all buttons', () => { const array = overviewPageActions({ - addBasePath: addBasePathMock, + addDataHref: '', application: applicationStartMock, showDevToolsLink: true, showManagementLink: true, @@ -42,7 +40,7 @@ describe('overviewPageActions', () => { test('no buttons', () => { const array = overviewPageActions({ - addBasePath: addBasePathMock, + addDataHref: '', application: applicationStartMock, hidden: true, }); diff --git a/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.tsx b/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.tsx index 186becf757db3e..5903a51ae31707 100644 --- a/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.tsx +++ b/src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.tsx @@ -13,17 +13,21 @@ import { ApplicationStart } from 'kibana/public'; import { RedirectAppLinks } from '../../app_links'; interface Props { - addBasePath: (path: string) => string; + addDataHref: string; application: ApplicationStart; + devToolsHref?: string; hidden?: boolean; + managementHref?: string; showDevToolsLink?: boolean; showManagementLink?: boolean; } export const overviewPageActions = ({ - addBasePath, + addDataHref, application, + devToolsHref, hidden, + managementHref, showDevToolsLink, showManagementLink, }: Props) => { @@ -38,7 +42,7 @@ export const overviewPageActions = ({ data-test-subj="homeAddData" className="kbnOverviewPageHeader__actionButton" flush="both" - href={addBasePath('/app/home#/tutorial_directory')} + href={addDataHref} iconType="plusInCircle" > {i18n.translate('kibana-react.kbnOverviewPageHeader.addDataButtonLabel', { @@ -49,14 +53,14 @@ export const overviewPageActions = ({ ); const actionStackManagement = - showManagementLink && isManagementEnabled ? ( + managementHref && showManagementLink && isManagementEnabled ? ( {i18n.translate('kibana-react.kbnOverviewPageHeader.stackManagementButtonLabel', { defaultMessage: 'Manage', @@ -66,14 +70,14 @@ export const overviewPageActions = ({ ) : null; const actionDevTools = - showDevToolsLink && isDevToolsEnabled ? ( + devToolsHref && showDevToolsLink && isDevToolsEnabled ? ( {i18n.translate('kibana-react.kbnOverviewPageHeader.devToolsButtonLabel', { defaultMessage: 'Dev tools', diff --git a/src/plugins/maps_ems/config.ts b/src/plugins/maps_ems/config.ts index 1deff36a10e450..ed1648ebbe8bbb 100644 --- a/src/plugins/maps_ems/config.ts +++ b/src/plugins/maps_ems/config.ts @@ -36,33 +36,7 @@ export const tilemapConfigSchema = schema.object({ options: tileMapConfigOptionsSchema, }); -const layerConfigSchema = schema.object({ - url: schema.string(), - format: schema.object({ - type: schema.string({ defaultValue: 'geojson' }), - }), - meta: schema.object({ - feature_collection_path: schema.string({ defaultValue: 'data' }), - }), - attribution: schema.string(), - name: schema.string(), - fields: schema.arrayOf( - schema.object({ - name: schema.string(), - description: schema.string(), - }) - ), -}); - -export type LayerConfig = TypeOf; - -const regionmapConfigSchema = schema.object({ - includeElasticMapsService: schema.boolean({ defaultValue: true }), - layers: schema.arrayOf(layerConfigSchema, { defaultValue: [] }), -}); - export const emsConfigSchema = schema.object({ - regionmap: regionmapConfigSchema, tilemap: tilemapConfigSchema, includeElasticMapsService: schema.boolean({ defaultValue: true }), proxyElasticMapsServiceInMaps: schema.boolean({ defaultValue: false }), diff --git a/src/plugins/maps_ems/public/index.ts b/src/plugins/maps_ems/public/index.ts index 0dfe808ca6f628..1784613faa94d7 100644 --- a/src/plugins/maps_ems/public/index.ts +++ b/src/plugins/maps_ems/public/index.ts @@ -24,7 +24,7 @@ export function plugin(initializerContext: PluginInitializerContext) { return new MapsEmsPlugin(initializerContext); } -export type { MapsEmsConfig, LayerConfig } from '../config'; +export type { MapsEmsConfig } from '../config'; export * from '../common'; diff --git a/src/plugins/maps_ems/server/index.ts b/src/plugins/maps_ems/server/index.ts index 0bfa68c8edd779..b8b84d222b9585 100644 --- a/src/plugins/maps_ems/server/index.ts +++ b/src/plugins/maps_ems/server/index.ts @@ -16,7 +16,6 @@ import { MapsEmsConfig, emsConfigSchema } from '../config'; export const config: PluginConfigDescriptor = { exposeToBrowser: { - regionmap: true, tilemap: true, includeElasticMapsService: true, proxyElasticMapsServiceInMaps: true, diff --git a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts index b96c81819c39ce..95bd41745553d9 100644 --- a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts @@ -17,7 +17,7 @@ import { IndexPatternSpec, } from '../../../data/public'; import { FailedImport } from './process_import_response'; -import { DuplicateIndexPatternError, IndexPattern } from '../../../data/public'; +import { DuplicateDataViewError, IndexPattern } from '../../../data/public'; type SavedObjectsRawDoc = Record; @@ -89,7 +89,7 @@ async function importIndexPattern( try { emptyPattern = await indexPatterns.createAndSave(indexPatternSpec, overwriteAll, true); } catch (err) { - if (err instanceof DuplicateIndexPatternError) { + if (err instanceof DuplicateDataViewError) { // We can override and we want to prompt for confirmation const isConfirmed = await openConfirm( i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteLabel', { diff --git a/src/plugins/share/common/url_service/locators/README.md b/src/plugins/share/common/url_service/locators/README.md index 1ca990f92d7b87..c3e0721e63f19e 100644 --- a/src/plugins/share/common/url_service/locators/README.md +++ b/src/plugins/share/common/url_service/locators/README.md @@ -65,7 +65,7 @@ plugins.share.url.locators.create({ }; }, - migrations = { + migrations: { '7.20.0': ({productId, ...rest}) => { return { id: productId, @@ -76,20 +76,20 @@ plugins.share.url.locators.create({ }); ``` -The migration version should correspond to Kibana relase when the chagne was +The migration version should correspond to Kibana relase when the change was introduced. It is the responsibility of the *consumer* to make sure they migrate their stored parameters using the provided migration function to the latest version. Migrations versions are ordered by semver. As a consumer, -if persist somewhere a locator parameters object, you also want to store +if you persist somewhere a locator parameters object, you also want to store the version of that object, so later you know from starting from which version you need to execute migrations. ## Consumer Usage -Consumers of the Locators service can use the locators to generate deeps links -into Kibana apps, or navigate to the apps while passing to the destination -app the *location state*. +Consumers of the Locators service can use the locators to generate deep links +into Kibana apps, or navigate to the apps while passing the *location state* to +the destination app. First you will need to get hold of the *locator* for the app you want to navigate to. @@ -115,7 +115,7 @@ class MyPlugin { } ``` -Once you have the locator, you can use it to navigate to some kibana app: +Once you have the locator, you can use it to navigate to some Kibana app: ```ts await locator.navigate({ @@ -136,10 +136,10 @@ const url = await locator.getUrl({ **As a consumer, you should not persist the resulting URL string!** As soon as you do, you have lost your migration options. Instead you should -store the ID, version and params of your locator. This will let you +store the ID, version, and params of your locator. This will let you re-create the migrated URL later. -If, as a consumer, you store the ID, version and params of the locator, you +If, as a consumer, you store the ID, version, and params of the locator, you should use the migration functions provided by the locator when migrating between Kibana versions. diff --git a/src/plugins/vis_type_table/server/usage_collector/get_stats.test.ts b/src/plugins/vis_type_table/server/usage_collector/get_stats.test.ts index 3f8f4289321b58..76f067e3a23d7a 100644 --- a/src/plugins/vis_type_table/server/usage_collector/get_stats.test.ts +++ b/src/plugins/vis_type_table/server/usage_collector/get_stats.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectsClientContract } from 'kibana/server'; import { getStats } from './get_stats'; +import type { SavedObjectsClientContract } from '../../../../core/server'; const mockVisualizations = { saved_objects: [ @@ -42,15 +42,23 @@ const mockVisualizations = { describe('vis_type_table getStats', () => { const mockSoClient = ({ - find: jest.fn().mockResolvedValue(mockVisualizations), + createPointInTimeFinder: jest.fn().mockResolvedValue({ + close: jest.fn(), + find: function* asyncGenerator() { + yield mockVisualizations; + }, + }), } as unknown) as SavedObjectsClientContract; test('Returns stats from saved objects for table vis only', async () => { const result = await getStats(mockSoClient); - expect(mockSoClient.find).toHaveBeenCalledWith({ + + expect(mockSoClient.createPointInTimeFinder).toHaveBeenCalledWith({ type: 'visualization', - perPage: 10000, + perPage: 1000, + namespaces: ['*'], }); + expect(result).toEqual({ total: 4, total_split: 3, diff --git a/src/plugins/vis_type_table/server/usage_collector/get_stats.ts b/src/plugins/vis_type_table/server/usage_collector/get_stats.ts index 6fdb555c863289..ef948c2d7b70b9 100644 --- a/src/plugins/vis_type_table/server/usage_collector/get_stats.ts +++ b/src/plugins/vis_type_table/server/usage_collector/get_stats.ts @@ -6,12 +6,14 @@ * Side Public License, v 1. */ -import { ISavedObjectsRepository, SavedObjectsClientContract } from 'kibana/server'; -import { - SavedVisState, - VisualizationSavedObjectAttributes, -} from 'src/plugins/visualizations/common'; -import { TableVisParams, VIS_TYPE_TABLE } from '../../common'; +import { VIS_TYPE_TABLE } from '../../common'; + +import type { + ISavedObjectsRepository, + SavedObjectsClientContract, + SavedObjectsFindResult, +} from '../../../../core/server'; +import type { SavedVisState } from '../../../visualizations/common'; export interface VisTypeTableUsage { /** @@ -44,17 +46,14 @@ export interface VisTypeTableUsage { export async function getStats( soClient: SavedObjectsClientContract | ISavedObjectsRepository ): Promise { - const visualizations = await soClient.find({ + const finder = await soClient.createPointInTimeFinder({ type: 'visualization', - perPage: 10000, + perPage: 1000, + namespaces: ['*'], }); - const tableVisualizations = visualizations.saved_objects - .map>(({ attributes }) => JSON.parse(attributes.visState)) - .filter(({ type }) => type === VIS_TYPE_TABLE); - - const defaultStats = { - total: tableVisualizations.length, + const stats: VisTypeTableUsage = { + total: 0, total_split: 0, split_columns: { total: 0, @@ -66,20 +65,39 @@ export async function getStats( }, }; - return tableVisualizations.reduce((acc, { aggs, params }) => { + const doTelemetry = ({ aggs, params }: SavedVisState) => { + stats.total += 1; + const hasSplitAgg = aggs.find((agg) => agg.schema === 'split'); if (hasSplitAgg) { - acc.total_split += 1; + stats.total_split += 1; const isSplitRow = params.row; const isSplitEnabled = hasSplitAgg.enabled; + const container = isSplitRow ? stats.split_rows : stats.split_columns; - const container = isSplitRow ? acc.split_rows : acc.split_columns; container.total += 1; container.enabled = isSplitEnabled ? container.enabled + 1 : container.enabled; } + }; + + for await (const response of finder.find()) { + (response.saved_objects || []).forEach(({ attributes }: SavedObjectsFindResult) => { + if (attributes?.visState) { + try { + const visState: SavedVisState = JSON.parse(attributes.visState); + + if (visState.type === VIS_TYPE_TABLE) { + doTelemetry(visState); + } + } catch { + // nothing to be here, "so" not valid + } + } + }); + } + await finder.close(); - return acc; - }, defaultStats); + return stats; } diff --git a/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.test.ts b/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.test.ts index e045788897b61e..d32435ac454061 100644 --- a/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.test.ts +++ b/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.test.ts @@ -6,20 +6,19 @@ * Side Public License, v 1. */ -jest.mock('./get_stats', () => ({ - getStats: jest.fn().mockResolvedValue({ somestat: 1 }), -})); - import { createUsageCollectionSetupMock, createCollectorFetchContextMock, -} from 'src/plugins/usage_collection/server/mocks'; - +} from '../../../usage_collection/server/mocks'; import { registerVisTypeTableUsageCollector } from './register_usage_collector'; import { getStats } from './get_stats'; +jest.mock('./get_stats', () => ({ + getStats: jest.fn().mockResolvedValue({ somestat: 1 }), +})); + describe('registerVisTypeTableUsageCollector', () => { - it('Usage collector configs fit the shape', () => { + test('Usage collector configs fit the shape', () => { const mockCollectorSet = createUsageCollectionSetupMock(); registerVisTypeTableUsageCollector(mockCollectorSet); expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1); @@ -45,7 +44,7 @@ describe('registerVisTypeTableUsageCollector', () => { expect(usageCollectorConfig.isReady()).toBe(true); }); - it('Usage collector config.fetch calls getStats', async () => { + test('Usage collector config.fetch calls getStats', async () => { const mockCollectorSet = createUsageCollectionSetupMock(); registerVisTypeTableUsageCollector(mockCollectorSet); const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value; diff --git a/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.ts b/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.ts index d3d3204e0841c7..74044c9ae70c0f 100644 --- a/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.ts +++ b/src/plugins/vis_type_table/server/usage_collector/register_usage_collector.ts @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; - import { getStats, VisTypeTableUsage } from './get_stats'; +import type { UsageCollectionSetup } from '../../../usage_collection/server'; export function registerVisTypeTableUsageCollector(collectorSet: UsageCollectionSetup) { const collector = collectorSet.makeUsageCollector({ diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx index 6ba213a47cd474..17af812ae5ce30 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx @@ -13,14 +13,13 @@ import { aggToComponent } from '../lib/agg_to_component'; import { isMetricEnabled } from '../../lib/check_ui_restrictions'; import { UnsupportedAgg } from './unsupported_agg'; import { TemporaryUnsupportedAgg } from './temporary_unsupported_agg'; -import type { Metric, Panel, Series } from '../../../../common/types'; +import type { Metric, Panel, Series, SanitizedFieldType } from '../../../../common/types'; import { DragHandleProps } from '../../../types'; import { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions'; -import { IFieldType } from '../../../../../data/common/index_patterns/fields'; interface AggProps extends HTMLAttributes { disableDelete: boolean; - fields: IFieldType[]; + fields: Record; model: Metric; panel: Panel; series: Series; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx index 192cdd4985c6cc..0edd8b9c3feb53 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/aggs.tsx @@ -15,9 +15,8 @@ import { Agg } from './agg'; import { seriesChangeHandler } from '../lib/series_change_handler'; import { handleAdd, handleDelete } from '../lib/collection_actions'; import { newMetricAggFn } from '../lib/new_metric_agg_fn'; -import type { Panel, Series } from '../../../../common/types'; +import type { Panel, Series, SanitizedFieldType } from '../../../../common/types'; import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions'; -import { IFieldType } from '../../../../../data/common/index_patterns/fields'; const DROPPABLE_ID = 'aggs_dnd'; @@ -25,7 +24,7 @@ export interface AggsProps { name: keyof Series; panel: Panel; model: Series; - fields: IFieldType[]; + fields: Record; uiRestrictions: TimeseriesUIRestrictions; onChange(): void; } diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx index 3633f8add74578..d9d5401fd131fb 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx @@ -42,6 +42,7 @@ import { BUCKET_TYPES } from '../../../../common/enums'; import { PanelConfigProps, PANEL_CONFIG_TABS } from './types'; import { TimeseriesVisParams } from '../../../types'; import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; +import { KBN_FIELD_TYPES } from '../../../../../data/public'; export class TablePanelConfig extends Component< PanelConfigProps, @@ -115,6 +116,13 @@ export class TablePanelConfig extends Component< defaultMessage="Group by field" /> } + restrict={[ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.BOOLEAN, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ]} fields={this.props.fields} value={model.pivot_id} indexPattern={model.index_pattern} 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 ce381a0e539d0d..524e35f9d29e1b 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 @@ -54,6 +54,15 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js /> } onChange={[Function]} + restrict={ + Array [ + "number", + "boolean", + "date", + "ip", + "string", + ] + } type="terms" value="OriginCityName" /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js index 9c097de38d56ac..a668e5b727b482 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js @@ -120,6 +120,13 @@ export const SplitByTermsUI = ({ description="This labels a field selector allowing the user to chose 'by' which field to group." /> } + restrict={[ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.BOOLEAN, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ]} data-test-subj="groupByField" indexPattern={indexPattern} onChange={handleSelectChange('terms_field')} diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts index 7add5cb4a45533..767d5c00d7a334 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts @@ -10,7 +10,7 @@ import { IndexPatternsService } from '../../../../../data/common'; import { from } from 'rxjs'; import { AbstractSearchStrategy } from './abstract_search_strategy'; -import type { IFieldType } from '../../../../../data/common'; +import type { FieldSpec } from '../../../../../data/common'; import type { CachedIndexPatternFetcher } from '../lib/cached_index_pattern_fetcher'; import type { VisTypeTimeseriesRequestHandlerContext, @@ -21,7 +21,7 @@ class FooSearchStrategy extends AbstractSearchStrategy {} describe('AbstractSearchStrategy', () => { let abstractSearchStrategy: AbstractSearchStrategy; - let mockedFields: IFieldType[]; + let mockedFields: FieldSpec[]; let requestContext: VisTypeTimeseriesRequestHandlerContext; beforeEach(() => { diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index 68c1db3c03582e..58cd58c812e4df 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -120,7 +120,7 @@ export class VisTypeTimeseriesPlugin implements Plugin { fieldsRoutes(router, framework); if (plugins.usageCollection) { - registerTimeseriesUsageCollector(plugins.usageCollection, globalConfig$); + registerTimeseriesUsageCollector(plugins.usageCollection); } return { diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.test.ts b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.test.ts index 81ac2be0e7da99..6bb5f3bbfcadd7 100644 --- a/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.test.ts +++ b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.test.ts @@ -7,15 +7,14 @@ */ import { getStats } from './get_usage_collector'; -import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks'; +import { createCollectorFetchContextMock } from '../../../usage_collection/server/mocks'; +import type { SavedObjectsClientContract, SavedObjectsFindResponse } from '../../../../core/server'; import { TIME_RANGE_DATA_MODES } from '../../common/enums'; -const mockedSavedObjects = [ - { - _id: 'visualization:timeseries-123', - _source: { - type: 'visualization', - visualization: { +const mockedSavedObject = { + saved_objects: [ + { + attributes: { visState: JSON.stringify({ type: 'metrics', title: 'TSVB visualization 1', @@ -25,12 +24,8 @@ const mockedSavedObjects = [ }), }, }, - }, - { - _id: 'visualization:timeseries-321', - _source: { - type: 'visualization', - visualization: { + { + attributes: { visState: JSON.stringify({ type: 'metrics', title: 'TSVB visualization 2', @@ -40,12 +35,8 @@ const mockedSavedObjects = [ }), }, }, - }, - { - _id: 'visualization:timeseries-456', - _source: { - type: 'visualization', - visualization: { + { + attributes: { visState: JSON.stringify({ type: 'metrics', title: 'TSVB visualization 3', @@ -55,8 +46,8 @@ const mockedSavedObjects = [ }), }, }, - }, -]; + ], +} as SavedObjectsFindResponse; const mockedSavedObjectsByValue = [ { @@ -91,42 +82,43 @@ const mockedSavedObjectsByValue = [ }, ]; -const getMockCollectorFetchContext = (hits?: unknown[], savedObjectsByValue: unknown[] = []) => { +const getMockCollectorFetchContext = ( + savedObjects: SavedObjectsFindResponse, + savedObjectsByValue: unknown[] = [] +) => { const fetchParamsMock = createCollectorFetchContextMock(); - fetchParamsMock.esClient.search = jest.fn().mockResolvedValue({ body: { hits: { hits } } }); - fetchParamsMock.soClient.find = jest.fn().mockResolvedValue({ - saved_objects: savedObjectsByValue, - }); + fetchParamsMock.soClient = ({ + find: jest.fn().mockResolvedValue({ + saved_objects: savedObjectsByValue, + }), + createPointInTimeFinder: jest.fn().mockResolvedValue({ + close: jest.fn(), + find: function* asyncGenerator() { + yield savedObjects; + }, + }), + } as unknown) as SavedObjectsClientContract; return fetchParamsMock; }; describe('Timeseries visualization usage collector', () => { - const mockIndex = 'mock_index'; - test('Returns undefined when no results found (undefined)', async () => { - const mockCollectorFetchContext = getMockCollectorFetchContext([], []); - const result = await getStats( - mockCollectorFetchContext.esClient, - mockCollectorFetchContext.soClient, - mockIndex + const mockCollectorFetchContext = getMockCollectorFetchContext( + ({ saved_objects: [] } as unknown) as SavedObjectsFindResponse, + [] ); + const result = await getStats(mockCollectorFetchContext.soClient); expect(result).toBeUndefined(); }); test('Returns undefined when no timeseries saved objects found', async () => { - const mockCollectorFetchContext = getMockCollectorFetchContext( - [ + const mockCollectorFetchContext = getMockCollectorFetchContext({ + saved_objects: [ { - _id: 'visualization:myvis-123', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "area"}' }, - }, + attributes: { visState: '{"type": "area"}' }, }, - ], - [ { attributes: { panelsJSON: JSON.stringify({ @@ -139,27 +131,20 @@ describe('Timeseries visualization usage collector', () => { }), }, }, - ] - ); - const result = await getStats( - mockCollectorFetchContext.esClient, - mockCollectorFetchContext.soClient, - mockIndex - ); + ], + } as SavedObjectsFindResponse); + + const result = await getStats(mockCollectorFetchContext.soClient); expect(result).toBeUndefined(); }); test('Summarizes visualizations response data', async () => { const mockCollectorFetchContext = getMockCollectorFetchContext( - mockedSavedObjects, + mockedSavedObject, mockedSavedObjectsByValue ); - const result = await getStats( - mockCollectorFetchContext.esClient, - mockCollectorFetchContext.soClient, - mockIndex - ); + const result = await getStats(mockCollectorFetchContext.soClient); expect(result).toMatchObject({ timeseries_use_last_value_mode_total: 3, diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.ts b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.ts index 5146b52a8e5028..b5b22e06b51c9e 100644 --- a/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.ts +++ b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.ts @@ -6,49 +6,65 @@ * Side Public License, v 1. */ -import { ElasticsearchClient } from 'src/core/server'; -import { SavedObjectsClientContract, ISavedObjectsRepository } from 'kibana/server'; import { TIME_RANGE_DATA_MODES } from '../../common/enums'; import { findByValueEmbeddables } from '../../../dashboard/server'; +import type { + SavedObjectsClientContract, + ISavedObjectsRepository, + SavedObjectsFindResult, +} from '../../../../core/server'; +import type { SavedVisState } from '../../../visualizations/common'; + export interface TimeseriesUsage { timeseries_use_last_value_mode_total: number; } -interface VisState { - type?: string; - params?: any; -} +const doTelemetryFoVisualizations = async ( + soClient: SavedObjectsClientContract | ISavedObjectsRepository, + telemetryUseLastValueMode: (savedVis: SavedVisState) => void +) => { + const finder = await soClient.createPointInTimeFinder({ + type: 'visualization', + perPage: 1000, + namespaces: ['*'], + }); -export const getStats = async ( - esClient: ElasticsearchClient, + for await (const response of finder.find()) { + (response.saved_objects || []).forEach(({ attributes }: SavedObjectsFindResult) => { + if (attributes?.visState) { + try { + const visState: SavedVisState = JSON.parse(attributes.visState); + + telemetryUseLastValueMode(visState); + } catch { + // nothing to be here, "so" not valid + } + } + }); + } + await finder.close(); +}; + +const doTelemetryForByValueVisualizations = async ( soClient: SavedObjectsClientContract | ISavedObjectsRepository, - index: string + telemetryUseLastValueMode: (savedVis: SavedVisState) => void +) => { + const byValueVisualizations = await findByValueEmbeddables(soClient, 'visualization'); + + for (const item of byValueVisualizations) { + telemetryUseLastValueMode((item.savedVis as unknown) as SavedVisState); + } +}; + +export const getStats = async ( + soClient: SavedObjectsClientContract | ISavedObjectsRepository ): Promise => { const timeseriesUsage = { timeseries_use_last_value_mode_total: 0, }; - const searchParams = { - size: 10000, - index, - ignoreUnavailable: true, - filterPath: ['hits.hits._id', 'hits.hits._source.visualization'], - body: { - query: { - bool: { - filter: { term: { type: 'visualization' } }, - }, - }, - }, - }; - - const { body: esResponse } = await esClient.search<{ - visualization: { visState: string }; - updated_at: string; - }>(searchParams); - - function telemetryUseLastValueMode(visState: VisState) { + function telemetryUseLastValueMode(visState: SavedVisState) { if ( visState.type === 'metrics' && visState.params.type !== 'timeseries' && @@ -59,28 +75,10 @@ export const getStats = async ( } } - if (esResponse?.hits?.hits?.length) { - for (const hit of esResponse.hits.hits) { - if (hit._source && 'visualization' in hit._source) { - const { visualization } = hit._source!; - - let visState: VisState = {}; - try { - visState = JSON.parse(visualization?.visState ?? '{}'); - } catch (e) { - // invalid visState - } - - telemetryUseLastValueMode(visState); - } - } - } - - const byValueVisualizations = await findByValueEmbeddables(soClient, 'visualization'); - - for (const item of byValueVisualizations) { - telemetryUseLastValueMode(item.savedVis as VisState); - } + await Promise.all([ + doTelemetryFoVisualizations(soClient, telemetryUseLastValueMode), + doTelemetryForByValueVisualizations(soClient, telemetryUseLastValueMode), + ]); return timeseriesUsage.timeseries_use_last_value_mode_total ? timeseriesUsage : undefined; }; diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.test.ts b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.test.ts index 726ad972ab8d1b..0dfe5ae5f9351a 100644 --- a/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.test.ts +++ b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.test.ts @@ -6,27 +6,22 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; import { mockStats, mockGetStats } from './get_usage_collector.mock'; -import { createUsageCollectionSetupMock } from 'src/plugins/usage_collection/server/mocks'; -import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks'; +import { createUsageCollectionSetupMock } from '../../../usage_collection/server/mocks'; +import { createCollectorFetchContextMock } from '../../../usage_collection/server/mocks'; import { registerTimeseriesUsageCollector } from './register_timeseries_collector'; -import { ConfigObservable } from '../types'; describe('registerTimeseriesUsageCollector', () => { - const mockIndex = 'mock_index'; - const mockConfig = of({ kibana: { index: mockIndex } }) as ConfigObservable; - it('makes a usage collector and registers it`', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerTimeseriesUsageCollector(mockCollectorSet, mockConfig); + registerTimeseriesUsageCollector(mockCollectorSet); expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1); expect(mockCollectorSet.registerCollector).toBeCalledTimes(1); }); it('makeUsageCollector configs fit the shape', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerTimeseriesUsageCollector(mockCollectorSet, mockConfig); + registerTimeseriesUsageCollector(mockCollectorSet); expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({ type: 'vis_type_timeseries', isReady: expect.any(Function), @@ -39,23 +34,19 @@ describe('registerTimeseriesUsageCollector', () => { it('makeUsageCollector config.isReady returns true', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerTimeseriesUsageCollector(mockCollectorSet, mockConfig); + registerTimeseriesUsageCollector(mockCollectorSet); const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0]; expect(usageCollectorConfig.isReady()).toBe(true); }); it('makeUsageCollector config.fetch calls getStats', async () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerTimeseriesUsageCollector(mockCollectorSet, mockConfig); + registerTimeseriesUsageCollector(mockCollectorSet); const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value; const mockedCollectorFetchContext = createCollectorFetchContextMock(); const fetchResult = await usageCollector.fetch(mockedCollectorFetchContext); expect(mockGetStats).toBeCalledTimes(1); - expect(mockGetStats).toBeCalledWith( - mockedCollectorFetchContext.esClient, - mockedCollectorFetchContext.soClient, - mockIndex - ); + expect(mockGetStats).toBeCalledWith(mockedCollectorFetchContext.soClient); expect(fetchResult).toBe(mockStats); }); }); diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.ts b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.ts index 5edeb6654020e5..7e9294f03ba1cb 100644 --- a/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.ts +++ b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.ts @@ -6,15 +6,10 @@ * Side Public License, v 1. */ -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { first } from 'rxjs/operators'; import { getStats, TimeseriesUsage } from './get_usage_collector'; -import { ConfigObservable } from '../types'; +import type { UsageCollectionSetup } from '../../../usage_collection/server'; -export function registerTimeseriesUsageCollector( - collectorSet: UsageCollectionSetup, - config: ConfigObservable -) { +export function registerTimeseriesUsageCollector(collectorSet: UsageCollectionSetup) { const collector = collectorSet.makeUsageCollector({ type: 'vis_type_timeseries', isReady: () => true, @@ -24,11 +19,7 @@ export function registerTimeseriesUsageCollector( _meta: { description: 'Number of TSVB visualizations using "last value" as a time range' }, }, }, - fetch: async ({ esClient, soClient }) => { - const { index } = (await config.pipe(first()).toPromise()).kibana; - - return await getStats(esClient, soClient, index); - }, + fetch: async ({ soClient }) => await getStats(soClient), }); collectorSet.registerCollector(collector); diff --git a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts index ce815cba4a4e28..82aba087dedc13 100644 --- a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts +++ b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts @@ -7,67 +7,63 @@ */ import { getStats } from './get_usage_collector'; -import { HomeServerPluginSetup } from '../../../home/server'; -import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks'; +import { createCollectorFetchContextMock } from '../../../usage_collection/server/mocks'; +import type { HomeServerPluginSetup } from '../../../home/server'; +import type { SavedObjectsClientContract } from '../../../../core/server'; const mockedSavedObjects = [ // vega-lite lib spec { - _id: 'visualization:vega-1', - _source: { - type: 'visualization', - visualization: { - visState: JSON.stringify({ - type: 'vega', - params: { - spec: '{"$schema": "https://vega.github.io/schema/vega-lite/v5.json" }', - }, - }), - }, + attributes: { + visState: JSON.stringify({ + type: 'vega', + params: { + spec: '{"$schema": "https://vega.github.io/schema/vega-lite/v5.json" }', + }, + }), }, }, // vega lib spec { - _id: 'visualization:vega-2', - _source: { - type: 'visualization', - visualization: { - visState: JSON.stringify({ - type: 'vega', - params: { - spec: '{"$schema": "https://vega.github.io/schema/vega/v5.json" }', - }, - }), - }, + attributes: { + visState: JSON.stringify({ + type: 'vega', + params: { + spec: '{"$schema": "https://vega.github.io/schema/vega/v5.json" }', + }, + }), }, }, // map layout { - _id: 'visualization:vega-3', - _source: { - type: 'visualization', - visualization: { - visState: JSON.stringify({ - type: 'vega', - params: { - spec: - '{"$schema": "https://vega.github.io/schema/vega/v3.json" \n "config": { "kibana" : { "type": "map" }} }', - }, - }), - }, + attributes: { + visState: JSON.stringify({ + type: 'vega', + params: { + spec: + '{"$schema": "https://vega.github.io/schema/vega/v3.json" \n "config": { "kibana" : { "type": "map" }} }', + }, + }), }, }, ]; -const getMockCollectorFetchContext = (hits?: unknown[]) => { +const getMockCollectorFetchContext = (savedObjects?: unknown[]) => { const fetchParamsMock = createCollectorFetchContextMock(); - fetchParamsMock.esClient.search = jest.fn().mockResolvedValue({ body: { hits: { hits } } }); + fetchParamsMock.soClient = ({ + createPointInTimeFinder: jest.fn().mockResolvedValue({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: savedObjects }; + }, + }), + } as unknown) as SavedObjectsClientContract; + return fetchParamsMock; }; describe('Vega visualization usage collector', () => { - const mockIndex = 'mock_index'; const mockDeps = { home: ({ sampleData: { @@ -94,13 +90,13 @@ describe('Vega visualization usage collector', () => { }; test('Returns undefined when no results found (undefined)', async () => { - const result = await getStats(getMockCollectorFetchContext().esClient, mockIndex, mockDeps); + const result = await getStats(getMockCollectorFetchContext().soClient, mockDeps); expect(result).toBeUndefined(); }); test('Returns undefined when no results found (0 results)', async () => { - const result = await getStats(getMockCollectorFetchContext([]).esClient, mockIndex, mockDeps); + const result = await getStats(getMockCollectorFetchContext([]).soClient, mockDeps); expect(result).toBeUndefined(); }); @@ -115,7 +111,7 @@ describe('Vega visualization usage collector', () => { }, }, ]); - const result = await getStats(mockCollectorFetchContext.esClient, mockIndex, mockDeps); + const result = await getStats(mockCollectorFetchContext.soClient, mockDeps); expect(result).toBeUndefined(); }); @@ -123,30 +119,26 @@ describe('Vega visualization usage collector', () => { test('Should ingnore sample data visualizations', async () => { const mockCollectorFetchContext = getMockCollectorFetchContext([ { - _id: 'visualization:sampledata-123', - _source: { - type: 'visualization', - visualization: { - visState: JSON.stringify({ - type: 'vega', - title: 'sample vega visualization', - params: { - spec: '{"$schema": "https://vega.github.io/schema/vega/v5.json" }', - }, - }), - }, + attributes: { + visState: JSON.stringify({ + type: 'vega', + title: 'sample vega visualization', + params: { + spec: '{"$schema": "https://vega.github.io/schema/vega/v5.json" }', + }, + }), }, }, ]); - const result = await getStats(mockCollectorFetchContext.esClient, mockIndex, mockDeps); + const result = await getStats(mockCollectorFetchContext.soClient, mockDeps); expect(result).toBeUndefined(); }); test('Summarizes visualizations response data', async () => { const mockCollectorFetchContext = getMockCollectorFetchContext(mockedSavedObjects); - const result = await getStats(mockCollectorFetchContext.esClient, mockIndex, mockDeps); + const result = await getStats(mockCollectorFetchContext.soClient, mockDeps); expect(result).toMatchObject({ vega_lib_specs_total: 2, diff --git a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts index 310486bfdfffde..ae99021745a0cc 100644 --- a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts +++ b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.ts @@ -7,14 +7,20 @@ */ import { parse } from 'hjson'; -import type { ElasticsearchClient } from 'src/core/server'; -import { VegaSavedObjectAttributes, VisTypeVegaPluginSetupDependencies } from '../types'; +import type { SavedObjectsClientContract, SavedObjectsFindResult } from '../../../../core/server'; +import type { SavedVisState } from '../../../visualizations/common'; +import type { VegaSavedObjectAttributes, VisTypeVegaPluginSetupDependencies } from '../types'; type UsageCollectorDependencies = Pick; - type VegaType = 'vega' | 'vega-lite'; +export interface VegaUsage { + vega_lib_specs_total: number; + vega_lite_lib_specs_total: number; + vega_use_map_total: number; +} + function isVegaType(attributes: any): attributes is VegaSavedObjectAttributes { return attributes && attributes.type === 'vega' && attributes.params?.spec; } @@ -45,15 +51,8 @@ const getDefaultVegaVisualizations = (home: UsageCollectorDependencies['home']) return titles; }; -export interface VegaUsage { - vega_lib_specs_total: number; - vega_lite_lib_specs_total: number; - vega_use_map_total: number; -} - export const getStats = async ( - esClient: ElasticsearchClient, - index: string, + soClient: SavedObjectsClientContract, { home }: UsageCollectorDependencies ): Promise => { let shouldPublishTelemetry = false; @@ -64,58 +63,54 @@ export const getStats = async ( vega_use_map_total: 0, }; - const searchParams = { - size: 10000, - index, - ignoreUnavailable: true, - filterPath: ['hits.hits._id', 'hits.hits._source.visualization'], - body: { - query: { - bool: { - filter: { term: { type: 'visualization' } }, - }, - }, - }, - }; - - const { body: esResponse } = await esClient.search<{ visualization: { visState: string } }>( - searchParams - ); - const size = esResponse?.hits?.hits?.length ?? 0; - - if (!size) { - return; - } - // we want to exclude the Vega Sample Data visualizations from the stats // in order to have more accurate results const excludedFromStatsVisualizations = getDefaultVegaVisualizations(home); - for (const hit of esResponse.hits.hits) { - const visualization = hit._source?.visualization; - const visState = JSON.parse(visualization?.visState ?? '{}'); - if (isVegaType(visState) && !excludedFromStatsVisualizations.includes(visState.title)) { - try { - const spec = parse(visState.params.spec, { legacyRoot: false }); + const finder = await soClient.createPointInTimeFinder({ + type: 'visualization', + perPage: 1000, + namespaces: ['*'], + }); - if (spec) { - shouldPublishTelemetry = true; + const doTelemetry = ({ params }: SavedVisState) => { + try { + const spec = parse(params.spec, { legacyRoot: false }); - if (checkVegaSchemaType(spec.$schema, 'vega')) { - vegaUsage.vega_lib_specs_total++; - } - if (checkVegaSchemaType(spec.$schema, 'vega-lite')) { - vegaUsage.vega_lite_lib_specs_total++; - } - if (spec.config?.kibana?.type === 'map') { - vegaUsage.vega_use_map_total++; - } + if (spec) { + shouldPublishTelemetry = true; + + if (checkVegaSchemaType(spec.$schema, 'vega')) { + vegaUsage.vega_lib_specs_total++; + } + if (checkVegaSchemaType(spec.$schema, 'vega-lite')) { + vegaUsage.vega_lite_lib_specs_total++; + } + if (spec.config?.kibana?.type === 'map') { + vegaUsage.vega_use_map_total++; } - } catch (e) { - // Let it go, the data is invalid and we'll don't need to handle it } + } catch (e) { + // Let it go, the data is invalid and we'll don't need to handle it } + }; + + for await (const response of finder.find()) { + (response.saved_objects || []).forEach(({ attributes }: SavedObjectsFindResult) => { + if (attributes?.visState) { + try { + const visState: SavedVisState = JSON.parse(attributes.visState); + + if (isVegaType(visState) && !excludedFromStatsVisualizations.includes(visState.title)) { + doTelemetry(visState); + } + } catch { + // nothing to be here, "so" not valid + } + } + }); } + await finder.close(); return shouldPublishTelemetry ? vegaUsage : undefined; }; diff --git a/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.test.ts b/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.test.ts index 7933da3e675f63..fc488540293add 100644 --- a/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.test.ts +++ b/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.test.ts @@ -6,27 +6,28 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; +import { + createUsageCollectionSetupMock, + createCollectorFetchContextMock, +} from '../../../usage_collection/server/mocks'; import { mockStats, mockGetStats } from './get_usage_collector.mock'; -import { createUsageCollectionSetupMock } from 'src/plugins/usage_collection/server/mocks'; -import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks'; -import { HomeServerPluginSetup } from '../../../home/server'; import { registerVegaUsageCollector } from './register_vega_collector'; -import { ConfigObservable } from '../types'; + +import type { HomeServerPluginSetup } from '../../../home/server'; +import type { ConfigObservable } from '../types'; describe('registerVegaUsageCollector', () => { - const mockIndex = 'mock_index'; const mockDeps = { home: ({} as unknown) as HomeServerPluginSetup }; - const mockConfig = of({ kibana: { index: mockIndex } }) as ConfigObservable; + const mockConfig = {} as ConfigObservable; - it('makes a usage collector and registers it`', () => { + test('makes a usage collector and registers it`', () => { const mockCollectorSet = createUsageCollectionSetupMock(); registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps); expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1); expect(mockCollectorSet.registerCollector).toBeCalledTimes(1); }); - it('makeUsageCollector configs fit the shape', () => { + test('makeUsageCollector configs fit the shape', () => { const mockCollectorSet = createUsageCollectionSetupMock(); registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps); expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({ @@ -39,21 +40,21 @@ describe('registerVegaUsageCollector', () => { expect(usageCollectorConfig.isReady()).toBe(true); }); - it('makeUsageCollector config.isReady returns true', () => { + test('makeUsageCollector config.isReady returns true', () => { const mockCollectorSet = createUsageCollectionSetupMock(); registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps); const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0]; expect(usageCollectorConfig.isReady()).toBe(true); }); - it('makeUsageCollector config.fetch calls getStats', async () => { + test('makeUsageCollector config.fetch calls getStats', async () => { const mockCollectorSet = createUsageCollectionSetupMock(); registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps); const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value; const mockedCollectorFetchContext = createCollectorFetchContextMock(); const fetchResult = await usageCollector.fetch(mockedCollectorFetchContext); expect(mockGetStats).toBeCalledTimes(1); - expect(mockGetStats).toBeCalledWith(mockedCollectorFetchContext.esClient, mockIndex, mockDeps); + expect(mockGetStats).toBeCalledWith(mockedCollectorFetchContext.soClient, mockDeps); expect(fetchResult).toBe(mockStats); }); }); diff --git a/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.ts b/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.ts index 45f1758c90450e..ef65b58a8315b1 100644 --- a/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.ts +++ b/src/plugins/vis_type_vega/server/usage_collector/register_vega_collector.ts @@ -6,10 +6,9 @@ * Side Public License, v 1. */ -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { first } from 'rxjs/operators'; import { getStats, VegaUsage } from './get_usage_collector'; -import { ConfigObservable, VisTypeVegaPluginSetupDependencies } from '../types'; +import type { UsageCollectionSetup } from '../../../usage_collection/server'; +import type { ConfigObservable, VisTypeVegaPluginSetupDependencies } from '../types'; export function registerVegaUsageCollector( collectorSet: UsageCollectionSetup, @@ -24,11 +23,7 @@ export function registerVegaUsageCollector( vega_lite_lib_specs_total: { type: 'long' }, vega_use_map_total: { type: 'long' }, }, - fetch: async ({ esClient }) => { - const { index } = (await config.pipe(first()).toPromise()).kibana; - - return await getStats(esClient, index, dependencies); - }, + fetch: async ({ soClient }) => await getStats(soClient, dependencies), }); collectorSet.registerCollector(collector); diff --git a/src/plugins/visualizations/server/plugin.ts b/src/plugins/visualizations/server/plugin.ts index 5a5a80b2689d6e..6b87c0206347fb 100644 --- a/src/plugins/visualizations/server/plugin.ts +++ b/src/plugins/visualizations/server/plugin.ts @@ -8,33 +8,29 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { Observable } from 'rxjs'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { + +import { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants'; +import { visualizationSavedObjectType } from './saved_objects'; +import { registerVisualizationsCollector } from './usage_collector'; + +import type { VisualizationsPluginSetup, VisualizationsPluginStart } from './types'; +import type { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger, } from '../../../core/server'; - -import { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants'; - -import { visualizationSavedObjectType } from './saved_objects'; - -import { VisualizationsPluginSetup, VisualizationsPluginStart } from './types'; -import { registerVisualizationsCollector } from './usage_collector'; -import { EmbeddableSetup } from '../../embeddable/server'; +import type { UsageCollectionSetup } from '../../usage_collection/server'; +import type { EmbeddableSetup } from '../../embeddable/server'; import { visualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; export class VisualizationsPlugin implements Plugin { private readonly logger: Logger; - private readonly config: Observable<{ kibana: { index: string } }>; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); - this.config = initializerContext.config.legacy.globalConfig$; } public setup( @@ -61,7 +57,7 @@ export class VisualizationsPlugin }); if (plugins.usageCollection) { - registerVisualizationsCollector(plugins.usageCollection, this.config); + registerVisualizationsCollector(plugins.usageCollection); } plugins.embeddable.registerEmbeddableFactory(visualizeEmbeddableFactory()); diff --git a/src/plugins/visualizations/server/usage_collector/get_usage_collector.test.ts b/src/plugins/visualizations/server/usage_collector/get_usage_collector.test.ts index 325b52eaec3689..24c290ca849ed9 100644 --- a/src/plugins/visualizations/server/usage_collector/get_usage_collector.test.ts +++ b/src/plugins/visualizations/server/usage_collector/get_usage_collector.test.ts @@ -7,126 +7,106 @@ */ import moment from 'moment'; -import { ElasticsearchClient } from 'src/core/server'; import { getStats } from './get_usage_collector'; +import type { SavedObjectsClientContract } from '../../../../core/server'; const defaultMockSavedObjects = [ { - _id: 'visualization:coolviz-123', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "shell_beads"}' }, - updated_at: moment().subtract(7, 'days').startOf('day').toString(), - }, + id: 'visualization:coolviz-123', + attributes: { visState: '{"type": "shell_beads"}' }, + updated_at: moment().subtract(7, 'days').startOf('day').toString(), }, ]; const enlargedMockSavedObjects = [ // default space { - _id: 'visualization:coolviz-123', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "cave_painting"}' }, - updated_at: moment().subtract(7, 'days').startOf('day').toString(), - }, + id: 'visualization:coolviz-123', + namespaces: ['default'], + attributes: { visState: '{"type": "cave_painting"}' }, + updated_at: moment().subtract(7, 'days').startOf('day').toString(), }, { - _id: 'visualization:coolviz-456', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "printing_press"}' }, - updated_at: moment().subtract(20, 'days').startOf('day').toString(), - }, + id: 'visualization:coolviz-456', + namespaces: ['default'], + attributes: { visState: '{"type": "printing_press"}' }, + updated_at: moment().subtract(20, 'days').startOf('day').toString(), }, { - _id: 'meat:visualization:coolviz-789', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "floppy_disk"}' }, - updated_at: moment().subtract(2, 'months').startOf('day').toString(), - }, + id: 'meat:visualization:coolviz-789', + namespaces: ['default'], + attributes: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(2, 'months').startOf('day').toString(), }, // meat space { - _id: 'meat:visualization:coolviz-789', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "cave_painting"}' }, - updated_at: moment().subtract(89, 'days').startOf('day').toString(), - }, + id: 'meat:visualization:coolviz-789', + namespaces: ['meat'], + attributes: { visState: '{"type": "cave_painting"}' }, + updated_at: moment().subtract(89, 'days').startOf('day').toString(), }, { - _id: 'meat:visualization:coolviz-789', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "cuneiform"}' }, - updated_at: moment().subtract(5, 'months').startOf('day').toString(), - }, + id: 'meat:visualization:coolviz-789', + namespaces: ['meat'], + attributes: { visState: '{"type": "cuneiform"}' }, + updated_at: moment().subtract(5, 'months').startOf('day').toString(), }, { - _id: 'meat:visualization:coolviz-789', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "cuneiform"}' }, - updated_at: moment().subtract(2, 'days').startOf('day').toString(), - }, + id: 'meat:visualization:coolviz-789', + namespaces: ['meat'], + attributes: { visState: '{"type": "cuneiform"}' }, + updated_at: moment().subtract(2, 'days').startOf('day').toString(), }, { - _id: 'meat:visualization:coolviz-789', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "floppy_disk"}' }, - updated_at: moment().subtract(7, 'days').startOf('day').toString(), - }, + id: 'meat:visualization:coolviz-789', + attributes: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(7, 'days').startOf('day').toString(), }, // cyber space { - _id: 'cyber:visualization:coolviz-789', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "floppy_disk"}' }, - updated_at: moment().subtract(7, 'months').startOf('day').toString(), - }, + id: 'cyber:visualization:coolviz-789', + namespaces: ['cyber'], + attributes: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(7, 'months').startOf('day').toString(), }, { - _id: 'cyber:visualization:coolviz-789', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "floppy_disk"}' }, - updated_at: moment().subtract(3, 'days').startOf('day').toString(), - }, + id: 'cyber:visualization:coolviz-789', + namespaces: ['cyber'], + attributes: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(3, 'days').startOf('day').toString(), }, { - _id: 'cyber:visualization:coolviz-123', - _source: { - type: 'visualization', - visualization: { visState: '{"type": "cave_painting"}' }, - updated_at: moment().subtract(15, 'days').startOf('day').toString(), - }, + id: 'cyber:visualization:coolviz-123', + namespaces: ['cyber'], + attributes: { visState: '{"type": "cave_painting"}' }, + updated_at: moment().subtract(15, 'days').startOf('day').toString(), }, ]; describe('Visualizations usage collector', () => { - const mockIndex = ''; - - const getMockCallCluster = (hits: unknown[]) => - ({ - search: () => Promise.resolve({ body: { hits: { hits } } }) as unknown, - } as ElasticsearchClient); + const getMockCallCluster = (savedObjects: unknown[]) => + (({ + createPointInTimeFinder: jest.fn().mockResolvedValue({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: savedObjects }; + }, + }), + } as unknown) as SavedObjectsClientContract); test('Returns undefined when no results found (undefined)', async () => { - const result = await getStats(getMockCallCluster(undefined as any), mockIndex); + const result = await getStats(getMockCallCluster(undefined as any)); + expect(result).toBeUndefined(); }); test('Returns undefined when no results found (0 results)', async () => { - const result = await getStats(getMockCallCluster([]), mockIndex); + const result = await getStats(getMockCallCluster([])); expect(result).toBeUndefined(); }); test('Summarizes visualizations response data', async () => { - const result = await getStats(getMockCallCluster(defaultMockSavedObjects), mockIndex); + const result = await getStats(getMockCallCluster(defaultMockSavedObjects)); expect(result).toMatchObject({ shell_beads: { @@ -181,7 +161,7 @@ describe('Visualizations usage collector', () => { }, }; - const result = await getStats(getMockCallCluster(enlargedMockSavedObjects), mockIndex); + const result = await getStats(getMockCallCluster(enlargedMockSavedObjects)); expect(result).toMatchObject(expectedStats); }); diff --git a/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts b/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts index 2cd715b7b02c80..c2fa148cf121c1 100644 --- a/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts +++ b/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts @@ -6,12 +6,11 @@ * Side Public License, v 1. */ -import { countBy, get, groupBy, mapValues, max, min, values } from 'lodash'; -import { ElasticsearchClient } from 'kibana/server'; -import type { estypes } from '@elastic/elasticsearch'; - +import { countBy, groupBy, mapValues, max, min, values } from 'lodash'; import { getPastDays } from './get_past_days'; -type ESResponse = estypes.SearchResponse<{ visualization: { visState: string } }>; + +import type { SavedObjectsClientContract, SavedObjectsFindResult } from '../../../../core/server'; +import type { SavedVisState } from '../../../visualizations/common'; interface VisSummary { type: string; @@ -35,61 +34,50 @@ export interface VisualizationUsage { * Parse the response data into telemetry payload */ export async function getStats( - esClient: ElasticsearchClient, - index: string + soClient: SavedObjectsClientContract ): Promise { - const searchParams = { - size: 10000, // elasticsearch index.max_result_window default value - index, - ignoreUnavailable: true, - filterPath: [ - 'hits.hits._id', - 'hits.hits._source.visualization', - 'hits.hits._source.updated_at', - ], - body: { - query: { - bool: { filter: { term: { type: 'visualization' } } }, - }, - }, - }; - const { body: esResponse } = await esClient.search(searchParams); - const size = get(esResponse, 'hits.hits.length', 0); - if (size < 1) { - return; - } - - // `map` to get the raw types - const visSummaries: VisSummary[] = esResponse.hits.hits.map((hit) => { - const spacePhrases = hit._id.toString().split(':'); - const lastUpdated: string = get(hit, '_source.updated_at'); - const space = spacePhrases.length === 3 ? spacePhrases[0] : 'default'; // if in a custom space, the format of a saved object ID is space:type:id - const visualization = get(hit, '_source.visualization', { visState: '{}' }); - const visState: { type?: string } = JSON.parse(visualization.visState); - return { - type: visState.type || '_na_', - space, - past_days: getPastDays(lastUpdated), - }; + const finder = await soClient.createPointInTimeFinder({ + type: 'visualization', + perPage: 1000, + namespaces: ['*'], }); - // organize stats per type - const visTypes = groupBy(visSummaries, 'type'); + const visSummaries: VisSummary[] = []; - // get the final result - return mapValues(visTypes, (curr) => { - const total = curr.length; - const spacesBreakdown = countBy(curr, 'space'); - const spaceCounts: number[] = values(spacesBreakdown); + for await (const response of finder.find()) { + (response.saved_objects || []).forEach((so: SavedObjectsFindResult) => { + if (so.attributes?.visState) { + const visState: SavedVisState = JSON.parse(so.attributes.visState); - return { - total, - spaces_min: min(spaceCounts), - spaces_max: max(spaceCounts), - spaces_avg: total / spaceCounts.length, - saved_7_days_total: curr.filter((c) => c.past_days <= 7).length, - saved_30_days_total: curr.filter((c) => c.past_days <= 30).length, - saved_90_days_total: curr.filter((c) => c.past_days <= 90).length, - }; - }); + visSummaries.push({ + type: visState.type ?? '_na_', + space: so.namespaces?.[0] ?? 'default', + past_days: getPastDays(so.updated_at!), + }); + } + }); + } + await finder.close(); + + if (visSummaries.length) { + // organize stats per type + const visTypes = groupBy(visSummaries, 'type'); + + // get the final result + return mapValues(visTypes, (curr) => { + const total = curr.length; + const spacesBreakdown = countBy(curr, 'space'); + const spaceCounts: number[] = values(spacesBreakdown); + + return { + total, + spaces_min: min(spaceCounts), + spaces_max: max(spaceCounts), + spaces_avg: total / spaceCounts.length, + saved_7_days_total: curr.filter((c) => c.past_days <= 7).length, + saved_30_days_total: curr.filter((c) => c.past_days <= 30).length, + saved_90_days_total: curr.filter((c) => c.past_days <= 90).length, + }; + }); + } } diff --git a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.test.ts b/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.test.ts index a3617631f734ba..e8f9df85166325 100644 --- a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.test.ts +++ b/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.test.ts @@ -5,28 +5,24 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -import { of } from 'rxjs'; +import { + createUsageCollectionSetupMock, + createCollectorFetchContextMock, +} from '../../../usage_collection/server/mocks'; import { mockStats, mockGetStats } from './get_usage_collector.mock'; -import { createUsageCollectionSetupMock } from 'src/plugins/usage_collection/server/mocks'; -import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks'; - import { registerVisualizationsCollector } from './register_visualizations_collector'; describe('registerVisualizationsCollector', () => { - const mockIndex = 'mock_index'; - const mockConfig = of({ kibana: { index: mockIndex } }); - - it('makes a usage collector and registers it`', () => { + test('makes a usage collector and registers it`', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerVisualizationsCollector(mockCollectorSet, mockConfig); + registerVisualizationsCollector(mockCollectorSet); expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1); expect(mockCollectorSet.registerCollector).toBeCalledTimes(1); }); - it('makeUsageCollector configs fit the shape', () => { + test('makeUsageCollector configs fit the shape', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerVisualizationsCollector(mockCollectorSet, mockConfig); + registerVisualizationsCollector(mockCollectorSet); expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({ type: 'visualization_types', isReady: expect.any(Function), @@ -37,21 +33,21 @@ describe('registerVisualizationsCollector', () => { expect(usageCollectorConfig.isReady()).toBe(true); }); - it('makeUsageCollector config.isReady returns true', () => { + test('makeUsageCollector config.isReady returns true', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerVisualizationsCollector(mockCollectorSet, mockConfig); + registerVisualizationsCollector(mockCollectorSet); const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0]; expect(usageCollectorConfig.isReady()).toBe(true); }); - it('makeUsageCollector config.fetch calls getStats', async () => { + test('makeUsageCollector config.fetch calls getStats', async () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerVisualizationsCollector(mockCollectorSet, mockConfig); + registerVisualizationsCollector(mockCollectorSet); const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value; const mockCollectorFetchContext = createCollectorFetchContextMock(); const fetchResult = await usageCollector.fetch(mockCollectorFetchContext); expect(mockGetStats).toBeCalledTimes(1); - expect(mockGetStats).toBeCalledWith(mockCollectorFetchContext.esClient, mockIndex); + expect(mockGetStats).toBeCalledWith(mockCollectorFetchContext.soClient); expect(fetchResult).toBe(mockStats); }); }); diff --git a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.ts b/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.ts index 1836af66233bd2..b7ca4268c9a890 100644 --- a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.ts +++ b/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.ts @@ -6,16 +6,10 @@ * Side Public License, v 1. */ -import { Observable } from 'rxjs'; -import { first } from 'rxjs/operators'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; - import { getStats, VisualizationUsage } from './get_usage_collector'; +import type { UsageCollectionSetup } from '../../../usage_collection/server'; -export function registerVisualizationsCollector( - collectorSet: UsageCollectionSetup, - config: Observable<{ kibana: { index: string } }> -) { +export function registerVisualizationsCollector(collectorSet: UsageCollectionSetup) { const collector = collectorSet.makeUsageCollector({ type: 'visualization_types', isReady: () => true, @@ -30,10 +24,7 @@ export function registerVisualizationsCollector( saved_90_days_total: { type: 'long' }, }, }, - fetch: async ({ esClient }) => { - const index = (await config.pipe(first()).toPromise()).kibana.index; - return await getStats(esClient, index); - }, + fetch: async ({ soClient }) => await getStats(soClient), }); collectorSet.registerCollector(collector); } diff --git a/test/accessibility/apps/discover.ts b/test/accessibility/apps/discover.ts index 6705598ef6e74d..c7794c5023bae0 100644 --- a/test/accessibility/apps/discover.ts +++ b/test/accessibility/apps/discover.ts @@ -19,8 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Discover a11y tests', () => { before(async () => { - await esArchiver.load('test/functional/fixtures/es_archiver/discover'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); await kibanaServer.uiSettings.update({ defaultIndex: 'logstash-*', 'doc_table:legacy': true, @@ -30,6 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); }); diff --git a/test/accessibility/apps/management.ts b/test/accessibility/apps/management.ts index 2fb3de63a81a74..69b799cc3b9e7b 100644 --- a/test/accessibility/apps/management.ts +++ b/test/accessibility/apps/management.ts @@ -22,8 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Management', () => { before(async () => { - await esArchiver.load('test/functional/fixtures/es_archiver/discover'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); await kibanaServer.uiSettings.update({ defaultIndex: 'logstash-*', }); @@ -31,6 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); }); diff --git a/test/accessibility/apps/visualize.ts b/test/accessibility/apps/visualize.ts index 6478907fcb96a6..d0592352170fb2 100644 --- a/test/accessibility/apps/visualize.ts +++ b/test/accessibility/apps/visualize.ts @@ -11,14 +11,18 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'visualize', 'header']); const a11y = getService('a11y'); - const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); describe('Visualize', () => { before(async () => { - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/discover'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); await PageObjects.common.navigateToApp('visualize'); }); + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + }); + it('visualize', async () => { await a11y.testAppSnapshot(); }); diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 473c04182f6975..2b30e558eb904f 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -91,6 +91,8 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { def fleetPackageRegistryPort = "64${parallelId}1" def alertingProxyPort = "64${parallelId}2" def corsTestServerPort = "64${parallelId}3" + // needed for https://github.com/elastic/kibana/issues/107246 + def proxyTestServerPort = "64${parallelId}4" def apmActive = githubPr.isPr() ? "false" : "true" withEnv([ @@ -103,6 +105,7 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", "TEST_ES_TRANSPORT_PORT=${esTransportPort}", "TEST_CORS_SERVER_PORT=${corsTestServerPort}", + "TEST_PROXY_SERVER_PORT=${proxyTestServerPort}", "KBN_NP_PLUGINS_BUILT=true", "FLEET_PACKAGE_REGISTRY_PORT=${fleetPackageRegistryPort}", "ALERTING_PROXY_PORT=${alertingProxyPort}", diff --git a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts index dc52d572e2f35f..4fc2dcefc9c5fd 100644 --- a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts +++ b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts @@ -78,7 +78,7 @@ export function registerApmAlerts( }, iconClass: 'bell', documentationUrl(docLinks) { - return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + return `${docLinks.links.alerting.apmRules}`; }, alertParamsExpression: lazy(() => import('./error_count_alert_trigger')), validate: () => ({ @@ -126,7 +126,7 @@ export function registerApmAlerts( }), iconClass: 'bell', documentationUrl(docLinks) { - return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + return `${docLinks.links.alerting.apmRules}`; }, alertParamsExpression: lazy( () => import('./transaction_duration_alert_trigger') @@ -177,7 +177,7 @@ export function registerApmAlerts( }), iconClass: 'bell', documentationUrl(docLinks) { - return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + return `${docLinks.links.alerting.apmRules}`; }, alertParamsExpression: lazy( () => import('./transaction_error_rate_alert_trigger') @@ -226,7 +226,7 @@ export function registerApmAlerts( }), iconClass: 'bell', documentationUrl(docLinks) { - return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; + return `${docLinks.links.alerting.apmRules}`; }, alertParamsExpression: lazy( () => import('./transaction_duration_anomaly_alert_trigger') diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.test.ts b/x-pack/plugins/apm/public/components/app/service_logs/index.test.ts new file mode 100644 index 00000000000000..b0cc134778d21d --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { getInfrastructureKQLFilter } from './'; + +describe('service logs', () => { + describe('getInfrastructureKQLFilter', () => { + it('filter by container id', () => { + expect( + getInfrastructureKQLFilter({ + serviceInfrastructure: { + containerIds: ['foo', 'bar'], + hostNames: ['baz', `quz`], + }, + }) + ).toEqual('container.id: "foo" or container.id: "bar"'); + }); + it('filter by host names', () => { + expect( + getInfrastructureKQLFilter({ + serviceInfrastructure: { + containerIds: [], + hostNames: ['baz', `quz`], + }, + }) + ).toEqual('host.name: "baz" or host.name: "quz"'); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx index e8ac370368365c..ac4a4fb51ce8aa 100644 --- a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx @@ -18,7 +18,6 @@ import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { CONTAINER_ID, HOSTNAME, - POD_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; @@ -55,8 +54,7 @@ export function ServiceLogs() { const noInfrastructureData = useMemo(() => { return ( isEmpty(data?.serviceInfrastructure?.containerIds) && - isEmpty(data?.serviceInfrastructure?.hostNames) && - isEmpty(data?.serviceInfrastructure?.podNames) + isEmpty(data?.serviceInfrastructure?.hostNames) ); }, [data]); @@ -93,16 +91,15 @@ export function ServiceLogs() { ); } -const getInfrastructureKQLFilter = ( +export const getInfrastructureKQLFilter = ( data?: APIReturnType<'GET /api/apm/services/{serviceName}/infrastructure'> ) => { const containerIds = data?.serviceInfrastructure?.containerIds ?? []; const hostNames = data?.serviceInfrastructure?.hostNames ?? []; - const podNames = data?.serviceInfrastructure?.podNames ?? []; - return [ - ...containerIds.map((id) => `${CONTAINER_ID}: "${id}"`), - ...hostNames.map((id) => `${HOSTNAME}: "${id}"`), - ...podNames.map((id) => `${POD_NAME}: "${id}"`), - ].join(' or '); + const kqlFilter = containerIds.length + ? containerIds.map((id) => `${CONTAINER_ID}: "${id}"`) + : hostNames.map((id) => `${HOSTNAME}: "${id}"`); + + return kqlFilter.join(' or '); }; diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index f3e2bba2d9789d..0d1ed4745d00d8 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -64,7 +64,7 @@ export const APM_FEATURE = { management: { insightsAndAlerting: ['triggersActions'], }, - ui: ['show', 'alerting:show', 'alerting:save'], + ui: ['show', 'alerting:show'], }, }, subFeatures: [ diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts index a9d2110117d897..fdcfffaf4bac1a 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts @@ -44,7 +44,6 @@ describe('get buckets', () => { get: () => 'myIndex', } ) as APMConfig, - uiFilters: {}, indices: { /* eslint-disable @typescript-eslint/naming-convention */ 'apm_oss.sourcemapIndices': 'apm-*', diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 7b5425321d239a..2f2435c21dc17f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -95,6 +95,7 @@ export function createApmEventClient({ ...withPossibleLegacyDataFilter, ignore_throttled: !includeFrozen, ignore_unavailable: true, + preference: 'any', }; // only "search" operation is currently supported diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts index 66b3c91fc6f2d1..00f3f15786ad61 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -131,6 +131,7 @@ describe('setupRequest', () => { }, ignore_unavailable: true, ignore_throttled: true, + preference: 'any', }); }); 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 ba67a42fbbade4..d2527b4a25d1d5 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -5,12 +5,10 @@ * 2.0. */ -import { Logger } from 'kibana/server'; import { isActivePlatinumLicense } from '../../../common/license_check'; import { APMConfig } from '../..'; import { KibanaRequest } from '../../../../../../src/core/server'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; -import { UxUIFilters } from '../../../typings/ui_filters'; import { APMRouteHandlerResources } from '../../routes/typings'; import { ApmIndicesConfig, @@ -35,7 +33,6 @@ export interface Setup { ml?: ReturnType; config: APMConfig; indices: ApmIndicesConfig; - uiFilters: UxUIFilters; } export interface SetupTimeRange { @@ -43,7 +40,7 @@ export interface SetupTimeRange { end: number; } -interface SetupRequestParams { +export interface SetupRequestParams { query: { _inspect?: boolean; @@ -56,7 +53,6 @@ interface SetupRequestParams { * Timestamp in ms since epoch */ end?: number; - uiFilters?: string; }; } @@ -74,7 +70,6 @@ export async function setupRequest({ plugins, request, config, - logger, }: APMRouteHandlerResources & { params: TParams; }): Promise> { @@ -91,8 +86,6 @@ export async function setupRequest({ ), ]); - const uiFilters = decodeUiFilters(logger, query.uiFilters); - const coreSetupRequest = { indices, apmEventClient: createApmEventClient({ @@ -116,7 +109,6 @@ export async function setupRequest({ ) : undefined, config, - uiFilters, }; return { @@ -138,18 +130,3 @@ function getMlSetup( modules: ml.modulesProvider(request, savedObjectsClient), }; } - -function decodeUiFilters( - logger: Logger, - uiFiltersEncoded?: string -): UxUIFilters { - if (!uiFiltersEncoded) { - return {}; - } - try { - return JSON.parse(uiFiltersEncoded); - } catch (error) { - logger.error(error); - return {}; - } -} 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 e56f234c0633ea..60f0eaff28e8ec 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 @@ -7,7 +7,8 @@ import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { TRANSACTION_TIME_TO_FIRST_BYTE, TRANSACTION_DURATION, @@ -18,7 +19,7 @@ export async function getClientMetrics({ urlQuery, percentile = 50, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; urlQuery?: string; percentile?: number; }) { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts b/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts index 6f734a214501d1..6967dd3d166081 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts @@ -6,7 +6,8 @@ */ import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { getRumErrorsProjection } from '../../projections/rum_page_load_transactions'; import { ERROR_EXC_MESSAGE, @@ -23,7 +24,7 @@ export async function getJSErrors({ pageIndex, urlQuery, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; pageSize: number; pageIndex: number; urlQuery?: string; diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts index c4c6f613172d14..751534272bd71f 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts @@ -7,7 +7,8 @@ import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; const LONG_TASK_SUM_FIELD = 'transaction.experience.longtask.sum'; const LONG_TASK_COUNT_FIELD = 'transaction.experience.longtask.count'; @@ -18,7 +19,7 @@ export async function getLongTaskMetrics({ urlQuery, percentile = 50, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; urlQuery?: string; percentile?: number; }) { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts index 73d634e3134d18..92b6eea76ab546 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts @@ -8,7 +8,8 @@ import { TRANSACTION_DURATION } from '../../../common/elasticsearch_fieldnames'; import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; export const MICRO_TO_SEC = 1000000; @@ -64,7 +65,7 @@ export async function getPageLoadDistribution({ maxPercentile, urlQuery, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; minPercentile?: string; maxPercentile?: string; urlQuery?: string; @@ -176,7 +177,7 @@ const getPercentilesDistribution = async ({ minDuration, maxDuration, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; minDuration: number; maxDuration: number; }) => { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts index 41af2ae166aaf6..3eae873f03918e 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts @@ -7,7 +7,8 @@ import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { BreakdownItem } from '../../../typings/ui_filters'; export async function getPageViewTrends({ @@ -15,7 +16,7 @@ export async function getPageViewTrends({ breakdowns, urlQuery, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; breakdowns?: string; urlQuery?: string; }) { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts index e63d834307a5f0..b26c76d3fe670a 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts @@ -8,7 +8,8 @@ import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { ProcessorEvent } from '../../../common/processor_event'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { CLIENT_GEO_COUNTRY_ISO_CODE, USER_AGENT_DEVICE, @@ -44,7 +45,7 @@ export const getPageLoadDistBreakdown = async ({ breakdown, urlQuery, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; minPercentile: number; maxPercentile: number; breakdown: string; diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts b/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts index a2e6b55738d3a1..42766c286035e4 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts @@ -4,16 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; export async function getRumServices({ setup, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; }) { const projection = getRumPageLoadTransactionsProjection({ setup, diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts index ae65cdbd121ea4..6d5d501ccb6b47 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts @@ -6,7 +6,8 @@ */ import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { TRANSACTION_DURATION, @@ -18,7 +19,7 @@ export async function getUrlSearch({ urlQuery, percentile, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; urlQuery?: string; percentile: number; }) { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts index 9c7a64d7c6481f..eab2ddaeb1cc58 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts @@ -7,7 +7,8 @@ import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { USER_AGENT_NAME, USER_AGENT_OS, @@ -17,7 +18,7 @@ export async function getVisitorBreakdown({ setup, urlQuery, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts index bbb301e22aa8de..c9046e01cdeb1d 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts @@ -7,7 +7,8 @@ import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { CLS_FIELD, FCP_FIELD, @@ -21,7 +22,7 @@ export async function getWebCoreVitals({ urlQuery, percentile = 50, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; urlQuery?: string; percentile?: number; }) { diff --git a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts index 28fab3369b1eb3..1d4fa3c31a2692 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { SetupTimeRange } from '../helpers/setup_request'; +import { SetupUX } from '../../routes/rum_client'; import { SERVICE_NAME, TRANSACTION_TYPE, @@ -17,7 +18,7 @@ import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; export async function hasRumData({ setup, }: { - setup: Setup & Partial; + setup: SetupUX & Partial; }) { try { const { start, end } = setup; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_infrastructure.ts b/x-pack/plugins/apm/server/lib/services/get_service_infrastructure.ts index b6621d590b17df..90be97d9497b2d 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_infrastructure.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_infrastructure.ts @@ -14,7 +14,6 @@ import { SERVICE_NAME, CONTAINER_ID, HOSTNAME, - POD_NAME, } from '../../../common/elasticsearch_fieldnames'; export const getServiceInfrastructure = async ({ @@ -61,12 +60,6 @@ export const getServiceInfrastructure = async ({ size: 500, }, }, - podNames: { - terms: { - field: POD_NAME, - size: 500, - }, - }, }, }, }); @@ -74,13 +67,11 @@ export const getServiceInfrastructure = async ({ return { containerIds: response.aggregations?.containerIds?.buckets.map( - (bucket) => bucket.key + (bucket) => bucket.key as string ) ?? [], hostNames: - response.aggregations?.hostNames?.buckets.map((bucket) => bucket.key) ?? - [], - podNames: - response.aggregations?.podNames?.buckets.map((bucket) => bucket.key) ?? - [], + response.aggregations?.hostNames?.buckets.map( + (bucket) => bucket.key as string + ) ?? [], }; }; diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts index b0a9b27ba771bd..73294a0bd12330 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts @@ -22,6 +22,7 @@ export async function getAnomalySeries({ serviceName, transactionType, transactionName, + kuery, setup, logger, }: { @@ -29,6 +30,7 @@ export async function getAnomalySeries({ serviceName: string; transactionType: string; transactionName?: string; + kuery: string; setup: Setup & SetupTimeRange; logger: Logger; }) { @@ -50,13 +52,8 @@ export async function getAnomalySeries({ return undefined; } - // Don't fetch anomalies if uiFilters are applied. This filters out anything - // with empty values so `kuery: ''` returns false but `kuery: 'x:y'` returns true. - const hasUiFiltersApplied = - Object.entries(setup.uiFilters).filter(([_key, value]) => !!value).length > - 0; - - if (hasUiFiltersApplied) { + // Don't fetch anomalies if kuery is present + if (kuery) { return undefined; } diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index b8cf92f15c7066..ac46a6df08e878 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; +import { SetupTimeRange } from '../../server/lib/helpers/setup_request'; +import { SetupUX } from '../routes/rum_client'; import { AGENT_NAME, TRANSACTION_TYPE, @@ -21,7 +22,7 @@ export function getRumPageLoadTransactionsProjection({ urlQuery, checkFetchStartFieldExists = true, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; urlQuery?: string; checkFetchStartFieldExists?: boolean; }) { @@ -72,7 +73,7 @@ export function getRumErrorsProjection({ setup, urlQuery, }: { - setup: Setup & SetupTimeRange; + setup: SetupUX & SetupTimeRange; urlQuery?: string; }) { const { start, end, uiFilters } = setup; diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts index c43917e32b7e32..f6f4aca3a1d9fc 100644 --- a/x-pack/plugins/apm/server/routes/rum_client.ts +++ b/x-pack/plugins/apm/server/routes/rum_client.ts @@ -4,10 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import * as t from 'io-ts'; +import { Logger } from 'kibana/server'; import { isoToEpochRt } from '@kbn/io-ts-utils'; -import { setupRequest } from '../lib/helpers/setup_request'; +import { + setupRequest, + Setup, + SetupRequestParams, +} from '../lib/helpers/setup_request'; import { getClientMetrics } from '../lib/rum_client/get_client_metrics'; import { getJSErrors } from '../lib/rum_client/get_js_errors'; import { getLongTaskMetrics } from '../lib/rum_client/get_long_task_metrics'; @@ -22,6 +26,18 @@ import { hasRumData } from '../lib/rum_client/has_rum_data'; import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; import { rangeRt } from './default_api_types'; +import { UxUIFilters } from '../../typings/ui_filters'; +import { APMRouteHandlerResources } from '../routes/typings'; + +export type SetupUX = Setup & { + uiFilters: UxUIFilters; +}; + +type SetupUXRequestParams = Omit & { + query: SetupRequestParams['query'] & { + uiFilters?: string; + }; +}; export const percentileRangeRt = t.partial({ minPercentile: t.string, @@ -43,7 +59,7 @@ const rumClientMetricsRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { urlQuery, percentile }, @@ -64,7 +80,7 @@ const rumPageLoadDistributionRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { minPercentile, maxPercentile, urlQuery }, @@ -92,7 +108,7 @@ const rumPageLoadDistBreakdownRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { minPercentile, maxPercentile, breakdown, urlQuery }, @@ -117,7 +133,7 @@ const rumPageViewsTrendRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { breakdowns, urlQuery }, @@ -138,7 +154,7 @@ const rumServicesRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const rumServices = await getRumServices({ setup }); return { rumServices }; @@ -152,7 +168,7 @@ const rumVisitorsBreakdownRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { urlQuery }, @@ -172,7 +188,7 @@ const rumWebCoreVitals = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { urlQuery, percentile }, @@ -193,7 +209,7 @@ const rumLongTaskMetrics = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { urlQuery, percentile }, @@ -214,7 +230,7 @@ const rumUrlSearch = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { urlQuery, percentile }, @@ -236,7 +252,7 @@ const rumJSErrors = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); const { query: { pageSize, pageIndex, urlQuery }, @@ -262,11 +278,39 @@ const rumHasDataRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const setup = await setupRequest(resources); + const setup = await setupUXRequest(resources); return await hasRumData({ setup }); }, }); +function decodeUiFilters( + logger: Logger, + uiFiltersEncoded?: string +): UxUIFilters { + if (!uiFiltersEncoded) { + return {}; + } + try { + return JSON.parse(uiFiltersEncoded); + } catch (error) { + logger.error(error); + return {}; + } +} + +async function setupUXRequest( + resources: APMRouteHandlerResources & { params: TParams } +) { + const setup = await setupRequest(resources); + return { + ...setup, + uiFilters: decodeUiFilters( + resources.logger, + resources.params.query.uiFilters + ), + }; +} + export const rumRouteRepository = createApmServerRouteRepository() .add(rumClientMetricsRoute) .add(rumPageLoadDistributionRoute) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/index.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/index.tsx index de90bc8c7cbef0..d956c6291c6096 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/index.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/index.tsx @@ -47,7 +47,7 @@ export const dropdownFilter: RendererFactory = () => ({ render(domNode, config, handlers) { let filterExpression = handlers.getFilter(); - if (filterExpression === undefined || filterExpression.indexOf('exactly')) { + if (filterExpression === undefined || !filterExpression.includes('exactly')) { filterExpression = ''; handlers.setFilter(filterExpression); } else if (filterExpression !== '') { diff --git a/x-pack/plugins/canvas/public/components/datatable/datatable.tsx b/x-pack/plugins/canvas/public/components/datatable/datatable.tsx index db21379dfed92c..5668c93c28b278 100644 --- a/x-pack/plugins/canvas/public/components/datatable/datatable.tsx +++ b/x-pack/plugins/canvas/public/components/datatable/datatable.tsx @@ -40,6 +40,8 @@ const getIcon = (type: DatatableColumnType | null) => { const getColumnName = (col: DatatableColumn) => (typeof col === 'string' ? col : col.name); +const getColumnId = (col: DatatableColumn) => (typeof col === 'string' ? col : col.id); + const getColumnType = (col: DatatableColumn) => col.meta?.type || null; const getFormattedValue = (val: any, type: any) => { @@ -85,7 +87,7 @@ export const Datatable: FC = ({ {datatable.columns.map((col) => ( - {getFormattedValue(row[getColumnName(col)], getColumnType(col))} + {getFormattedValue(row[getColumnId(col)], getColumnType(col))} ))} diff --git a/x-pack/plugins/canvas/public/components/workpad_app/index.ts b/x-pack/plugins/canvas/public/components/workpad_app/index.ts index af5dc1cf21adcd..f40f0a2644f824 100644 --- a/x-pack/plugins/canvas/public/components/workpad_app/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_app/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { WorkpadApp } from './workpad_app'; +export { WorkpadApp, WORKPAD_CONTAINER_ID } from './workpad_app'; export { WorkpadApp as WorkpadAppComponent } from './workpad_app.component'; diff --git a/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js b/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js index 132ee551c8b748..9e0627b2c089d6 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js @@ -12,8 +12,8 @@ import { selectToplevelNodes } from '../../state/actions/transient'; import { arrayToMap, flatten, identity } from '../../lib/aeroelastic/functional'; import { getLocalTransformMatrix } from '../../lib/aeroelastic/layout_functions'; import { matrixToAngle } from '../../lib/aeroelastic/matrix'; -import { isGroupId, elementToShape } from './utils'; -export * from './utils'; +import { isGroupId, elementToShape } from './positioning_utils'; +export * from './positioning_utils'; const shapeToElement = (shape) => ({ left: shape.transformMatrix[12] - shape.a, diff --git a/x-pack/plugins/canvas/public/components/workpad_page/utils.js b/x-pack/plugins/canvas/public/components/workpad_page/utils.js deleted file mode 100644 index 1cc2710b2d3d40..00000000000000 --- a/x-pack/plugins/canvas/public/components/workpad_page/utils.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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { multiply, rotateZ, translate } from '../../lib/aeroelastic/matrix'; - -export const isGroupId = (id) => id.startsWith('group'); - -const headerData = (id) => - isGroupId(id) - ? { id, type: 'group', subtype: 'persistentGroup' } - : { id, type: 'rectangleElement', subtype: '' }; - -const transformData = ({ top, left, width, height, angle }, z) => - multiply( - translate(left + width / 2, top + height / 2, z), // painter's algo: latest item (highest z) goes to top - rotateZ((-angle / 180) * Math.PI) // minus angle as transform:matrix3d uses a left-handed coordinate system - ); - -/** - * elementToShape - * - * converts a `kibana-canvas` element to an `aeroelastic` shape. - * - * Shape: the layout algorithms need to deal with objects through their geometric properties, excluding other aspects, - * such as what's inside the element, eg. image or scatter plot. This representation is, at its core, a transform matrix - * that establishes a new local coordinate system https://drafts.csswg.org/css-transforms/#local-coordinate-system plus a - * size descriptor. There are two versions of the transform matrix: - * - `transformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#current-transformation-matrix - * - `localTransformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#transformation-matrix - * - * Element: it also needs to represent the geometry, primarily because of the need to persist it in `redux` and on the - * server, and to accept such data from the server. The redux and server representations will need to change as more general - * projections such as 3D are added. The element also needs to maintain its content, such as an image or a plot. - * - * While all elements on the current page also exist as shapes, there are shapes that are not elements: annotations. - * For example, `rotation_handle`, `border_resize_handle` and `border_connection` are modeled as shapes by the layout - * library, simply for generality. - */ -export const elementToShape = ({ id, position }, z) => ({ - ...headerData(id), - parent: (position && position.parent) || null, - transformMatrix: transformData(position, z), - a: position.width / 2, // we currently specify half-width, half-height as it leads to - b: position.height / 2, // more regular math (like ellipsis radii rather than diameters) -}); - -const simplePosition = ({ id, position, filter }, z) => ({ - ...headerData(id), - width: position.width, - height: position.height, - transformMatrix: transformData(position, z), - filter, -}); - -export const simplePositioning = ({ elements }) => ({ elements: elements.map(simplePosition) }); diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.ts similarity index 58% rename from x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js rename to x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.ts index f0814172fe66f5..e8527089c9ac42 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/event_handlers.ts @@ -5,7 +5,27 @@ * 2.0. */ -const localMousePosition = (canvasOrigin, clientX, clientY, zoomScale = 1) => { +import { CommitFn } from '../../../lib/aeroelastic'; +import { WORKPAD_CONTAINER_ID } from '../../workpad_app/workpad_app.component'; + +type CanvasOriginFn = () => { left: number; top: number }; + +export interface Props { + commit: CommitFn | undefined; + canvasOrigin: CanvasOriginFn; + zoomScale: number; + canDragElement: (target: EventTarget | null) => boolean; +} + +const isInCanvas = (target: EventTarget | null) => + target instanceof Element && target.closest(`#${WORKPAD_CONTAINER_ID}`); + +const localMousePosition = ( + canvasOrigin: CanvasOriginFn, + clientX: number, + clientY: number, + zoomScale = 1 +) => { const { left, top } = canvasOrigin(); return { // commit unscaled coordinates @@ -19,12 +39,26 @@ const resetHandler = () => { window.onmouseup = null; }; -const setupHandler = (commit, canvasOrigin, zoomScale) => { +const setupHandler = (commit: CommitFn, canvasOrigin: CanvasOriginFn, zoomScale?: number) => { // Ancestor has to be identified on setup, rather than 1st interaction, otherwise events may be triggered on // DOM elements that had been removed: kibana-canvas github issue #1093 - window.onmousemove = ({ buttons, clientX, clientY, altKey, metaKey, shiftKey, ctrlKey }) => { + window.onmousemove = ({ + buttons, + clientX, + clientY, + altKey, + metaKey, + shiftKey, + ctrlKey, + target, + }: MouseEvent) => { + if (!isInCanvas(target)) { + return; + } + const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale); + // only commits the cursor position if there's a way to latch onto x/y calculation (canvasOrigin is knowable) // or if left button is being held down (i.e. an element is being dragged) if (buttons === 1 || canvasOrigin) { @@ -34,9 +68,15 @@ const setupHandler = (commit, canvasOrigin, zoomScale) => { commit('cursorPosition', {}); } }; - window.onmouseup = (e) => { + + window.onmouseup = (e: MouseEvent) => { + const { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey, target } = e; + + if (!isInCanvas(target)) { + return; + } + e.stopPropagation(); - const { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey } = e; const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale); commit('mouseEvent', { event: 'mouseUp', x, y, altKey, metaKey, shiftKey, ctrlKey }); resetHandler(); @@ -44,26 +84,46 @@ const setupHandler = (commit, canvasOrigin, zoomScale) => { }; const handleMouseMove = ( - commit, - { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey }, - canvasOrigin, - zoomScale + commit: CommitFn | undefined, + { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey, target }: MouseEvent, + canvasOrigin: CanvasOriginFn, + zoomScale?: number ) => { + if (!isInCanvas(target)) { + return; + } + const { x, y } = localMousePosition(canvasOrigin, clientX, clientY, zoomScale); + if (commit) { commit('cursorPosition', { x, y, altKey, metaKey, shiftKey, ctrlKey }); } }; -const handleMouseLeave = (commit, { buttons }) => { +const handleMouseLeave = (commit: CommitFn | undefined, { buttons, target }: MouseEvent) => { + if (!isInCanvas(target)) { + return; + } + if (buttons !== 1 && commit) { commit('cursorPosition', {}); // reset hover only if we're not holding down left key (ie. drag in progress) } }; -const handleMouseDown = (commit, e, canvasOrigin, zoomScale, allowDrag = true) => { +const handleMouseDown = ( + commit: CommitFn | undefined, + e: MouseEvent, + canvasOrigin: CanvasOriginFn, + zoomScale: number, + allowDrag = true +) => { + const { clientX, clientY, buttons, altKey, metaKey, shiftKey, ctrlKey, target } = e; + + if (!isInCanvas(target)) { + return; + } + e.stopPropagation(); - const { clientX, clientY, buttons, altKey, metaKey, shiftKey, ctrlKey } = e; if (buttons !== 1 || !commit) { resetHandler(); return; // left-click only @@ -82,7 +142,7 @@ const handleMouseDown = (commit, e, canvasOrigin, zoomScale, allowDrag = true) = }; export const eventHandlers = { - onMouseDown: (props) => (e) => + onMouseDown: (props: Props) => (e: MouseEvent) => handleMouseDown( props.commit, e, @@ -90,9 +150,10 @@ export const eventHandlers = { props.zoomScale, props.canDragElement(e.target) ), - onMouseMove: (props) => (e) => + onMouseMove: (props: Props) => (e: MouseEvent) => handleMouseMove(props.commit, e, props.canvasOrigin, props.zoomScale), - onMouseLeave: (props) => (e) => handleMouseLeave(props.commit, e), - onWheel: (props) => (e) => handleMouseMove(props.commit, e, props.canvasOrigin), + onMouseLeave: (props: Props) => (e: MouseEvent) => handleMouseLeave(props.commit, e), + onWheel: (props: Props) => (e: WheelEvent) => + handleMouseMove(props.commit, e, props.canvasOrigin), resetHandler: () => () => resetHandler(), }; diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js index 04cc3dcbfacc25..f03547df8de991 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/index.js @@ -243,17 +243,7 @@ export const InteractivePage = compose( })), withProps((...props) => ({ ...props, - canDragElement: (element) => { - return !isEmbeddableBody(element) && isInWorkpad(element); - - const hasClosest = typeof element.closest === 'function'; - - if (hasClosest) { - return !element.closest('.embeddable') || element.closest('.embPanel__header'); - } else { - return !closest.call(element, '.embeddable') || closest.call(element, '.embPanel__header'); - } - }, + canDragElement: (element) => !isEmbeddableBody(element) && isInWorkpad(element), })), withHandlers(eventHandlers), // Captures user intent, needs to have reconciled state () => InteractiveComponent diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx index a15458f4c7276a..db0aef22512c98 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx @@ -6,7 +6,6 @@ */ import React, { CSSProperties, PureComponent } from 'react'; -// @ts-expect-error untyped local import { WORKPAD_CONTAINER_ID } from '../../workpad_app'; interface State { diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/index.d.ts b/x-pack/plugins/canvas/public/lib/aeroelastic/index.d.ts index 4b5ff917fe7ebd..d959890e3e346a 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/index.d.ts +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/index.d.ts @@ -44,6 +44,8 @@ export type TypeName = string; export type Payload = JsonMap; export type UpdaterFunction = (arg: State) => State; +export type CommitFn = (type: TypeName, payload: Payload) => void; + export interface Store { getCurrentState: () => State; setCurrentState: (state: State) => void; diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js b/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js index f620de03bea4c1..a2f212704638a7 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/layout_functions.js @@ -819,7 +819,7 @@ const resizePointAnnotations = (config, parent, a, b) => ([x, y, cursorAngle]) = const xName = xNames[x]; const yName = yNames[y]; return { - id: [config.resizeHandleName, xName, yName, parent].join('_'), + id: [config.resizeHandleName, xName, yName, parent.id].join('_'), type: 'annotation', subtype: config.resizeHandleName, horizontalPosition: xName, diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/store.ts b/x-pack/plugins/canvas/public/lib/aeroelastic/store.ts index 7e6197fb160a3b..84e37968b784de 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/store.ts +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/store.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { ActionId, Payload, State, Store, TypeName, UpdaterFunction } from '.'; +import { ActionId, CommitFn, State, Store, UpdaterFunction } from '.'; let counter = 0 as ActionId; export const createStore = (initialState: State, updater: UpdaterFunction): Store => { let currentState = initialState; - const commit = (type: TypeName, payload: Payload) => { + const commit: CommitFn = (type, payload) => { return (currentState = updater({ ...currentState, primaryUpdate: { diff --git a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx index 6caabd04060cd6..01c0a633fbdb78 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx @@ -10,8 +10,7 @@ import React, { FC, PureComponent } from 'react'; import Style from 'style-it'; import { AnyExpressionFunctionDefinition } from '../../../../../src/plugins/expressions'; import { Positionable } from '../../public/components/positionable/positionable'; -// @ts-expect-error untyped local -import { elementToShape } from '../../public/components/workpad_page/utils'; +import { elementToShape } from '../../public/components/workpad_page/positioning_utils'; import { CanvasRenderedElement } from '../types'; import { CanvasShareableContext, useCanvasShareableState } from '../context'; import { AnyRendererSpec } from '../../types'; diff --git a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js index d0bdc292619d89..dc516060ea3608 100644 --- a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js +++ b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js @@ -116,6 +116,7 @@ module.exports = { { loader: 'sass-loader', options: { + implementation: require('node-sass'), sourceMap: !isProd, }, }, @@ -146,12 +147,13 @@ module.exports = { { loader: 'sass-loader', options: { - prependData(loaderContext) { + additionalData(content, loaderContext) { return `@import ${stringifyRequest( loaderContext, path.resolve(KIBANA_ROOT, 'src/core/public/core_app/styles/_globals_v7light.scss') - )};\n`; + )};\n${content}`; }, + implementation: require('node-sass'), webpackImporter: false, sassOptions: { outputStyle: 'nested', diff --git a/x-pack/plugins/canvas/storybook/main.ts b/x-pack/plugins/canvas/storybook/main.ts index a043efd7c87f52..5d77dd0fc85e69 100644 --- a/x-pack/plugins/canvas/storybook/main.ts +++ b/x-pack/plugins/canvas/storybook/main.ts @@ -38,6 +38,9 @@ const canvasWebpack = { }, { loader: 'sass-loader', + options: { + implementation: require('node-sass'), + }, }, ], }, diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts index 5107cbf8121d7a..67df65b2f12bfd 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -75,7 +75,7 @@ export const validatePackagePolicy = ( const packageVars = Object.entries(packagePolicy.vars || {}); if (packageVars.length) { validationResults.vars = packageVars.reduce((results, [name, varEntry]) => { - results[name] = validatePackagePolicyConfig(varEntry, packageVarsByName[name]); + results[name] = validatePackagePolicyConfig(varEntry, packageVarsByName[name], name); return results; }, {} as ValidationEntry); } @@ -138,7 +138,8 @@ export const validatePackagePolicy = ( results[name] = input.enabled ? validatePackagePolicyConfig( configEntry, - inputVarDefsByPolicyTemplateAndType[inputKey][name] + inputVarDefsByPolicyTemplateAndType[inputKey][name], + name ) : null; return results; @@ -161,7 +162,7 @@ export const validatePackagePolicy = ( (results, [name, configEntry]) => { results[name] = streamVarDefs && streamVarDefs[name] && input.enabled && stream.enabled - ? validatePackagePolicyConfig(configEntry, streamVarDefs[name]) + ? validatePackagePolicyConfig(configEntry, streamVarDefs[name], name) : null; return results; }, @@ -183,12 +184,14 @@ export const validatePackagePolicy = ( if (Object.entries(validationResults.inputs!).length === 0) { validationResults.inputs = null; } + return validationResults; }; export const validatePackagePolicyConfig = ( configEntry: PackagePolicyConfigRecordEntry, - varDef: RegistryVarsEntry + varDef: RegistryVarsEntry, + varName: string ): string[] | null => { const errors = []; const { value } = configEntry; @@ -198,6 +201,13 @@ export const validatePackagePolicyConfig = ( parsedValue = value.trim(); } + if (varDef === undefined) { + // eslint-disable-next-line no-console + console.debug(`No variable definition for ${varName} found`); + + return null; + } + if (varDef.required) { if (parsedValue === undefined || (typeof parsedValue === 'string' && !parsedValue)) { errors.push( diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx index 3b0ab9c62ca11c..3149a454c6c52c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx @@ -113,6 +113,24 @@ const breadcrumbGetters: { }), }, ], + upgrade_package_policy: ({ policyName, policyId }) => [ + BASE_BREADCRUMB, + { + href: pagePathGetters.policies()[1], + text: i18n.translate('xpack.fleet.breadcrumbs.policiesPageTitle', { + defaultMessage: 'Agent policies', + }), + }, + { + href: pagePathGetters.policy_details({ policyId })[1], + text: policyName, + }, + { + text: i18n.translate('xpack.fleet.breadcrumbs.upgradePacagePolicyPageTitle', { + defaultMessage: 'Upgrade integration ', + }), + }, + ], agent_list: () => [ BASE_BREADCRUMB, { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts index e204d86b51511f..bf75b05f41b8d0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts @@ -22,7 +22,11 @@ export const hasInvalidButRequiredVar = ( registryVar.required && (!packagePolicyVars || !packagePolicyVars[registryVar.name] || - validatePackagePolicyConfig(packagePolicyVars[registryVar.name], registryVar)?.length) + validatePackagePolicyConfig( + packagePolicyVars[registryVar.name], + registryVar, + registryVar.name + )?.length) ) ) ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 67cff3e2d03045..ea027f95eb9e80 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -525,15 +525,14 @@ export const EditPackagePolicyForm = memo<{ /> ) : ( <> - {from === 'package' || from === 'package-edit' ? ( - - ) : ( - - )} + {formState === 'CONFIRM' && ( setFormState('VALID')} /> )} - {isUpgrade && dryRunData && ( <> )} - {configurePackage} - {/* Extra space to accomodate the EuiBottomBar height */} - @@ -602,14 +597,56 @@ export const EditPackagePolicyForm = memo<{ ); }); -const PoliciesBreadcrumb: React.FunctionComponent<{ policyName: string; policyId: string }> = ({ - policyName, - policyId, -}) => { +const Breadcrumb = memo<{ + agentPolicyName: string; + from: EditPackagePolicyFrom; + packagePolicyName: string; + pkgkey: string; + pkgTitle: string; + policyId: string; +}>(({ agentPolicyName, from, packagePolicyName, pkgkey, pkgTitle, policyId }) => { + let breadcrumb = ; + + if ( + from === 'package' || + from === 'package-edit' || + from === 'upgrade-from-integrations-policy-list' + ) { + breadcrumb = ( + + ); + } else if (from === 'upgrade-from-fleet-policy-list') { + breadcrumb = ; + } + + return breadcrumb; +}); + +const IntegrationsBreadcrumb = memo<{ + pkgTitle: string; + policyName: string; + pkgkey: string; +}>(({ pkgTitle, policyName, pkgkey }) => { + useIntegrationsBreadcrumbs('integration_policy_edit', { policyName, pkgTitle, pkgkey }); + return null; +}); + +const PoliciesBreadcrumb: React.FunctionComponent<{ + policyName: string; + policyId: string; +}> = ({ policyName, policyId }) => { useBreadcrumbs('edit_integration', { policyName, policyId }); return null; }; +const UpgradeBreadcrumb: React.FunctionComponent<{ + policyName: string; + policyId: string; +}> = ({ policyName, policyId }) => { + useBreadcrumbs('upgrade_package_policy', { policyName, policyId }); + return null; +}; + const UpgradeStatusCallout: React.FunctionComponent<{ dryRunData: UpgradePackagePolicyDryRunResponse; }> = ({ dryRunData }) => { @@ -658,7 +695,7 @@ const UpgradeStatusCallout: React.FunctionComponent<{ )} - {isReadyForUpgrade ? ( + {isReadyForUpgrade && currentPackagePolicy ? ( ); }; - -const IntegrationsBreadcrumb = memo<{ - pkgTitle: string; - policyName: string; - pkgkey: string; -}>(({ pkgTitle, policyName, pkgkey }) => { - useIntegrationsBreadcrumbs('integration_policy_edit', { policyName, pkgTitle, pkgkey }); - return null; -}); 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 de399e068be078..5a6d8bb878c379 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -12,7 +12,7 @@ import { UnmountCallback } from 'src/core/public'; import { CloudSetup } from '../../../cloud/public'; import { ILicense } from '../../../licensing/public'; -import { KibanaContextProvider } from '../shared_imports'; +import { KibanaContextProvider, APP_WRAPPER_CLASS } from '../shared_imports'; import { App } from './app'; @@ -30,7 +30,7 @@ export const renderApp = ( ): UnmountCallback => { const { getUrlForApp } = application; render( - + diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index 7e9aa7a0d422b4..e191c4bd799a1c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -43,4 +43,6 @@ export { attemptToURIDecode } from '../../../../src/plugins/es_ui_shared/public' export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; +export { APP_WRAPPER_CLASS } from '../../../../src/core/public'; + export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/lens/common/suffix_formatter/index.ts b/x-pack/plugins/lens/common/suffix_formatter/index.ts index 00ae005c38b148..4fa6457f0125db 100644 --- a/x-pack/plugins/lens/common/suffix_formatter/index.ts +++ b/x-pack/plugins/lens/common/suffix_formatter/index.ts @@ -28,9 +28,11 @@ export const unitSuffixesLong: Record = { d: i18n.translate('xpack.lens.fieldFormats.longSuffix.d', { defaultMessage: 'per day' }), }; -export function getSuffixFormatter(formatFactory: FormatFactory): FieldFormatInstanceType { +export const suffixFormatterId = 'suffix'; + +export function getSuffixFormatter(getFormatFactory: () => FormatFactory): FieldFormatInstanceType { return class SuffixFormatter extends FieldFormat { - static id = 'suffix'; + static id = suffixFormatterId; static hidden = true; // Don't want this format to appear in index pattern editor static title = i18n.translate('xpack.lens.fieldFormats.suffix.title', { defaultMessage: 'Suffix', @@ -51,9 +53,10 @@ export function getSuffixFormatter(formatFactory: FormatFactory): FieldFormatIns const nestedFormatter = this.param('id'); const nestedParams = this.param('params'); - const formattedValue = formatFactory({ id: nestedFormatter, params: nestedParams }).convert( - val - ); + const formattedValue = getFormatFactory()({ + id: nestedFormatter, + params: nestedParams, + }).convert(val); // do not add suffixes to empty strings if (formattedValue === '') { diff --git a/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts b/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts index d08908ecde417f..9ab76b73cbb666 100644 --- a/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts +++ b/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts @@ -12,7 +12,7 @@ describe('suffix formatter', () => { it('should call nested formatter and apply suffix', () => { const convertMock = jest.fn((x) => x); const formatFactory = jest.fn(() => ({ convert: convertMock })); - const SuffixFormatter = getSuffixFormatter((formatFactory as unknown) as FormatFactory); + const SuffixFormatter = getSuffixFormatter(() => (formatFactory as unknown) as FormatFactory); const nestedParams = { abc: 123 }; const formatterInstance = new SuffixFormatter({ unit: 'h', @@ -30,7 +30,7 @@ describe('suffix formatter', () => { it('should not add suffix to empty strings', () => { const convertMock = jest.fn((x) => ''); const formatFactory = jest.fn(() => ({ convert: convertMock })); - const SuffixFormatter = getSuffixFormatter((formatFactory as unknown) as FormatFactory); + const SuffixFormatter = getSuffixFormatter(() => (formatFactory as unknown) as FormatFactory); const nestedParams = { abc: 123 }; const formatterInstance = new SuffixFormatter({ unit: 'h', @@ -46,7 +46,7 @@ describe('suffix formatter', () => { it('should be a hidden formatter', () => { const convertMock = jest.fn((x) => ''); const formatFactory = jest.fn(() => ({ convert: convertMock })); - const SuffixFormatter = getSuffixFormatter((formatFactory as unknown) as FormatFactory); + const SuffixFormatter = getSuffixFormatter(() => (formatFactory as unknown) as FormatFactory); expect(SuffixFormatter.hidden).toBe(true); }); }); diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 22da62c616c4be..8cb4a7c4c8433c 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -23,10 +23,10 @@ import { createMemoryHistory } from 'history'; import { esFilters, FilterManager, - IFieldType, IndexPattern, Query, } from '../../../../../src/plugins/data/public'; +import type { FieldSpec } from '../../../../../src/plugins/data/common'; import { TopNavMenuData } from '../../../../../src/plugins/navigation/public'; import { LensByValueInput } from '../embeddable/embeddable'; import { SavedObjectReference } from '../../../../../src/core/types'; @@ -146,7 +146,7 @@ describe('Lens App', () => { it('updates global filters with store state', async () => { const services = makeDefaultServices(sessionIdSubject); const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; + const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; const pinnedFilter = esFilters.buildExistsFilter(pinnedField, indexPattern); services.data.query.filterManager.getFilters = jest.fn().mockImplementation(() => { return []; @@ -644,8 +644,8 @@ describe('Lens App', () => { it('saves app filters and does not save pinned filters', async () => { const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as IFieldType; - const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; + const field = ({ name: 'myfield' } as unknown) as FieldSpec; + const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); await act(async () => { @@ -857,7 +857,7 @@ describe('Lens App', () => { it('updates the filters when the user changes them', async () => { const { instance, services, lensStore } = await mountWith({}); const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as IFieldType; + const field = ({ name: 'myfield' } as unknown) as FieldSpec; expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ filters: [], @@ -912,7 +912,7 @@ describe('Lens App', () => { }), }); const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as IFieldType; + const field = ({ name: 'myfield' } as unknown) as FieldSpec; act(() => services.data.query.filterManager.setFilters([ esFilters.buildExistsFilter(field, indexPattern), @@ -1047,8 +1047,8 @@ describe('Lens App', () => { }) ); const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as IFieldType; - const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; + const field = ({ name: 'myfield' } as unknown) as FieldSpec; + const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); @@ -1104,8 +1104,8 @@ describe('Lens App', () => { }) ); const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as IFieldType; - const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; + const field = ({ name: 'myfield' } as unknown) as FieldSpec; + const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 15f72bed582ee0..1ac8833f0f519b 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -37,6 +37,7 @@ import { navigateAway, LensRootStore, loadInitial, + LensAppState, LensState, } from '../state_management'; import { getPreloadedState } from '../state_management/lens_slice'; @@ -186,8 +187,9 @@ export async function mountApp( embeddableEditorIncomingState, initialContext, }; + const emptyState = getPreloadedState(storeDeps) as LensAppState; const lensStore: LensRootStore = makeConfigureStore(storeDeps, { - lens: getPreloadedState(storeDeps), + lens: emptyState, } as DeepPartial); const EditorRenderer = React.memo( @@ -200,7 +202,8 @@ export async function mountApp( ); trackUiEvent('loaded'); const initialInput = getInitialInput(props.id, props.editByValue); - lensStore.dispatch(loadInitial({ redirectCallback, initialInput })); + + lensStore.dispatch(loadInitial({ redirectCallback, initialInput, emptyState })); return ( diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index 45d3f959274f5d..0f99902e0b10ab 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -10,18 +10,18 @@ import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { partition } from 'lodash'; -import type { ChromeStart, NotificationsStart, SavedObjectReference } from 'kibana/public'; +import type { SavedObjectReference } from 'kibana/public'; import { SaveModal } from './save_modal'; import type { LensAppProps, LensAppServices } from './types'; import type { SaveProps } from './app'; import { Document, injectFilterReferences } from '../persistence'; import type { LensByReferenceInput, LensEmbeddableInput } from '../embeddable'; -import type { LensAttributeService } from '../lens_attribute_service'; -import { DataPublicPluginStart, esFilters } from '../../../../../src/plugins/data/public'; +import { esFilters } from '../../../../../src/plugins/data/public'; import { APP_ID, getFullPath, LENS_EMBEDDABLE_TYPE } from '../../common'; import { trackUiEvent } from '../lens_ui_telemetry'; import { checkForDuplicateTitle } from '../../../../../src/plugins/saved_objects/public'; import type { LensAppState } from '../state_management'; +import { getPersisted } from '../state_management/init_middleware/load_initial'; type ExtraProps = Pick & Partial>; @@ -51,54 +51,41 @@ export function SaveModalContainer({ redirectToOrigin, getAppNameFromId = () => undefined, isSaveable = true, - lastKnownDoc: initLastKnowDoc, + lastKnownDoc: initLastKnownDoc, lensServices, }: SaveModalContainerProps) { - const [lastKnownDoc, setLastKnownDoc] = useState(initLastKnowDoc); let title = ''; let description; let savedObjectId; + const [lastKnownDoc, setLastKnownDoc] = useState(initLastKnownDoc); if (lastKnownDoc) { title = lastKnownDoc.title; description = lastKnownDoc.description; savedObjectId = lastKnownDoc.savedObjectId; } - const { - attributeService, - notifications, - data, - chrome, - savedObjectsTagging, - application, - dashboardFeatureFlag, - } = lensServices; + const { attributeService, savedObjectsTagging, application, dashboardFeatureFlag } = lensServices; useEffect(() => { - setLastKnownDoc(initLastKnowDoc); - }, [initLastKnowDoc]); + setLastKnownDoc(initLastKnownDoc); + }, [initLastKnownDoc]); useEffect(() => { let isMounted = true; - async function loadPersistedDoc() { - if (initialInput) { - getPersistedDoc({ - data, - initialInput, - chrome, - notifications, - attributeService, - }).then((doc) => { - if (doc && isMounted) setLastKnownDoc(doc); - }); - } + + if (initialInput) { + getPersisted({ + initialInput, + lensServices, + }).then((persisted) => { + if (persisted?.doc && isMounted) setLastKnownDoc(persisted.doc); + }); } - loadPersistedDoc(); return () => { isMounted = false; }; - }, [chrome, data, initialInput, notifications, attributeService]); + }, [initialInput, lensServices]); const tagsIds = persistedDoc && savedObjectsTagging @@ -109,27 +96,25 @@ export function SaveModalContainer({ if (runSave) { // inside lens, we use the function that's passed to it runSave(saveProps, options); - } else { - if (attributeService && lastKnownDoc) { - runSaveLensVisualization( - { - ...lensServices, - lastKnownDoc, - initialInput, - attributeService, - redirectTo, - redirectToOrigin, - originatingApp, - getIsByValueMode: () => false, - onAppLeave: () => {}, - }, - saveProps, - options - ).then(() => { - onSave?.(); - onClose(); - }); - } + } else if (attributeService && lastKnownDoc) { + runSaveLensVisualization( + { + ...lensServices, + lastKnownDoc, + initialInput, + attributeService, + redirectTo, + redirectToOrigin, + originatingApp, + getIsByValueMode: () => false, + onAppLeave: () => {}, + }, + saveProps, + options + ).then(() => { + onSave?.(); + onClose(); + }); } }; @@ -384,51 +369,5 @@ export function getLastKnownDocWithoutPinnedFilters(doc?: Document) { : doc; } -export const getPersistedDoc = async ({ - initialInput, - attributeService, - data, - notifications, - chrome, -}: { - initialInput: LensEmbeddableInput; - attributeService: LensAttributeService; - data: DataPublicPluginStart; - notifications: NotificationsStart; - chrome: ChromeStart; -}): Promise => { - let doc: Document; - - try { - const attributes = await attributeService.unwrapAttributes(initialInput); - - doc = { - ...initialInput, - ...attributes, - type: LENS_EMBEDDABLE_TYPE, - }; - - if (attributeService.inputIsRefType(initialInput)) { - chrome.recentlyAccessed.add( - getFullPath(initialInput.savedObjectId), - attributes.title, - initialInput.savedObjectId - ); - } - - // Don't overwrite any pinned filters - data.query.filterManager.setAppFilters( - injectFilterReferences(doc.state.filters, doc.references) - ); - return doc; - } catch (e) { - notifications.toasts.addDanger( - i18n.translate('xpack.lens.app.docLoadingError', { - defaultMessage: 'Error loading saved document', - }) - ); - } -}; - // eslint-disable-next-line import/no-default-export export default SaveModalContainer; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index fff9fe1372a28f..7c5fd4f5b88453 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -230,7 +230,7 @@ describe('editor_frame', () => { await mountWithProvider(, { data: props.plugins.data, preloadedState: { - visualization: { activeId: 'testVis', state: null }, + visualization: { activeId: 'testVis', state: {} }, datasourceStates: { testDatasource: { isLoading: false, @@ -285,7 +285,7 @@ describe('editor_frame', () => { await mountWithProvider(, { data: props.plugins.data, preloadedState: { - visualization: { activeId: 'testVis', state: null }, + visualization: { activeId: 'testVis', state: {} }, datasourceStates: { testDatasource: { isLoading: false, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 7700bc708fc164..3b55c4923f9676 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -45,7 +45,8 @@ export function EditorFrame(props: EditorFrameProps) { const activeDatasourceId = useLensSelector(selectActiveDatasourceId); const datasourceStates = useLensSelector(selectDatasourceStates); const visualization = useLensSelector(selectVisualization); - const allLoaded = useLensSelector(selectAreDatasourcesLoaded); + const areDatasourcesLoaded = useLensSelector(selectAreDatasourcesLoaded); + const isVisualizationLoaded = !!visualization.state; const framePublicAPI: FramePublicAPI = useLensSelector((state) => selectFramePublicAPI(state, datasourceMap) ); @@ -95,7 +96,7 @@ export function EditorFrame(props: EditorFrameProps) { /> } configPanel={ - allLoaded && ( + areDatasourcesLoaded && ( { expect(expressionRendererMock).toHaveBeenCalledTimes(1); const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as IFieldType; + const field = ({ name: 'myfield' } as unknown) as FieldSpec; await act(async () => { instance.setProps({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index 59aebc517bf22a..a6828bf9d58739 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -664,7 +664,14 @@ describe('IndexPattern Data Panel', () => { ...props.indexPatterns['1'], fields: [ ...props.indexPatterns['1'].fields, - { name: '_id', displayName: '_id', meta: true, type: 'string' }, + { + name: '_id', + displayName: '_id', + meta: true, + type: 'string', + searchable: true, + aggregatable: true, + }, ], }, }} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 3c00241cd7eda4..9ff80f51bea97c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -6,7 +6,7 @@ */ import type { CoreSetup } from 'kibana/public'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { createStartServicesGetter, Storage } from '../../../../../src/plugins/kibana_utils/public'; import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import type { IndexPatternFieldEditorStart } from '../../../../../src/plugins/index_pattern_field_editor/public'; @@ -14,7 +14,7 @@ import type { DataPublicPluginSetup, DataPublicPluginStart, } from '../../../../../src/plugins/data/public'; -import type { Datasource, EditorFrameSetup } from '../types'; +import type { EditorFrameSetup } from '../types'; import type { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import type { FieldFormatsStart, @@ -57,29 +57,37 @@ export class IndexPatternDatasource { counterRate, getTimeScale, getSuffixFormatter, + suffixFormatterId, } = await import('../async_services'); - return core - .getStartServices() - .then(([coreStart, { indexPatternFieldEditor, uiActions, data, fieldFormats }]) => { - const suffixFormatter = getSuffixFormatter(fieldFormats.deserialize); - if (!fieldFormats.has(suffixFormatter.id)) { - // todo: this code should be executed on setup phase. - fieldFormatsSetup.register([suffixFormatter]); - } - expressions.registerFunction(getTimeScale(() => getTimeZone(core.uiSettings))); - expressions.registerFunction(counterRate); - expressions.registerFunction(renameColumns); - expressions.registerFunction(formatColumn); - return getIndexPatternDatasource({ - core: coreStart, - fieldFormats, - storage: new Storage(localStorage), - data, - charts, - indexPatternFieldEditor, - uiActions, - }); - }) as Promise; + + if (!fieldFormatsSetup.has(suffixFormatterId)) { + const startServices = createStartServicesGetter(core.getStartServices); + const suffixFormatter = getSuffixFormatter( + () => startServices().plugins.fieldFormats.deserialize + ); + + fieldFormatsSetup.register([suffixFormatter]); + } + + expressions.registerFunction(getTimeScale(() => getTimeZone(core.uiSettings))); + expressions.registerFunction(counterRate); + expressions.registerFunction(renameColumns); + expressions.registerFunction(formatColumn); + + const [ + coreStart, + { indexPatternFieldEditor, uiActions, data, fieldFormats }, + ] = await core.getStartServices(); + + return getIndexPatternDatasource({ + core: coreStart, + fieldFormats, + storage: new Storage(localStorage), + data, + charts, + indexPatternFieldEditor, + uiActions, + }); }); } } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index fffbf0cba34d72..8f66bcf7fe49c3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -76,7 +76,11 @@ export { counterRate, } from '../../common/expressions'; export { FormatColumnArgs, supportedFormats, formatColumn } from '../../common/expressions'; -export { getSuffixFormatter, unitSuffixesLong } from '../../common/suffix_formatter'; +export { + getSuffixFormatter, + unitSuffixesLong, + suffixFormatterId, +} from '../../common/suffix_formatter'; export { getTimeScale, TimeScaleArgs } from '../../common/expressions'; export { renameColumns } from '../../common/expressions'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx index 118405baebc8bc..c4a88617c24b71 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx @@ -81,6 +81,7 @@ describe('percentile', () => { displayName: 'bytes', type: 'number', esTypes: ['long'], + searchable: true, aggregatable: true, }) ).toEqual({ @@ -97,6 +98,7 @@ describe('percentile', () => { displayName: 'response_time', type: 'histogram', esTypes: ['histogram'], + searchable: true, aggregatable: true, }) ).toEqual({ @@ -113,6 +115,7 @@ describe('percentile', () => { displayName: 'origin', type: 'string', esTypes: ['keyword'], + searchable: true, aggregatable: true, }) ).toBeUndefined(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index 92565d1590ea3b..aef086a6ee2880 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -96,9 +96,23 @@ const defaultOptions = { id: '1', title: 'my_index_pattern', hasRestrictions: false, - fields: [{ name: sourceField, type: 'number', displayName: sourceField }], + fields: [ + { + name: sourceField, + type: 'number', + displayName: sourceField, + searchable: true, + aggregatable: true, + }, + ], getFieldByName: getFieldByNameFactory([ - { name: sourceField, type: 'number', displayName: sourceField }, + { + name: sourceField, + type: 'number', + displayName: sourceField, + searchable: true, + aggregatable: true, + }, ]), }, operationDefinitionMap: {}, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 1a3451bdb403b4..72acc114ca4b2c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { IFieldType } from 'src/plugins/data/common'; -import { IndexPatternColumn, IncompleteColumn } from './operations'; -import { IndexPatternAggRestrictions } from '../../../../../src/plugins/data/public'; -import { DragDropIdentifier } from '../drag_drop/providers'; +import type { IndexPatternColumn, IncompleteColumn } from './operations'; +import type { IndexPatternAggRestrictions } from '../../../../../src/plugins/data/public'; +import type { FieldSpec } from '../../../../../src/plugins/data/common'; +import type { DragDropIdentifier } from '../drag_drop/providers'; export { FieldBasedIndexPatternColumn, @@ -57,7 +57,7 @@ export interface IndexPattern { hasRestrictions: boolean; } -export type IndexPatternField = IFieldType & { +export type IndexPatternField = FieldSpec & { displayName: string; aggregationRestrictions?: Partial; meta?: boolean; diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx index a88831dda7ba9e..b2c8d3948b2856 100644 --- a/x-pack/plugins/lens/public/mocks.tsx +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -208,12 +208,14 @@ export const defaultDoc = ({ savedObjectId: '1234', title: 'An extremely cool default document!', expression: 'definitely a valid expression', + visualizationType: 'testVis', state: { query: 'kuery', filters: [{ query: { match_phrase: { src: 'test' } } }], datasourceStates: { testDatasource: 'datasource', }, + visualization: {}, }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], } as unknown) as Document; diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/index.ts b/x-pack/plugins/lens/public/state_management/init_middleware/index.ts index 854e08dfe83e47..bf13ca69e82c0d 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/index.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/index.ts @@ -23,7 +23,8 @@ export const initMiddleware = (storeDeps: LensStoreDeps) => (store: MiddlewareAP store, storeDeps, action.payload.redirectCallback, - action.payload.initialInput + action.payload.initialInput, + action.payload.emptyState ); } else if (lensSlice.actions.navigateAway.match(action)) { return unsubscribeFromExternalContext(); diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx index 9f653c2ff9f86e..79402b698af98b 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx @@ -15,6 +15,8 @@ import { import { act } from 'react-dom/test-utils'; import { loadInitial } from './load_initial'; import { LensEmbeddableInput } from '../../embeddable'; +import { getPreloadedState } from '../lens_slice'; +import { LensAppState } from '..'; const defaultSavedObjectId = '1234'; const preloadedState = { @@ -63,7 +65,6 @@ describe('Mounter', () => { it('should initialize initial datasource', async () => { const services = makeDefaultServices(); - const redirectCallback = jest.fn(); services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc); const lensStore = await makeLensStore({ @@ -78,7 +79,7 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback, + jest.fn(), ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput ); }); @@ -87,7 +88,6 @@ describe('Mounter', () => { it('should have initialized only the initial datasource and visualization', async () => { const services = makeDefaultServices(); - const redirectCallback = jest.fn(); services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc); const lensStore = await makeLensStore({ data: services.data, preloadedState }); @@ -99,7 +99,7 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback + jest.fn() ); }); expect(mockDatasource.initialize).toHaveBeenCalled(); @@ -121,7 +121,6 @@ describe('Mounter', () => { describe('loadInitial', () => { it('does not load a document if there is no initial input', async () => { const services = makeDefaultServices(); - const redirectCallback = jest.fn(); const lensStore = makeLensStore({ data: services.data, preloadedState }); await loadInitial( lensStore, @@ -130,14 +129,66 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback + jest.fn() ); expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled(); }); + it('cleans datasource and visualization state properly when reloading', async () => { + const services = makeDefaultServices(); + const storeDeps = { + lensServices: services, + datasourceMap, + visualizationMap, + }; + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc); + const lensStore = await makeLensStore({ + data: services.data, + preloadedState: { + ...preloadedState, + visualization: { + activeId: 'testVis', + state: {}, + }, + datasourceStates: { testDatasource: { isLoading: false, state: {} } }, + }, + }); + + expect(lensStore.getState()).toEqual({ + lens: expect.objectContaining({ + visualization: { + activeId: 'testVis', + state: {}, + }, + activeDatasourceId: 'testDatasource', + datasourceStates: { + testDatasource: { isLoading: false, state: {} }, + }, + }), + }); + + const emptyState = getPreloadedState(storeDeps) as LensAppState; + services.attributeService.unwrapAttributes = jest.fn(); + await act(async () => { + await loadInitial(lensStore, storeDeps, jest.fn(), undefined, emptyState); + }); + + expect(lensStore.getState()).toEqual({ + lens: expect.objectContaining({ + visualization: { + activeId: 'testVis', + state: null, // resets to null + }, + activeDatasourceId: 'testDatasource2', // resets to first on the list + datasourceStates: { + testDatasource: { isLoading: false, state: undefined }, // state resets to undefined + }, + }), + }); + }); + it('loads a document and uses query and filters if initial input is provided', async () => { const services = makeDefaultServices(); - const redirectCallback = jest.fn(); services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc); const lensStore = await makeLensStore({ data: services.data, preloadedState }); @@ -149,7 +200,7 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback, + jest.fn(), ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput ); }); @@ -173,7 +224,6 @@ describe('Mounter', () => { }); it('does not load documents on sequential renders unless the id changes', async () => { - const redirectCallback = jest.fn(); const services = makeDefaultServices(); const lensStore = makeLensStore({ data: services.data, preloadedState }); @@ -185,7 +235,7 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback, + jest.fn(), ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput ); }); @@ -198,7 +248,7 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback, + jest.fn(), ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput ); }); @@ -213,7 +263,7 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback, + jest.fn(), ({ savedObjectId: '5678' } as unknown) as LensEmbeddableInput ); }); @@ -249,8 +299,6 @@ describe('Mounter', () => { }); it('adds to the recently accessed list on load', async () => { - const redirectCallback = jest.fn(); - const services = makeDefaultServices(); const lensStore = makeLensStore({ data: services.data, preloadedState }); await act(async () => { @@ -261,7 +309,7 @@ describe('Mounter', () => { datasourceMap, visualizationMap, }, - redirectCallback, + jest.fn(), ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput ); }); diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 5aeeec81e29b06..0be2bc9cfc00ea 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -7,7 +7,8 @@ import { MiddlewareAPI } from '@reduxjs/toolkit'; import { isEqual } from 'lodash'; -import { setState } from '..'; +import { i18n } from '@kbn/i18n'; +import { LensAppState, setState } from '..'; import { updateLayer, updateVisualizationState, LensStoreDeps } from '..'; import { LensEmbeddableInput, LensByReferenceInput } from '../../embeddable/embeddable'; import { getInitialDatasourceId } from '../../utils'; @@ -17,7 +18,40 @@ import { getVisualizeFieldSuggestions, switchToSuggestion, } from '../../editor_frame_service/editor_frame/suggestion_helpers'; -import { getPersistedDoc } from '../../app_plugin/save_modal_container'; +import { LensAppServices } from '../../app_plugin/types'; +import { getFullPath, LENS_EMBEDDABLE_TYPE } from '../../../common/constants'; +import { Document, injectFilterReferences } from '../../persistence'; + +export const getPersisted = async ({ + initialInput, + lensServices, +}: { + initialInput: LensEmbeddableInput; + lensServices: LensAppServices; +}): Promise<{ doc: Document } | undefined> => { + const { notifications, attributeService } = lensServices; + let doc: Document; + + try { + const attributes = await attributeService.unwrapAttributes(initialInput); + + doc = { + ...initialInput, + ...attributes, + type: LENS_EMBEDDABLE_TYPE, + }; + + return { + doc, + }; + } catch (e) { + notifications.toasts.addDanger( + i18n.translate('xpack.lens.app.docLoadingError', { + defaultMessage: 'Error loading saved document', + }) + ); + } +}; export function loadInitial( store: MiddlewareAPI, @@ -29,11 +63,13 @@ export function loadInitial( initialContext, }: LensStoreDeps, redirectCallback: (savedObjectId?: string) => void, - initialInput?: LensEmbeddableInput + initialInput?: LensEmbeddableInput, + emptyState?: LensAppState ) { const { getState, dispatch } = store; - const { attributeService, chrome, notifications, data, dashboardFeatureFlag } = lensServices; + const { attributeService, notifications, data, dashboardFeatureFlag } = lensServices; const { persistedDoc } = getState().lens; + if ( !initialInput || (attributeService.inputIsRefType(initialInput) && @@ -61,6 +97,7 @@ export function loadInitial( ); dispatch( setState({ + ...emptyState, datasourceStates, isLoading: false, }) @@ -109,17 +146,23 @@ export function loadInitial( redirectCallback(); }); } - getPersistedDoc({ - initialInput, - attributeService, - data, - chrome, - notifications, - }) + getPersisted({ initialInput, lensServices }) .then( - (doc) => { - if (doc) { - const currentSessionId = data.search.session.getSessionId(); + (persisted) => { + if (persisted) { + const { doc } = persisted; + if (attributeService.inputIsRefType(initialInput)) { + lensServices.chrome.recentlyAccessed.add( + getFullPath(initialInput.savedObjectId), + doc.title, + initialInput.savedObjectId + ); + } + // Don't overwrite any pinned filters + data.query.filterManager.setAppFilters( + injectFilterReferences(doc.state.filters, doc.references) + ); + const docDatasourceStates = Object.entries(doc.state.datasourceStates).reduce( (stateMap, [datasourceId, datasourceState]) => ({ ...stateMap, @@ -143,6 +186,8 @@ export function loadInitial( .then((result) => { const activeDatasourceId = getInitialDatasourceId(datasourceMap, doc); + const currentSessionId = data.search.session.getSessionId(); + dispatch( setState({ query: doc.state.query, diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 21624ae99cefc9..85cb79f6ea5da6 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -12,6 +12,7 @@ import { getInitialDatasourceId, getResolvedDateRange } from '../utils'; import { LensAppState, LensStoreDeps } from './types'; export const initialState: LensAppState = { + persistedDoc: undefined, searchSessionId: '', filters: [], query: { language: 'kuery', query: '' }, @@ -299,6 +300,7 @@ export const lensSlice = createSlice({ payload: PayloadAction<{ initialInput?: LensEmbeddableInput; redirectCallback: (savedObjectId?: string) => void; + emptyState: LensAppState; }> ) => state, }, diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 1a3065cb4518d5..5cfed7d6a58b5c 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -92,7 +92,6 @@ export enum SOURCE_TYPES { EMS_XYZ = 'EMS_XYZ', // identifies a custom TMS source. EMS-prefix in the name is a little unfortunate :( WMS = 'WMS', KIBANA_TILEMAP = 'KIBANA_TILEMAP', - REGIONMAP_FILE = 'REGIONMAP_FILE', GEOJSON_FILE = 'GEOJSON_FILE', MVT_SINGLE_LAYER = 'MVT_SINGLE_LAYER', TABLE_SOURCE = 'TABLE_SOURCE', @@ -131,7 +130,7 @@ export enum ES_GEO_FIELD_TYPE { GEO_SHAPE = 'geo_shape', } -// Using strings instead of ES_GEO_FIELD_TYPE enum to avoid typeing errors where IFieldType.type is compared to value +// Using strings instead of ES_GEO_FIELD_TYPE enum to avoid typeing errors where IndexPatternField.type is compared to value export const ES_GEO_FIELD_TYPES = ['geo_point', 'geo_shape']; export enum ES_SPATIAL_RELATIONS { diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index 06afe34bb73fe5..9a2af711ea2c7c 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -112,10 +112,6 @@ export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & { type: SOURCE_TYPES.ES_TERM_SOURCE; }; -export type KibanaRegionmapSourceDescriptor = AbstractSourceDescriptor & { - name: string; -}; - // This is for symmetry with other sources only. // It takes no additional configuration since all params are in the .yml. export type KibanaTilemapSourceDescriptor = AbstractSourceDescriptor; diff --git a/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts index cae5f6ee9f41a5..55600ca307ff2b 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts @@ -7,13 +7,13 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { IndexPattern, IFieldType } from '../../../../../src/plugins/data/common'; +import type { IndexPattern, IndexPatternField } from 'src/plugins/data/common'; import { AGG_TYPE, JOIN_FIELD_NAME_PREFIX, TOP_TERM_PERCENTAGE_SUFFIX } from '../constants'; export type BucketProperties = Record; export type PropertiesMap = Map; -export function getField(indexPattern: IndexPattern, fieldName: string): IFieldType { +export function getField(indexPattern: IndexPattern, fieldName: string): IndexPatternField { const field = indexPattern.fields.getByName(fieldName); if (!field) { throw new Error( @@ -26,7 +26,7 @@ export function getField(indexPattern: IndexPattern, fieldName: string): IFieldT return field; } -export function addFieldToDSL(dsl: object, field: IFieldType) { +export function addFieldToDSL(dsl: object, field: IndexPatternField) { return !field.scripted ? { ...dsl, field: field.name } : { diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.js b/x-pack/plugins/maps/public/actions/map_actions.test.ts similarity index 68% rename from x-pack/plugins/maps/public/actions/map_actions.test.js rename to x-pack/plugins/maps/public/actions/map_actions.test.ts index 763ce459dcc252..d222d8e5b04662 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.test.js +++ b/x-pack/plugins/maps/public/actions/map_actions.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint @typescript-eslint/no-var-requires: 0 */ + jest.mock('../selectors/map_selectors', () => ({})); jest.mock('./data_request_actions', () => { return { @@ -30,7 +32,7 @@ describe('map_actions', () => { }); describe('mapExtentChanged', () => { - describe('store mapState is empty', () => { + describe('mapState.buffer is undefined', () => { beforeEach(() => { require('../selectors/map_selectors').getDataFilters = () => { return { @@ -43,20 +45,12 @@ describe('map_actions', () => { }; }); - it('should add newMapConstants to dispatch action mapState', async () => { - const action = mapExtentChanged({ zoom: 5 }); - await action(dispatchMock, getStoreMock); - - expect(dispatchMock).toHaveBeenCalledWith({ - mapState: { - zoom: 5, - }, - type: 'MAP_EXTENT_CHANGED', - }); - }); - - it('should add buffer to dispatch action mapState', async () => { + it('should set buffer', () => { const action = mapExtentChanged({ + center: { + lat: 7.5, + lon: 97.5, + }, extent: { maxLat: 10, maxLon: 100, @@ -65,30 +59,36 @@ describe('map_actions', () => { }, zoom: 5, }); - await action(dispatchMock, getStoreMock); + action(dispatchMock, getStoreMock); - expect(dispatchMock).toHaveBeenCalledWith({ - mapState: { - zoom: 5, - extent: { - maxLat: 10, - maxLon: 100, - minLat: 5, - minLon: 95, - }, - buffer: { - maxLat: 11.1784, - maxLon: 101.25, - minLat: 0, - minLon: 90, + expect(dispatchMock.mock.calls[0]).toEqual([ + { + mapViewContext: { + center: { + lat: 7.5, + lon: 97.5, + }, + zoom: 5, + extent: { + maxLat: 10, + maxLon: 100, + minLat: 5, + minLon: 95, + }, + buffer: { + maxLat: 11.1784, + maxLon: 101.25, + minLat: 0, + minLon: 90, + }, }, + type: 'MAP_EXTENT_CHANGED', }, - type: 'MAP_EXTENT_CHANGED', - }); + ]); }); }); - describe('store mapState is populated', () => { + describe('mapState.buffer is defined', () => { const initialZoom = 10; beforeEach(() => { require('../selectors/map_selectors').getDataFilters = () => { @@ -104,8 +104,12 @@ describe('map_actions', () => { }; }); - it('should not update buffer if extent is contained in existing buffer', async () => { + it('should not update buffer if extent is contained in existing buffer', () => { const action = mapExtentChanged({ + center: { + lat: 8.5, + lon: 98.5, + }, zoom: initialZoom, extent: { maxLat: 11, @@ -114,30 +118,40 @@ describe('map_actions', () => { minLon: 96, }, }); - await action(dispatchMock, getStoreMock); - - expect(dispatchMock).toHaveBeenCalledWith({ - mapState: { - zoom: 10, - extent: { - maxLat: 11, - maxLon: 101, - minLat: 6, - minLon: 96, - }, - buffer: { - maxLat: 12.5, - maxLon: 102.5, - minLat: 2.5, - minLon: 92.5, + action(dispatchMock, getStoreMock); + + expect(dispatchMock.mock.calls[0]).toEqual([ + { + mapViewContext: { + center: { + lat: 8.5, + lon: 98.5, + }, + zoom: 10, + extent: { + maxLat: 11, + maxLon: 101, + minLat: 6, + minLon: 96, + }, + buffer: { + maxLat: 12.5, + maxLon: 102.5, + minLat: 2.5, + minLon: 92.5, + }, }, + type: 'MAP_EXTENT_CHANGED', }, - type: 'MAP_EXTENT_CHANGED', - }); + ]); }); - it('should update buffer if extent is outside of existing buffer', async () => { + it('should update buffer if extent is outside of existing buffer', () => { const action = mapExtentChanged({ + center: { + lat: 2.5, + lon: 87.5, + }, zoom: initialZoom, extent: { maxLat: 5, @@ -146,30 +160,40 @@ describe('map_actions', () => { minLon: 85, }, }); - await action(dispatchMock, getStoreMock); - - expect(dispatchMock).toHaveBeenCalledWith({ - mapState: { - zoom: 10, - extent: { - maxLat: 5, - maxLon: 90, - minLat: 0, - minLon: 85, - }, - buffer: { - maxLat: 5.26601, - maxLon: 90.35156, - minLat: -0.35156, - minLon: 84.72656, + action(dispatchMock, getStoreMock); + + expect(dispatchMock.mock.calls[0]).toEqual([ + { + mapViewContext: { + center: { + lat: 2.5, + lon: 87.5, + }, + zoom: 10, + extent: { + maxLat: 5, + maxLon: 90, + minLat: 0, + minLon: 85, + }, + buffer: { + maxLat: 5.26601, + maxLon: 90.35156, + minLat: -0.35156, + minLon: 84.72656, + }, }, + type: 'MAP_EXTENT_CHANGED', }, - type: 'MAP_EXTENT_CHANGED', - }); + ]); }); - it('should update buffer when zoom changes', async () => { + it('should update buffer when zoom changes', () => { const action = mapExtentChanged({ + center: { + lat: 8.5, + lon: 98.5, + }, zoom: initialZoom + 1, extent: { maxLat: 11, @@ -178,26 +202,32 @@ describe('map_actions', () => { minLon: 96, }, }); - await action(dispatchMock, getStoreMock); - - expect(dispatchMock).toHaveBeenCalledWith({ - mapState: { - zoom: 11, - extent: { - maxLat: 11, - maxLon: 101, - minLat: 6, - minLon: 96, - }, - buffer: { - maxLat: 11.0059, - maxLon: 101.07422, - minLat: 5.96575, - minLon: 95.97656, + action(dispatchMock, getStoreMock); + + expect(dispatchMock.mock.calls[0]).toEqual([ + { + mapViewContext: { + center: { + lat: 8.5, + lon: 98.5, + }, + zoom: 11, + extent: { + maxLat: 11, + maxLon: 101, + minLat: 6, + minLon: 96, + }, + buffer: { + maxLat: 11.0059, + maxLon: 101.07422, + minLat: 5.96575, + minLon: 95.97656, + }, }, + type: 'MAP_EXTENT_CHANGED', }, - type: 'MAP_EXTENT_CHANGED', - }); + ]); }); }); }); @@ -262,13 +292,12 @@ describe('map_actions', () => { params: { query: 'png' }, }, query: { match_phrase: { extension: 'png' } }, - $state: { store: 'appState' }, }, ]; const searchSessionId = '1234'; beforeEach(() => { - //Mocks the "previous" state + // Mocks the "previous" state require('../selectors/map_selectors').getQuery = () => { return query; }; diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index af8d6cc6bedc3c..45f3299db90413 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -13,6 +13,7 @@ import turfBooleanContains from '@turf/boolean-contains'; import { Filter, Query, TimeRange } from 'src/plugins/data/public'; import { Geometry, Position } from 'geojson'; import { DRAW_MODE, DRAW_SHAPE } from '../../common/constants'; +import type { MapExtentState, MapViewContext } from '../reducers/map/types'; import { MapStoreState } from '../reducers/store'; import { getDataFilters, @@ -52,25 +53,13 @@ import { import { autoFitToBounds, syncDataForAllLayers, syncDataForLayer } from './data_request_actions'; import { addLayer, addLayerWithoutDataSync } from './layer_actions'; import { MapSettings } from '../reducers/map'; -import { - DrawState, - MapCenter, - MapCenterAndZoom, - MapExtent, - Timeslice, -} from '../../common/descriptor_types'; +import { DrawState, MapCenterAndZoom, MapExtent, Timeslice } from '../../common/descriptor_types'; import { INITIAL_LOCATION } from '../../common/constants'; import { cleanTooltipStateForLayer } from './tooltip_actions'; import { VectorLayer } from '../classes/layers/vector_layer'; import { SET_DRAW_MODE } from './ui_actions'; import { expandToTileBoundaries } from '../../common/geo_tile_utils'; -export interface MapExtentState { - zoom: number; - extent: MapExtent; - center: MapCenter; -} - export function setMapInitError(errorMessage: string) { return { type: SET_MAP_INIT_ERROR, @@ -138,56 +127,50 @@ export function mapDestroyed() { } export function mapExtentChanged(mapExtentState: MapExtentState) { - return async ( + return ( dispatch: ThunkDispatch, getState: () => MapStoreState ) => { - const dataFilters = getDataFilters(getState()); - const { extent, zoom: newZoom } = mapExtentState; - const { buffer, zoom: currentZoom } = dataFilters; - - if (extent) { - let doesBufferContainExtent = false; - if (buffer) { - const bufferGeometry = turfBboxPolygon([ - buffer.minLon, - buffer.minLat, - buffer.maxLon, - buffer.maxLat, - ]); - const extentGeometry = turfBboxPolygon([ - extent.minLon, - extent.minLat, - extent.maxLon, - extent.maxLat, - ]); - - doesBufferContainExtent = turfBooleanContains(bufferGeometry, extentGeometry); - } - - if (!doesBufferContainExtent || currentZoom !== newZoom) { - // snap to the smallest tile-bounds, to avoid jitter in the bounds - dataFilters.buffer = expandToTileBoundaries(extent, Math.ceil(newZoom)); - } + const { extent, zoom: nextZoom } = mapExtentState; + const { buffer: prevBuffer, zoom: prevZoom } = getDataFilters(getState()); + + let doesPrevBufferContainNextExtent = true; + if (prevBuffer) { + const bufferGeometry = turfBboxPolygon([ + prevBuffer.minLon, + prevBuffer.minLat, + prevBuffer.maxLon, + prevBuffer.maxLat, + ]); + const extentGeometry = turfBboxPolygon([ + extent.minLon, + extent.minLat, + extent.maxLon, + extent.maxLat, + ]); + doesPrevBufferContainNextExtent = turfBooleanContains(bufferGeometry, extentGeometry); } dispatch({ type: MAP_EXTENT_CHANGED, - mapState: { - ...dataFilters, + mapViewContext: { ...mapExtentState, - }, + buffer: + !prevBuffer || !doesPrevBufferContainNextExtent || prevZoom !== nextZoom + ? expandToTileBoundaries(extent, Math.ceil(nextZoom)) + : prevBuffer, + } as MapViewContext, }); - if (currentZoom !== newZoom) { + if (prevZoom !== nextZoom) { getLayerList(getState()).map((layer) => { - if (!layer.showAtZoomLevel(newZoom)) { + if (!layer.showAtZoomLevel(nextZoom)) { dispatch(cleanTooltipStateForLayer(layer.getId())); } }); } - await dispatch(syncDataForAllLayers()); + dispatch(syncDataForAllLayers()); }; } 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 abdcf65a4ab1d0..afddb34d2d0ec2 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 @@ -5,11 +5,11 @@ * 2.0. */ +import type { IndexPatternField } from 'src/plugins/data/public'; import { FIELD_ORIGIN } from '../../../common/constants'; import { ESTooltipProperty } from '../tooltips/es_tooltip_property'; import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { indexPatterns } from '../../../../../../src/plugins/data/public'; -import { IFieldType } from '../../../../../../src/plugins/data/public'; import { IField, AbstractField } from './field'; import { IESSource } from '../sources/es_source'; import { IVectorSource } from '../sources/vector_source'; @@ -42,7 +42,7 @@ export class ESDocField extends AbstractField implements IField { return this._source; } - async _getIndexPatternField(): Promise { + async _getIndexPatternField(): Promise { const indexPattern = await this._source.getIndexPattern(); const indexPatternField = indexPattern.fields.getByName(this.getName()); return indexPatternField && indexPatterns.isNestedField(indexPatternField) diff --git a/x-pack/plugins/maps/public/classes/fields/kibana_region_field.ts b/x-pack/plugins/maps/public/classes/fields/kibana_region_field.ts deleted file mode 100644 index a183d197b1db3c..00000000000000 --- a/x-pack/plugins/maps/public/classes/fields/kibana_region_field.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { IField, AbstractField } from './field'; -import { KibanaRegionmapSource } from '../sources/kibana_regionmap_source/kibana_regionmap_source'; -import { FIELD_ORIGIN } from '../../../common/constants'; -import { IVectorSource } from '../sources/vector_source'; - -export class KibanaRegionField extends AbstractField implements IField { - private readonly _source: KibanaRegionmapSource; - - constructor({ - fieldName, - source, - origin, - }: { - fieldName: string; - source: KibanaRegionmapSource; - origin: FIELD_ORIGIN; - }) { - super({ fieldName, origin }); - this._source = source; - } - - getSource(): IVectorSource { - return this._source; - } - - async getLabel(): Promise { - const meta = await this._source.getVectorFileMeta(); - // TODO remove any and @ts-ignore when vectorFileMeta type defined - // @ts-ignore - const field: any = meta.fields.find((f) => f.name === this.getName()); - return field ? field.description : this.getName(); - } -} diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx index 82a741e7ccdab0..5bd2b68e61bc4f 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx @@ -18,7 +18,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { IFieldType, IndexPattern } from 'src/plugins/data/public'; +import { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; import { RenderWizardArguments } from '../layer_wizard_registry'; import { EMSFileSelect } from '../../../components/ems_file_select'; import { GeoIndexPatternSelect } from '../../../components/geo_index_pattern_select'; @@ -56,14 +56,14 @@ interface State { leftEmsFileId: string | null; leftEmsFields: Array>; leftIndexPattern: IndexPattern | null; - leftGeoFields: IFieldType[]; - leftJoinFields: IFieldType[]; + leftGeoFields: IndexPatternField[]; + leftJoinFields: IndexPatternField[]; leftGeoField: string | null; leftEmsJoinField: string | null; leftElasticsearchJoinField: string | null; rightIndexPatternId: string; rightIndexPatternTitle: string | null; - rightTermsFields: IFieldType[]; + rightTermsFields: IndexPatternField[]; rightJoinField: string | null; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx index 2688ef1d29686f..66f3efe992f88d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx @@ -9,11 +9,8 @@ import React, { Fragment, Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { - IFieldType, - IndexPattern, - indexPatterns, -} from '../../../../../../../src/plugins/data/public'; +import type { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { MetricsEditor } from '../../../components/metrics_editor'; import { getIndexPatternService } from '../../../kibana_services'; import { GeoLineForm } from './geo_line_form'; @@ -30,7 +27,7 @@ interface Props { interface State { indexPattern: IndexPattern | null; - fields: IFieldType[]; + fields: IndexPatternField[]; } export class UpdateSourceEditor extends Component { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 464ba024663ece..93362a1721ce55 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -9,7 +9,7 @@ import _ from 'lodash'; import React, { ReactElement } from 'react'; import rison from 'rison-node'; import { i18n } from '@kbn/i18n'; -import type { Filter, IFieldType, IndexPattern } from 'src/plugins/data/public'; +import type { Filter, IndexPatternField, IndexPattern } from 'src/plugins/data/public'; import { GeoJsonProperties, Geometry, Position } from 'geojson'; import { esFilters } from '../../../../../../../src/plugins/data/public'; import { AbstractESSource } from '../es_source'; @@ -183,7 +183,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye async getFields(): Promise { try { const indexPattern = await this.getIndexPattern(); - const fields: IFieldType[] = indexPattern.fields.filter((field) => { + const fields: IndexPatternField[] = indexPattern.fields.filter((field) => { // Ensure fielddata is enabled for field. // Search does not request _source return field.aggregatable; @@ -300,7 +300,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye }; } - const topHitsSplitField: IFieldType = getField(indexPattern, topHitsSplitFieldName); + const topHitsSplitField: IndexPatternField = getField(indexPattern, topHitsSplitFieldName); const cardinalityAgg = { precision_threshold: 1 }; const termsAgg = { size: DEFAULT_MAX_BUCKETS_LIMIT, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx index 41c9076c114360..e71ee803d77e66 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx @@ -8,16 +8,13 @@ import React, { Component } from 'react'; import { EuiPanel } from '@elastic/eui'; +import type { IndexPattern, IndexPatternField } from 'src/plugins/data/public'; import { SCALING_TYPES } from '../../../../../common/constants'; import { GeoFieldSelect } from '../../../../components/geo_field_select'; import { GeoIndexPatternSelect } from '../../../../components/geo_index_pattern_select'; import { getGeoFields, getTermsFields, getSortFields } from '../../../../index_pattern_util'; import { ESSearchSourceDescriptor } from '../../../../../common/descriptor_types'; -import { - IndexPattern, - IFieldType, - SortDirection, -} from '../../../../../../../../src/plugins/data/common'; +import { SortDirection } from '../../../../../../../../src/plugins/data/public'; import { TopHitsForm } from './top_hits_form'; import { OnSourceChangeArgs } from '../../source'; @@ -27,12 +24,12 @@ interface Props { interface State { indexPattern: IndexPattern | null; - geoFields: IFieldType[]; + geoFields: IndexPatternField[]; geoFieldName: string | null; sortField: string | null; - sortFields: IFieldType[]; + sortFields: IndexPatternField[]; sortOrder: SortDirection; - termFields: IFieldType[]; + termFields: IndexPatternField[]; topHitsSplitField: string | null; topHitsSize: number; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx index 79d6039076f81c..072c3995441828 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx @@ -8,6 +8,7 @@ import React, { ChangeEvent, Component, Fragment } from 'react'; import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import type { IndexPatternField } from 'src/plugins/data/public'; import { SingleFieldSelect } from '../../../../components/single_field_select'; import { getIndexPatternService } from '../../../../kibana_services'; // @ts-expect-error @@ -15,16 +16,16 @@ import { ValidatedRange } from '../../../../components/validated_range'; import { DEFAULT_MAX_INNER_RESULT_WINDOW } from '../../../../../common/constants'; import { loadIndexSettings } from '../util/load_index_settings'; import { OnSourceChangeArgs } from '../../source'; -import { IFieldType, SortDirection } from '../../../../../../../../src/plugins/data/public'; +import { SortDirection } from '../../../../../../../../src/plugins/data/public'; interface Props { indexPatternId: string; isColumnCompressed?: boolean; onChange: (args: OnSourceChangeArgs) => void; sortField: string; - sortFields: IFieldType[]; + sortFields: IndexPatternField[]; sortOrder: SortDirection; - termFields: IFieldType[]; + termFields: IndexPatternField[]; topHitsSplitField: string | null; topHitsSize: number; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx index 6ca38f3be22f48..33f0d3c1d98407 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx @@ -9,12 +9,13 @@ import React, { Component, Fragment } from 'react'; import { EuiFormRow, EuiTitle, EuiPanel, EuiSpacer, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import type { IndexPatternField } from 'src/plugins/data/public'; import { FIELD_ORIGIN } from '../../../../../common/constants'; import { TooltipSelector } from '../../../../components/tooltip_selector'; import { getIndexPatternService } from '../../../../kibana_services'; import { getTermsFields, getSortFields, getSourceFields } from '../../../../index_pattern_util'; -import { SortDirection, IFieldType } from '../../../../../../../../src/plugins/data/public'; +import { SortDirection } from '../../../../../../../../src/plugins/data/public'; import { ESDocField } from '../../../fields/es_doc_field'; import { OnSourceChangeArgs } from '../../source'; import { TopHitsForm } from './top_hits_form'; @@ -36,8 +37,8 @@ interface Props { interface State { loadError?: string; sourceFields: IField[]; - termFields: IFieldType[]; - sortFields: IFieldType[]; + termFields: IndexPatternField[]; + sortFields: IndexPatternField[]; } export class TopHitsUpdateSourceEditor extends Component { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts index 0a24c140d735fa..a4429c663da84c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts @@ -6,10 +6,9 @@ */ import { getDocValueAndSourceFields } from './get_docvalue_source_fields'; -import { IndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; -import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; +import type { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; -function createMockIndexPattern(fields: IFieldType[]): IndexPattern { +function createMockIndexPattern(fields: IndexPatternField[]): IndexPattern { const indexPattern = { get fields() { return { @@ -29,9 +28,8 @@ describe('getDocValueAndSourceFields', () => { createMockIndexPattern([ { name: 'foobar', - // @ts-expect-error runtimeField not added yet to IFieldType. API tbd - runtimeField: {}, - }, + runtimeField: { type: 'keyword' }, + } as IndexPatternField, ]), ['foobar'], 'epoch_millis' diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts index 78823f4631cacd..949dc990c44feb 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts @@ -34,9 +34,7 @@ export function getDocValueAndSourceFields( lang: field.lang || '', }, }; - } - // @ts-expect-error runtimeField has not been added to public API yet. exact shape of type TBD. - else if (field.readFromDocValues || field.runtimeField) { + } else if (field.readFromDocValues || field.runtimeField) { const docValueField = field.type === 'date' ? { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 23bcd9baed8c0b..ce8991bb63ce69 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; -import { Filter, IFieldType, IndexPattern, ISearchSource } from 'src/plugins/data/public'; +import { Filter, IndexPatternField, IndexPattern, ISearchSource } from 'src/plugins/data/public'; import { AbstractVectorSource, BoundsFilters } from '../vector_source'; import { getAutocompleteService, @@ -364,7 +364,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource } } - async _getGeoField(): Promise { + async _getGeoField(): Promise { const indexPattern = await this.getIndexPattern(); const geoField = indexPattern.fields.getByName(this.getGeoFieldName()); if (!geoField) { diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/fetch_geojson.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/fetch_geojson.ts deleted file mode 100644 index 329070632a94dd..00000000000000 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/fetch_geojson.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { FeatureCollection } from 'geojson'; -import * as topojson from 'topojson-client'; -import { GeometryCollection } from 'topojson-specification'; -import fetch from 'node-fetch'; - -export enum FORMAT_TYPE { - GEOJSON = 'geojson', - TOPOJSON = 'topojson', -} - -export async function fetchGeoJson( - fetchUrl: string, - format: FORMAT_TYPE, - featureCollectionPath: string -): Promise { - let fetchedJson; - try { - const response = await fetch(fetchUrl); - if (!response.ok) { - throw new Error('Request failed'); - } - fetchedJson = await response.json(); - } catch (e) { - throw new Error( - i18n.translate('xpack.maps.util.requestFailedErrorMessage', { - defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`, - values: { fetchUrl }, - }) - ); - } - - if (format === FORMAT_TYPE.GEOJSON) { - return fetchedJson; - } - - if (format === FORMAT_TYPE.TOPOJSON) { - const features = _.get(fetchedJson, `objects.${featureCollectionPath}`) as GeometryCollection; - return topojson.feature(fetchedJson, features); - } - - throw new Error( - i18n.translate('xpack.maps.util.formatErrorMessage', { - defaultMessage: `Unable to fetch vector shapes from url: {format}`, - values: { format }, - }) - ); -} diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/index.js b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/index.js deleted file mode 100644 index dcf009d0a280b9..00000000000000 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { KibanaRegionmapSource } from './kibana_regionmap_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts deleted file mode 100644 index f7f311d011d6e5..00000000000000 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source'; -import { getRegionmapLayers } from '../../../kibana_services'; -import { getDataSourceLabel } from '../../../../common/i18n_getters'; -import { FIELD_ORIGIN, SOURCE_TYPES } from '../../../../common/constants'; -import { KibanaRegionField } from '../../fields/kibana_region_field'; -import { registerSource } from '../source_registry'; -import { KibanaRegionmapSourceDescriptor } from '../../../../common/descriptor_types'; -import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; -import { IField } from '../../fields/field'; -import type { LayerConfig } from '../../../../../../../src/plugins/maps_ems/public'; -import { fetchGeoJson, FORMAT_TYPE } from './fetch_geojson'; - -const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', { - defaultMessage: 'Configured GeoJSON', -}); - -export class KibanaRegionmapSource extends AbstractVectorSource { - readonly _descriptor: KibanaRegionmapSourceDescriptor; - - static createDescriptor({ name }: { name: string }): KibanaRegionmapSourceDescriptor { - return { - type: SOURCE_TYPES.REGIONMAP_FILE, - name, - }; - } - - constructor(descriptor: KibanaRegionmapSourceDescriptor, inspectorAdapters?: Adapters) { - super(descriptor, inspectorAdapters); - this._descriptor = descriptor; - } - - createField({ fieldName }: { fieldName: string }): KibanaRegionField { - return new KibanaRegionField({ - fieldName, - source: this, - origin: FIELD_ORIGIN.SOURCE, - }); - } - - async getImmutableProperties() { - const vectorFileMeta = await this.getVectorFileMeta(); - return [ - { - label: getDataSourceLabel(), - value: sourceTitle, - }, - { - label: i18n.translate('xpack.maps.source.kbnRegionMap.vectorLayerLabel', { - defaultMessage: 'Vector layer', - }), - value: this._descriptor.name, - }, - { - label: i18n.translate('xpack.maps.source.kbnRegionMap.vectorLayerUrlLabel', { - defaultMessage: 'Vector layer url', - }), - value: vectorFileMeta.url, - }, - ]; - } - - async getVectorFileMeta(): Promise { - const regionList: LayerConfig[] = getRegionmapLayers(); - const layerConfig: LayerConfig | undefined = regionList.find( - (regionConfig: LayerConfig) => regionConfig.name === this._descriptor.name - ); - if (!layerConfig) { - throw new Error( - i18n.translate('xpack.maps.source.kbnRegionMap.noConfigErrorMessage', { - defaultMessage: `Unable to find map.regionmap configuration for {name}`, - values: { - name: this._descriptor.name, - }, - }) - ); - } - return layerConfig; - } - - async getGeoJsonWithMeta(): Promise { - const vectorFileMeta = await this.getVectorFileMeta(); - const featureCollection = await fetchGeoJson( - vectorFileMeta.url, - vectorFileMeta.format.type as FORMAT_TYPE, - vectorFileMeta.meta.feature_collection_path - ); - - return { - data: featureCollection, - meta: {}, - }; - } - - async getLeftJoinFields(): Promise { - const vectorFileMeta: LayerConfig = await this.getVectorFileMeta(); - return vectorFileMeta.fields.map( - (field): KibanaRegionField => { - return this.createField({ fieldName: field.name }); - } - ); - } - - async getDisplayName(): Promise { - return this._descriptor.name; - } - - hasTooltipProperties() { - return true; - } - - getSourceTooltipContent() { - return { - tooltipContent: i18n.translate('xpack.maps.source.kbnRegionMap.deprecationTooltipMessage', { - defaultMessage: `'Configured GeoJSON' layer is deprecated. 1) Use 'Upload GeoJSON' to upload '{vectorLayer}'. 2) Use Choropleth layer wizard to build a replacement layer. 3) Finally, delete this layer from your map.`, - values: { - vectorLayer: this._descriptor.name, - }, - }), - areResultsTrimmed: false, - isDeprecated: true, - }; - } -} - -registerSource({ - ConstructorFunction: KibanaRegionmapSource, - type: SOURCE_TYPES.REGIONMAP_FILE, -}); diff --git a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts index fbb416e7a7619a..b9021f18965504 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IFieldType, IndexPattern } from '../../../../../../src/plugins/data/public'; +import type { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; import { ESTooltipProperty } from './es_tooltip_property'; import { TooltipProperty } from './tooltip_property'; import { AbstractField } from '../fields/field'; @@ -25,7 +25,7 @@ const indexPatternField = { searchable: true, aggregatable: true, readFromDocValues: false, -} as IFieldType; +} as IndexPatternField; const featurePropertyField = new MockField({ fieldName: 'machine.os', @@ -41,7 +41,7 @@ const nonFilterableIndexPatternField = { searchable: true, aggregatable: true, readFromDocValues: false, -} as IFieldType; +} as IndexPatternField; const nonFilterableFeaturePropertyField = new MockField({ fieldName: 'location', @@ -51,7 +51,7 @@ const nonFilterableFeaturePropertyField = new MockField({ const indexPattern = { id: 'indexPatternId', fields: { - getByName: (name: string): IFieldType | null => { + getByName: (name: string): IndexPatternField | null => { if (name === 'machine.os') { return indexPatternField; } diff --git a/x-pack/plugins/maps/public/components/geo_field_select.tsx b/x-pack/plugins/maps/public/components/geo_field_select.tsx index 0b04ec7146611d..5ee81866784bc5 100644 --- a/x-pack/plugins/maps/public/components/geo_field_select.tsx +++ b/x-pack/plugins/maps/public/components/geo_field_select.tsx @@ -8,12 +8,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow } from '@elastic/eui'; +import type { IndexPatternField } from 'src/plugins/data/public'; import { SingleFieldSelect } from './single_field_select'; -import { IFieldType } from '../../../../../src/plugins/data/common'; interface Props { value: string; - geoFields: IFieldType[]; + geoFields: IndexPatternField[]; onChange: (geoFieldName?: string) => void; } diff --git a/x-pack/plugins/maps/public/components/metrics_editor/metric_editor.tsx b/x-pack/plugins/maps/public/components/metrics_editor/metric_editor.tsx index b72a8d01d4a93f..c3782d9be73d25 100644 --- a/x-pack/plugins/maps/public/components/metrics_editor/metric_editor.tsx +++ b/x-pack/plugins/maps/public/components/metrics_editor/metric_editor.tsx @@ -11,15 +11,15 @@ import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiComboBoxOptionOption, EuiFieldText, EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import type { IndexPatternField } from 'src/plugins/data/public'; import { MetricSelect } from './metric_select'; import { SingleFieldSelect } from '../single_field_select'; import { AggDescriptor } from '../../../common/descriptor_types'; import { AGG_TYPE, DEFAULT_PERCENTILE } from '../../../common/constants'; import { getTermsFields } from '../../index_pattern_util'; -import { IFieldType } from '../../../../../../src/plugins/data/public'; import { ValidatedNumberInput } from '../validated_number_input'; -function filterFieldsForAgg(fields: IFieldType[], aggType: AGG_TYPE) { +function filterFieldsForAgg(fields: IndexPatternField[], aggType: AGG_TYPE) { if (!fields) { return []; } @@ -40,7 +40,7 @@ function filterFieldsForAgg(fields: IFieldType[], aggType: AGG_TYPE) { interface Props { metric: AggDescriptor; - fields: IFieldType[]; + fields: IndexPatternField[]; onChange: (metric: AggDescriptor) => void; onRemove: () => void; metricsFilter?: (metricOption: EuiComboBoxOptionOption) => boolean; diff --git a/x-pack/plugins/maps/public/components/metrics_editor/metrics_editor.tsx b/x-pack/plugins/maps/public/components/metrics_editor/metrics_editor.tsx index 2124345e7e03ca..17baa7eb355d62 100644 --- a/x-pack/plugins/maps/public/components/metrics_editor/metrics_editor.tsx +++ b/x-pack/plugins/maps/public/components/metrics_editor/metrics_editor.tsx @@ -8,9 +8,9 @@ import React, { Component, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiComboBoxOptionOption, EuiSpacer, EuiTextAlign } from '@elastic/eui'; +import type { IndexPatternField } from 'src/plugins/data/public'; import { MetricEditor } from './metric_editor'; import { DEFAULT_METRIC } from '../../classes/sources/es_agg_source'; -import { IFieldType } from '../../../../../../src/plugins/data/public'; import { AggDescriptor, FieldedAggDescriptor } from '../../../common/descriptor_types'; import { AGG_TYPE } from '../../../common/constants'; @@ -23,7 +23,7 @@ export function isMetricValid(aggDescriptor: AggDescriptor) { interface Props { allowMultipleMetrics: boolean; metrics: AggDescriptor[]; - fields: IFieldType[]; + fields: IndexPatternField[]; onChange: (metrics: AggDescriptor[]) => void; metricsFilter?: (metricOption: EuiComboBoxOptionOption) => boolean; } diff --git a/x-pack/plugins/maps/public/components/single_field_select.tsx b/x-pack/plugins/maps/public/components/single_field_select.tsx index 6727de35b1be74..67594db11eb370 100644 --- a/x-pack/plugins/maps/public/components/single_field_select.tsx +++ b/x-pack/plugins/maps/public/components/single_field_select.tsx @@ -17,20 +17,20 @@ import { EuiFlexItem, EuiToolTip, } from '@elastic/eui'; -import { IFieldType } from 'src/plugins/data/public'; +import { IndexPatternField } from 'src/plugins/data/public'; import { FieldIcon } from '../../../../../src/plugins/kibana_react/public'; function fieldsToOptions( - fields?: IFieldType[], - isFieldDisabled?: (field: IFieldType) => boolean -): Array> { + fields?: IndexPatternField[], + isFieldDisabled?: (field: IndexPatternField) => boolean +): Array> { if (!fields) { return []; } return fields .map((field) => { - const option: EuiComboBoxOptionOption = { + const option: EuiComboBoxOptionOption = { value: field, label: field.displayName ? field.displayName : field.name, }; @@ -45,14 +45,14 @@ function fieldsToOptions( } type Props = Omit< - EuiComboBoxProps, + EuiComboBoxProps, 'isDisabled' | 'onChange' | 'options' | 'renderOption' | 'selectedOptions' | 'singleSelection' > & { - fields?: IFieldType[]; + fields?: IndexPatternField[]; onChange: (fieldName?: string) => void; value: string | null; // index pattern field name - isFieldDisabled?: (field: IFieldType) => boolean; - getFieldDisabledReason?: (field: IFieldType) => string | null; + isFieldDisabled?: (field: IndexPatternField) => boolean; + getFieldDisabledReason?: (field: IndexPatternField) => string | null; }; export function SingleFieldSelect({ @@ -64,7 +64,7 @@ export function SingleFieldSelect({ ...rest }: Props) { function renderOption( - option: EuiComboBoxOptionOption, + option: EuiComboBoxOptionOption, searchValue: string, contentClassName: string ) { @@ -91,13 +91,13 @@ export function SingleFieldSelect({ ); } - const onSelection = (selectedOptions: Array>) => { + const onSelection = (selectedOptions: Array>) => { onChange(_.get(selectedOptions, '0.value.name')); }; - const selectedOptions: Array> = []; + const selectedOptions: Array> = []; if (value && fields) { - const selectedField = fields.find((field: IFieldType) => { + const selectedField = fields.find((field: IndexPatternField) => { return field.name === value; }); if (selectedField) { diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx index 6962977627b985..c636047e2be377 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx @@ -9,7 +9,7 @@ import _ from 'lodash'; import React, { Component } from 'react'; import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { IFieldType, IndexPattern, Query } from 'src/plugins/data/public'; +import type { IndexPatternField, IndexPattern, Query } from 'src/plugins/data/public'; import { JoinExpression } from './join_expression'; import { MetricsExpression } from './metrics_expression'; import { WhereExpression } from './where_expression'; @@ -38,7 +38,7 @@ interface Props { } interface State { - rightFields: IFieldType[]; + rightFields: IndexPatternField[]; indexPattern?: IndexPattern; loadError?: string; } diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join_expression.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join_expression.tsx index f2073a9f6e650d..19a47736b19581 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join_expression.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join_expression.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IFieldType } from 'src/plugins/data/public'; +import { IndexPatternField } from 'src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { DEFAULT_MAX_BUCKETS_LIMIT } from '../../../../../common/constants'; import { SingleFieldSelect } from '../../../../components/single_field_select'; @@ -54,7 +54,7 @@ interface Props { // Right field props rightValue: string; rightSize?: number; - rightFields: IFieldType[]; + rightFields: IndexPatternField[]; onRightFieldChange: (term?: string) => void; onRightSizeChange: (size: number) => void; } diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.tsx index 899430f3c2f2d0..4c2e96498ee20c 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/metrics_expression.tsx @@ -15,7 +15,7 @@ import { EuiFormHelpText, } from '@elastic/eui'; -import { IFieldType } from 'src/plugins/data/public'; +import { IndexPatternField } from 'src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { MetricsEditor } from '../../../../components/metrics_editor'; import { AGG_TYPE } from '../../../../../common/constants'; @@ -23,7 +23,7 @@ import { AggDescriptor, FieldedAggDescriptor } from '../../../../../common/descr interface Props { metrics: AggDescriptor[]; - rightFields: IFieldType[]; + rightFields: IndexPatternField[]; onChange: (metrics: AggDescriptor[]) => void; } diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/index.ts index 3084d3b9c9f3f3..9936d412de9e67 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/index.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/index.ts @@ -14,7 +14,6 @@ import { clearMouseCoordinates, mapDestroyed, mapExtentChanged, - MapExtentState, mapReady, setAreTilesLoaded, setMapInitError, @@ -35,6 +34,7 @@ import { getInspectorAdapters } from '../../reducers/non_serializable_instances' import { MapStoreState } from '../../reducers/store'; import { DRAW_MODE } from '../../../common'; import { TileMetaFeature } from '../../../common/descriptor_types'; +import type { MapExtentState } from '../../reducers/map/types'; function mapStateToProps(state: MapStoreState) { return { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index a60aef95318f00..053e410b8c7123 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -49,10 +49,10 @@ import { } from './utils'; import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; -import { MapExtentState } from '../../actions'; import { TileStatusTracker } from './tile_status_tracker'; import { DrawFeatureControl } from './draw_control/draw_feature_control'; import { TiledVectorLayer } from '../../classes/layers/tiled_vector_layer/tiled_vector_layer'; +import type { MapExtentState } from '../../reducers/map/types'; export interface Props { isMapReady: boolean; @@ -150,7 +150,7 @@ export class MbMap extends Component { } }, 256); - _getMapState() { + _getMapExtentState(): MapExtentState { const zoom = this.state.mbMap!.getZoom(); const mbCenter = this.state.mbMap!.getCenter(); const mbBounds = this.state.mbMap!.getBounds(); @@ -257,7 +257,7 @@ export class MbMap extends Component { this._loadMakiSprites(mbMap); this._initResizerChecker(); this._registerMapEventListeners(mbMap); - this.props.onMapReady(this._getMapState()); + this.props.onMapReady(this._getMapExtentState()); }); } @@ -269,7 +269,7 @@ export class MbMap extends Component { mbMap.on( 'moveend', _.debounce(() => { - this.props.extentChanged(this._getMapState()); + this.props.extentChanged(this._getMapExtentState()); }, 100) ); @@ -413,7 +413,7 @@ export class MbMap extends Component { // hack to update extent after zoom update finishes moving map. if (zoomRangeChanged) { setTimeout(() => { - this.props.extentChanged(this._getMapState()); + this.props.extentChanged(this._getMapExtentState()); }, 300); } } diff --git a/x-pack/plugins/maps/public/index_pattern_util.test.ts b/x-pack/plugins/maps/public/index_pattern_util.test.ts index 49ae16415e159f..d1affe6ce4d85d 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.test.ts +++ b/x-pack/plugins/maps/public/index_pattern_util.test.ts @@ -14,6 +14,7 @@ import { supportsGeoTileAgg, } from './index_pattern_util'; import { ES_GEO_FIELD_TYPE } from '../common/constants'; +import { IndexPatternField } from 'src/plugins/data/public'; describe('getSourceFields', () => { test('Should remove multi fields from field list', () => { @@ -21,7 +22,7 @@ describe('getSourceFields', () => { { name: 'agent', type: 'string', - }, + } as IndexPatternField, { name: 'agent.keyword', subType: { @@ -30,7 +31,7 @@ describe('getSourceFields', () => { }, }, type: 'string', - }, + } as IndexPatternField, ]; const sourceFields = getSourceFields(fields); expect(sourceFields).toEqual([{ name: 'agent', type: 'string' }]); @@ -44,7 +45,7 @@ describe('Gold+ licensing', () => { name: 'location', type: 'geo_point', aggregatable: true, - }, + } as IndexPatternField, supportedInBasic: true, supportedInGold: true, }, @@ -53,7 +54,7 @@ describe('Gold+ licensing', () => { name: 'location', type: 'geo_shape', aggregatable: false, - }, + } as IndexPatternField, supportedInBasic: false, supportedInGold: false, }, @@ -62,7 +63,7 @@ describe('Gold+ licensing', () => { name: 'location', type: 'geo_shape', aggregatable: true, - }, + } as IndexPatternField, supportedInBasic: false, supportedInGold: true, }, diff --git a/x-pack/plugins/maps/public/index_pattern_util.ts b/x-pack/plugins/maps/public/index_pattern_util.ts index 3b1cb461c87793..a2133f4f2521b7 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.ts +++ b/x-pack/plugins/maps/public/index_pattern_util.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { IFieldType, IndexPattern } from 'src/plugins/data/public'; +import type { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; import { i18n } from '@kbn/i18n'; import { getIndexPatternService } from './kibana_services'; import { indexPatterns } from '../../../../src/plugins/data/public'; import { ES_GEO_FIELD_TYPE, ES_GEO_FIELD_TYPES } from '../common/constants'; import { getIsGoldPlus } from './licensed_features'; -export function getGeoTileAggNotSupportedReason(field: IFieldType): string | null { +export function getGeoTileAggNotSupportedReason(field: IndexPatternField): string | null { if (!field.aggregatable) { return i18n.translate('xpack.maps.geoTileAgg.disabled.docValues', { defaultMessage: @@ -46,7 +46,7 @@ export async function getIndexPatternsFromIds( return await Promise.all(promises); } -export function getTermsFields(fields: IFieldType[]): IFieldType[] { +export function getTermsFields(fields: IndexPatternField[]): IndexPatternField[] { return fields.filter((field) => { return ( field.aggregatable && @@ -56,7 +56,7 @@ export function getTermsFields(fields: IFieldType[]): IFieldType[] { }); } -export function getSortFields(fields: IFieldType[]): IFieldType[] { +export function getSortFields(fields: IndexPatternField[]): IndexPatternField[] { return fields.filter((field) => { return field.sortable && !indexPatterns.isNestedField(field); }); @@ -70,23 +70,23 @@ export function getAggregatableGeoFieldTypes(): string[] { return aggregatableFieldTypes; } -export function getGeoFields(fields: IFieldType[]): IFieldType[] { +export function getGeoFields(fields: IndexPatternField[]): IndexPatternField[] { return fields.filter((field) => { return !indexPatterns.isNestedField(field) && ES_GEO_FIELD_TYPES.includes(field.type); }); } -export function getGeoPointFields(fields: IFieldType[]): IFieldType[] { +export function getGeoPointFields(fields: IndexPatternField[]): IndexPatternField[] { return fields.filter((field) => { return !indexPatterns.isNestedField(field) && ES_GEO_FIELD_TYPE.GEO_POINT === field.type; }); } -export function getFieldsWithGeoTileAgg(fields: IFieldType[]): IFieldType[] { +export function getFieldsWithGeoTileAgg(fields: IndexPatternField[]): IndexPatternField[] { return fields.filter(supportsGeoTileAgg); } -export function supportsGeoTileAgg(field?: IFieldType): boolean { +export function supportsGeoTileAgg(field?: IndexPatternField): boolean { return ( !!field && !!field.aggregatable && @@ -95,7 +95,7 @@ export function supportsGeoTileAgg(field?: IFieldType): boolean { ); } -export function getSourceFields(fields: IFieldType[]): IFieldType[] { +export function getSourceFields(fields: IndexPatternField[]): IndexPatternField[] { return fields.filter((field) => { // Multi fields are not stored in _source and only exist in index. const isMultiField = field.subType && field.subType.multi; diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index c4340a051a07d1..300fe07a841e9a 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -77,14 +77,6 @@ export const getEMSSettings = () => { export const getEmsTileLayerId = () => getKibanaCommonConfig().emsTileLayerId; -export const getRegionmapLayers = () => { - const config = getKibanaCommonConfig(); - if (config.regionmap && config.regionmap.layers) { - return config.regionmap.layers; - } else { - return []; - } -}; export const getTilemap = () => { const config = getKibanaCommonConfig(); if (config.tilemap) { diff --git a/x-pack/plugins/maps/public/reducers/map/map.ts b/x-pack/plugins/maps/public/reducers/map/map.ts index de74adf55ba9a7..30db4ef8120adc 100644 --- a/x-pack/plugins/maps/public/reducers/map/map.ts +++ b/x-pack/plugins/maps/public/reducers/map/map.ts @@ -218,13 +218,7 @@ export function map(state: MapState = DEFAULT_MAP_STATE, action: Record & { scrollZoom: boolean; - buffer?: MapExtent; - extent?: MapExtent; mouseCoordinates?: { lat: number; lon: number; diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers_from_url_param.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers_from_url_param.ts index 712a6a1cae79bf..4dc4cbccb2f94a 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers_from_url_param.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers_from_url_param.ts @@ -11,7 +11,6 @@ import '../../../classes/sources/wms_source'; import '../../../classes/sources/ems_file_source'; import '../../../classes/sources/es_search_source'; import '../../../classes/sources/es_pew_pew_source'; -import '../../../classes/sources/kibana_regionmap_source'; import '../../../classes/sources/es_geo_grid_source'; import '../../../classes/sources/xyz_tms_source'; import { getToasts } from '../../../kibana_services'; diff --git a/x-pack/plugins/maps/server/index.ts b/x-pack/plugins/maps/server/index.ts index 6f0b9b39c40dca..b57f9ec9c29b1d 100644 --- a/x-pack/plugins/maps/server/index.ts +++ b/x-pack/plugins/maps/server/index.ts @@ -51,37 +51,6 @@ export const config: PluginConfigDescriptor = { }); return completeConfig; }, - ( - completeConfig: Record, - rootPath: string, - addDeprecation: AddConfigDeprecation - ) => { - if (_.get(completeConfig, 'map.regionmap') === undefined) { - return completeConfig; - } - addDeprecation({ - message: i18n.translate('xpack.maps.deprecation.regionmap.message', { - defaultMessage: 'map.regionmap is deprecated and is no longer used', - }), - correctiveActions: { - manualSteps: [ - i18n.translate('xpack.maps.deprecation.regionmap.step1', { - defaultMessage: - 'Remove "map.regionmap" in the Kibana config file, CLI flag, or environment variable (in Docker only).', - }), - i18n.translate('xpack.maps.deprecation.regionmap.step2', { - defaultMessage: - 'Use "Upload GeoJSON" to upload each layer defined by "map.regionmap.layers".', - }), - i18n.translate('xpack.maps.deprecation.regionmap.step3', { - defaultMessage: - 'Update all maps with "Configured GeoJSON" layers. Use Choropleth layer wizard to build a replacement layer. Delete "Configured GeoJSON" layer from your map.', - }), - ], - }, - }); - return completeConfig; - }, ], }; diff --git a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts index ee2b81716ca6a8..7dcfea6f267bb0 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts @@ -127,18 +127,6 @@ export function registerMapsUsageCollector(usageCollection: UsageCollectionSetup _meta: { description: 'total number of es track layers in cluster' }, }, }, - kbn_region: { - min: { type: 'long', _meta: { description: 'min number of kbn region layers per map' } }, - max: { type: 'long', _meta: { description: 'max number of kbn region layers per map' } }, - avg: { - type: 'float', - _meta: { description: 'avg number of kbn region layers per map' }, - }, - total: { - type: 'long', - _meta: { description: 'total number of kbn region layers in cluster' }, - }, - }, kbn_tms_raster: { min: { type: 'long', _meta: { description: 'min number of kbn tms layers per map' } }, max: { type: 'long', _meta: { description: 'max number of kbn tms layers per map' } }, diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index b345c427b50b9c..5041cb997ff58b 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { SavedObject } from 'kibana/server'; -import { IFieldType } from 'src/plugins/data/public'; +import type { IndexPatternField } from 'src/plugins/data/public'; import { ES_GEO_FIELD_TYPE, LAYER_TYPE, @@ -136,7 +136,8 @@ async function isFieldGeoShape( return false; } return indexPattern.fields.some( - (fieldDescriptor: IFieldType) => fieldDescriptor.name && fieldDescriptor.name === geoField! + (fieldDescriptor: IndexPatternField) => + fieldDescriptor.name && fieldDescriptor.name === geoField! ); } diff --git a/x-pack/plugins/maps/server/maps_telemetry/util.ts b/x-pack/plugins/maps/server/maps_telemetry/util.ts index 24d211de659ff1..844486db89abfe 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/util.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/util.ts @@ -29,7 +29,6 @@ export enum TELEMETRY_LAYER_TYPE { ES_AGG_HEATMAP = 'es_agg_heatmap', EMS_REGION = 'ems_region', EMS_BASEMAP = 'ems_basemap', - KBN_REGION = 'kbn_region', KBN_TMS_RASTER = 'kbn_tms_raster', UX_TMS_RASTER = 'ux_tms_raster', // configured in the UX layer wizard of Maps UX_TMS_MVT = 'ux_tms_mvt', // configured in the UX layer wizard of Maps @@ -111,10 +110,6 @@ export function getTelemetryLayerType( return TELEMETRY_LAYER_TYPE.KBN_TMS_RASTER; } - if (layerDescriptor.sourceDescriptor.type === SOURCE_TYPES.REGIONMAP_FILE) { - return TELEMETRY_LAYER_TYPE.KBN_REGION; - } - if (layerDescriptor.sourceDescriptor.type === SOURCE_TYPES.EMS_XYZ) { return TELEMETRY_LAYER_TYPE.UX_TMS_RASTER; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index ce91900f11e1b7..c02539fabfe41f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -13,7 +13,7 @@ import { extractErrorMessage } from '../../../../../../../common/util/errors'; import { DeepReadonly } from '../../../../../../../common/types/common'; import { ml } from '../../../../../services/ml_api_service'; import { useMlContext } from '../../../../../contexts/ml'; -import { DuplicateIndexPatternError } from '../../../../../../../../../../src/plugins/data/public'; +import { DuplicateDataViewError } from '../../../../../../../../../../src/plugins/data/public'; import { useRefreshAnalyticsList, DataFrameAnalyticsConfig } from '../../../../common'; import { extractCloningConfig, isAdvancedConfig } from '../../components/action_clone'; @@ -145,7 +145,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { ), }); } catch (e) { - if (e instanceof DuplicateIndexPatternError) { + if (e instanceof DuplicateDataViewError) { addRequestMessage({ error: i18n.translate( 'xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError', diff --git a/x-pack/plugins/observability/public/hooks/use_alert_permission.ts b/x-pack/plugins/observability/public/hooks/use_alert_permission.ts index 2c2837c4bda825..d754c53f23f5ca 100644 --- a/x-pack/plugins/observability/public/hooks/use_alert_permission.ts +++ b/x-pack/plugins/observability/public/hooks/use_alert_permission.ts @@ -12,7 +12,7 @@ import { Capabilities } from '../../../../../src/core/types'; export interface UseGetUserAlertsPermissionsProps { crud: boolean; read: boolean; - loading: boolean; + loading?: boolean; featureId: string | null; } @@ -30,9 +30,12 @@ export const getAlertsPermissions = ( } return { - crud: uiCapabilities[featureId].save as boolean, - read: uiCapabilities[featureId].show as boolean, - loading: false, + crud: (featureId === 'apm' + ? uiCapabilities[featureId]['alerting:save'] + : uiCapabilities[featureId].save) as boolean, + read: (featureId === 'apm' + ? uiCapabilities[featureId]['alerting:show'] + : uiCapabilities[featureId].show) as boolean, featureId, }; }; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx index 01bb01857eaf00..926f03acf01d89 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx @@ -46,7 +46,7 @@ export function AlertsSearchBar({ 75', + defaultMessage: 'Search alerts (e.g. kibana.alert.evaluation.threshold > 75)', })} query={{ query: query ?? '', language: queryLanguage }} timeHistory={timeHistory} diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index 7cb7395acaa8d1..2d325b6f3f7c4b 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -14,6 +14,7 @@ import { ALERT_DURATION as ALERT_DURATION_TYPED, ALERT_REASON as ALERT_REASON_TYPED, ALERT_RULE_CONSUMER, + ALERT_RULE_PRODUCER, ALERT_STATUS as ALERT_STATUS_TYPED, ALERT_WORKFLOW_STATUS as ALERT_WORKFLOW_STATUS_TYPED, } from '@kbn/rule-data-utils'; @@ -173,6 +174,9 @@ function ObservabilityActions({ const alertDataConsumer = useMemo(() => get(dataFieldEs, ALERT_RULE_CONSUMER, [''])[0], [ dataFieldEs, ]); + const alertDataProducer = useMemo(() => get(dataFieldEs, ALERT_RULE_PRODUCER, [''])[0], [ + dataFieldEs, + ]); const alert = parseObservabilityAlert(dataFieldEs); const { prepend } = core.http.basePath; @@ -204,7 +208,10 @@ function ObservabilityActions({ } }, [setActionsPopover, refetch]); - const alertPermissions = useGetUserAlertsPermissions(capabilities, alertDataConsumer); + const alertPermissions = useGetUserAlertsPermissions( + capabilities, + alertDataConsumer === 'alerts' ? alertDataProducer : alertDataConsumer + ); const statusActionItems = useStatusBulkActionItems({ eventIds: [eventId], @@ -298,8 +305,11 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { const casePermissions = useGetUserCasesPermissions(); const hasAlertsCrudPermissions = useCallback( - (featureId: string) => { - return getAlertsPermissions(capabilities, featureId).crud; + ({ ruleConsumer, ruleProducer }: { ruleConsumer: string; ruleProducer?: string }) => { + if (ruleConsumer === 'alerts' && ruleProducer) { + return getAlertsPermissions(capabilities, ruleProducer).crud; + } + return getAlertsPermissions(capabilities, ruleConsumer).crud; }, [capabilities] ); diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index 45a8dd842ee273..bba3b426598dfd 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -8,7 +8,6 @@ import { EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import { IndexPatternBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; import React, { useCallback, useRef } from 'react'; import { useHistory } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; @@ -43,12 +42,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { const history = useHistory(); const refetch = useRef<() => void>(); const { - query: { - rangeFrom = 'now-15m', - rangeTo = 'now', - kuery = `${ALERT_STATUS}: "${ALERT_STATUS_ACTIVE}"`, - workflowStatus = 'open', - }, + query: { rangeFrom = 'now-15m', rangeTo = 'now', kuery = '', workflowStatus = 'open' }, } = routeParams; useBreadcrumbs([ @@ -177,13 +171,11 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {

{i18n.translate('xpack.observability.alertsDisclaimerText', { defaultMessage: - 'This page shows an experimental alerting view. The data shown here will probably not be an accurate representation of alerts. A non-experimental list of alerts is available in the Alerts and Actions settings in Stack Management.', + 'This page shows an experimental list of alerts. The data might not be accurate. All alerts are available in the ', })} -

-

{i18n.translate('xpack.observability.alertsDisclaimerLinkText', { - defaultMessage: 'Alerts and Actions', + defaultMessage: 'Rules and Connectors settings.', })}

diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts index ecd7828cb828b8..15f1e48f1536e5 100644 --- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts +++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts @@ -36,10 +36,8 @@ export const useAgentPolicy = ({ policyId, skip, silent }: UseAgentPolicy) => { defaultMessage: 'Error while fetching agent policy details', }), }), - refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - staleTime: Infinity, } ); }; diff --git a/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts b/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts index 42790e46e0a973..65a2520e07d0bc 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts @@ -50,7 +50,6 @@ export const useAgentPolicyAgentIds = ({ defaultMessage: 'Error while fetching agents', }), }), - refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, } diff --git a/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx b/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx index 7cc561ff7a73af..1994ea348e30c1 100644 --- a/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx +++ b/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx @@ -22,9 +22,7 @@ export const useOsqueryIntegrationStatus = () => { defaultMessage: 'Error while fetching osquery integration', }), }), - refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - staleTime: Infinity, }); }; diff --git a/x-pack/plugins/osquery/public/common/schemas/ecs/v1.11.0.json b/x-pack/plugins/osquery/public/common/schemas/ecs/v1.11.0.json index 6d26f60000174d..406999901961dd 100644 --- a/x-pack/plugins/osquery/public/common/schemas/ecs/v1.11.0.json +++ b/x-pack/plugins/osquery/public/common/schemas/ecs/v1.11.0.json @@ -1 +1 @@ -[{"field":"@timestamp","type":"date","description":"Date/time when the event originated."},{"field":"labels","type":"object","description":"Custom key/value pairs."},{"field":"message","type":"text","description":"Log message optimized for viewing in a log viewer."},{"field":"tags","type":"keyword","description":"List of keywords used to tag each event."},{"field":"agent.build.original","type":"keyword","description":"Extended build information for the agent."},{"field":"agent.ephemeral_id","type":"keyword","description":"Ephemeral identifier of this agent."},{"field":"agent.id","type":"keyword","description":"Unique identifier of this agent."},{"field":"agent.name","type":"keyword","description":"Custom name of the agent."},{"field":"agent.type","type":"keyword","description":"Type of the agent."},{"field":"agent.version","type":"keyword","description":"Version of the agent."},{"field":"client.address","type":"keyword","description":"Client network address."},{"field":"client.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"client.as.organization.name","type":"keyword","description":"Organization name."},{"field":"client.as.organization.name.text","type":"text","description":"Organization name."},{"field":"client.bytes","type":"long","description":"Bytes sent from the client to the server."},{"field":"client.domain","type":"keyword","description":"Client domain."},{"field":"client.geo.city_name","type":"keyword","description":"City name."},{"field":"client.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"client.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"client.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"client.geo.country_name","type":"keyword","description":"Country name."},{"field":"client.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"client.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"client.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"client.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"client.geo.region_name","type":"keyword","description":"Region name."},{"field":"client.geo.timezone","type":"keyword","description":"Time zone."},{"field":"client.ip","type":"ip","description":"IP address of the client."},{"field":"client.mac","type":"keyword","description":"MAC address of the client."},{"field":"client.nat.ip","type":"ip","description":"Client NAT ip address"},{"field":"client.nat.port","type":"long","description":"Client NAT port"},{"field":"client.packets","type":"long","description":"Packets sent from the client to the server."},{"field":"client.port","type":"long","description":"Port of the client."},{"field":"client.registered_domain","type":"keyword","description":"The highest registered client domain, stripped of the subdomain."},{"field":"client.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"client.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"client.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"client.user.email","type":"keyword","description":"User email address."},{"field":"client.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"client.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"client.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"client.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"client.user.group.name","type":"keyword","description":"Name of the group."},{"field":"client.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"client.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"client.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"client.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"client.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"cloud.account.id","type":"keyword","description":"The cloud account or organization id."},{"field":"cloud.account.name","type":"keyword","description":"The cloud account name."},{"field":"cloud.availability_zone","type":"keyword","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.instance.id","type":"keyword","description":"Instance ID of the host machine."},{"field":"cloud.instance.name","type":"keyword","description":"Instance name of the host machine."},{"field":"cloud.machine.type","type":"keyword","description":"Machine type of the host machine."},{"field":"cloud.project.id","type":"keyword","description":"The cloud project id."},{"field":"cloud.project.name","type":"keyword","description":"The cloud project name."},{"field":"cloud.provider","type":"keyword","description":"Name of the cloud provider."},{"field":"cloud.region","type":"keyword","description":"Region in which this host, resource, or service is located."},{"field":"cloud.service.name","type":"keyword","description":"The cloud service name."},{"field":"container.id","type":"keyword","description":"Unique container id."},{"field":"container.image.name","type":"keyword","description":"Name of the image the container was built on."},{"field":"container.image.tag","type":"keyword","description":"Container image tags."},{"field":"container.labels","type":"object","description":"Image labels."},{"field":"container.name","type":"keyword","description":"Container name."},{"field":"container.runtime","type":"keyword","description":"Runtime managing this container."},{"field":"data_stream.dataset","type":"constant_keyword","description":"The field can contain anything that makes sense to signify the source of the data."},{"field":"data_stream.namespace","type":"constant_keyword","description":"A user defined namespace. Namespaces are useful to allow grouping of data."},{"field":"data_stream.type","type":"constant_keyword","description":"An overarching type for the data stream."},{"field":"destination.address","type":"keyword","description":"Destination network address."},{"field":"destination.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"destination.as.organization.name","type":"keyword","description":"Organization name."},{"field":"destination.as.organization.name.text","type":"text","description":"Organization name."},{"field":"destination.bytes","type":"long","description":"Bytes sent from the destination to the source."},{"field":"destination.domain","type":"keyword","description":"Destination domain."},{"field":"destination.geo.city_name","type":"keyword","description":"City name."},{"field":"destination.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"destination.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"destination.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"destination.geo.country_name","type":"keyword","description":"Country name."},{"field":"destination.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"destination.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"destination.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"destination.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"destination.geo.region_name","type":"keyword","description":"Region name."},{"field":"destination.geo.timezone","type":"keyword","description":"Time zone."},{"field":"destination.ip","type":"ip","description":"IP address of the destination."},{"field":"destination.mac","type":"keyword","description":"MAC address of the destination."},{"field":"destination.nat.ip","type":"ip","description":"Destination NAT ip"},{"field":"destination.nat.port","type":"long","description":"Destination NAT Port"},{"field":"destination.packets","type":"long","description":"Packets sent from the destination to the source."},{"field":"destination.port","type":"long","description":"Port of the destination."},{"field":"destination.registered_domain","type":"keyword","description":"The highest registered destination domain, stripped of the subdomain."},{"field":"destination.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"destination.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"destination.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"destination.user.email","type":"keyword","description":"User email address."},{"field":"destination.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"destination.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"destination.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"destination.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"destination.user.group.name","type":"keyword","description":"Name of the group."},{"field":"destination.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"destination.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"destination.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"destination.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"destination.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"dll.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"dll.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"dll.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"dll.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"dll.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"dll.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"dll.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"dll.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"dll.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"dll.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"dll.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"dll.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"dll.name","type":"keyword","description":"Name of the library."},{"field":"dll.path","type":"keyword","description":"Full file path of the library."},{"field":"dll.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"dll.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"dll.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"dll.pe.file_version","type":"keyword","description":"Process name."},{"field":"dll.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"dll.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"dll.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"dns.answers","type":"object","description":"Array of DNS answers."},{"field":"dns.answers.class","type":"keyword","description":"The class of DNS data contained in this resource record."},{"field":"dns.answers.data","type":"keyword","description":"The data describing the resource."},{"field":"dns.answers.name","type":"keyword","description":"The domain name to which this resource record pertains."},{"field":"dns.answers.ttl","type":"long","description":"The time interval in seconds that this resource record may be cached before it should be discarded."},{"field":"dns.answers.type","type":"keyword","description":"The type of data contained in this resource record."},{"field":"dns.header_flags","type":"keyword","description":"Array of DNS header flags."},{"field":"dns.id","type":"keyword","description":"The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response."},{"field":"dns.op_code","type":"keyword","description":"The DNS operation code that specifies the kind of query in the message."},{"field":"dns.question.class","type":"keyword","description":"The class of records being queried."},{"field":"dns.question.name","type":"keyword","description":"The name being queried."},{"field":"dns.question.registered_domain","type":"keyword","description":"The highest registered domain, stripped of the subdomain."},{"field":"dns.question.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"dns.question.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"dns.question.type","type":"keyword","description":"The type of record being queried."},{"field":"dns.resolved_ip","type":"ip","description":"Array containing all IPs seen in answers.data"},{"field":"dns.response_code","type":"keyword","description":"The DNS response code."},{"field":"dns.type","type":"keyword","description":"The type of DNS event captured, query or answer."},{"field":"ecs.version","type":"keyword","description":"ECS version this event conforms to."},{"field":"error.code","type":"keyword","description":"Error code describing the error."},{"field":"error.id","type":"keyword","description":"Unique identifier for the error."},{"field":"error.message","type":"text","description":"Error message."},{"field":"error.stack_trace","type":"keyword","description":"The stack trace of this error in plain text."},{"field":"error.stack_trace.text","type":"text","description":"The stack trace of this error in plain text."},{"field":"error.type","type":"keyword","description":"The type of the error, for example the class name of the exception."},{"field":"event.action","type":"keyword","description":"The action captured by the event."},{"field":"event.agent_id_status","type":"keyword","description":"Validation status of the event's agent.id field."},{"field":"event.category","type":"keyword","description":"Event category. The second categorization field in the hierarchy."},{"field":"event.code","type":"keyword","description":"Identification code for this event."},{"field":"event.created","type":"date","description":"Time when the event was first read by an agent or by your pipeline."},{"field":"event.dataset","type":"keyword","description":"Name of the dataset."},{"field":"event.duration","type":"long","description":"Duration of the event in nanoseconds."},{"field":"event.end","type":"date","description":"event.end contains the date when the event ended or when the activity was last observed."},{"field":"event.hash","type":"keyword","description":"Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity."},{"field":"event.id","type":"keyword","description":"Unique ID to describe the event."},{"field":"event.ingested","type":"date","description":"Timestamp when an event arrived in the central data store."},{"field":"event.kind","type":"keyword","description":"The kind of the event. The highest categorization field in the hierarchy."},{"field":"event.module","type":"keyword","description":"Name of the module this data is coming from."},{"field":"event.original","type":"keyword","description":"Raw text message of entire event."},{"field":"event.outcome","type":"keyword","description":"The outcome of the event. The lowest level categorization field in the hierarchy."},{"field":"event.provider","type":"keyword","description":"Source of the event."},{"field":"event.reason","type":"keyword","description":"Reason why this event happened, according to the source"},{"field":"event.reference","type":"keyword","description":"Event reference URL"},{"field":"event.risk_score","type":"float","description":"Risk score or priority of the event (e.g. security solutions). Use your system's original value here."},{"field":"event.risk_score_norm","type":"float","description":"Normalized risk score or priority of the event (0-100)."},{"field":"event.sequence","type":"long","description":"Sequence number of the event."},{"field":"event.severity","type":"long","description":"Numeric severity of the event."},{"field":"event.start","type":"date","description":"event.start contains the date when the event started or when the activity was first observed."},{"field":"event.timezone","type":"keyword","description":"Event time zone."},{"field":"event.type","type":"keyword","description":"Event type. The third categorization field in the hierarchy."},{"field":"event.url","type":"keyword","description":"Event investigation URL"},{"field":"file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"file.created","type":"date","description":"File creation time."},{"field":"file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"file.group","type":"keyword","description":"Primary group name of the file."},{"field":"file.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"file.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"file.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"file.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"file.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"file.owner","type":"keyword","description":"File owner's username."},{"field":"file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"file.path.text","type":"text","description":"Full path to the file, including the file name."},{"field":"file.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"file.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"file.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"file.pe.file_version","type":"keyword","description":"Process name."},{"field":"file.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"file.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"file.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"file.size","type":"long","description":"File size in bytes."},{"field":"file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"file.target_path.text","type":"text","description":"Target path for symlinks."},{"field":"file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"file.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"file.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"file.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"file.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"file.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"file.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"file.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"file.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"file.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"file.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"file.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"file.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"file.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"file.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"file.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"file.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"file.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"file.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"file.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"file.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"file.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"file.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"group.name","type":"keyword","description":"Name of the group."},{"field":"host.architecture","type":"keyword","description":"Operating system architecture."},{"field":"host.cpu.usage","type":"scaled_float","description":"Percent CPU used, between 0 and 1."},{"field":"host.disk.read.bytes","type":"long","description":"The number of bytes read by all disks."},{"field":"host.disk.write.bytes","type":"long","description":"The number of bytes written on all disks."},{"field":"host.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"host.geo.city_name","type":"keyword","description":"City name."},{"field":"host.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"host.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"host.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"host.geo.country_name","type":"keyword","description":"Country name."},{"field":"host.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"host.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"host.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"host.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"host.geo.region_name","type":"keyword","description":"Region name."},{"field":"host.geo.timezone","type":"keyword","description":"Time zone."},{"field":"host.hostname","type":"keyword","description":"Hostname of the host."},{"field":"host.id","type":"keyword","description":"Unique host id."},{"field":"host.ip","type":"ip","description":"Host ip addresses."},{"field":"host.mac","type":"keyword","description":"Host MAC addresses."},{"field":"host.name","type":"keyword","description":"Name of the host."},{"field":"host.network.egress.bytes","type":"long","description":"The number of bytes sent on all network interfaces."},{"field":"host.network.egress.packets","type":"long","description":"The number of packets sent on all network interfaces."},{"field":"host.network.ingress.bytes","type":"long","description":"The number of bytes received on all network interfaces."},{"field":"host.network.ingress.packets","type":"long","description":"The number of packets received on all network interfaces."},{"field":"host.os.family","type":"keyword","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"host.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"host.os.full.text","type":"text","description":"Operating system name, including the version or code name."},{"field":"host.os.kernel","type":"keyword","description":"Operating system kernel version as a raw string."},{"field":"host.os.name","type":"keyword","description":"Operating system name, without the version."},{"field":"host.os.name.text","type":"text","description":"Operating system name, without the version."},{"field":"host.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"host.os.type","type":"keyword","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"host.os.version","type":"keyword","description":"Operating system version as a raw string."},{"field":"host.type","type":"keyword","description":"Type of host."},{"field":"host.uptime","type":"long","description":"Seconds the host has been up."},{"field":"host.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"host.user.email","type":"keyword","description":"User email address."},{"field":"host.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"host.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"host.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"host.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"host.user.group.name","type":"keyword","description":"Name of the group."},{"field":"host.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"host.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"host.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"host.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"host.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"http.request.body.bytes","type":"long","description":"Size in bytes of the request body."},{"field":"http.request.body.content","type":"keyword","description":"The full HTTP request body."},{"field":"http.request.body.content.text","type":"text","description":"The full HTTP request body."},{"field":"http.request.bytes","type":"long","description":"Total size in bytes of the request (body and headers)."},{"field":"http.request.id","type":"keyword","description":"HTTP request ID."},{"field":"http.request.method","type":"keyword","description":"HTTP request method."},{"field":"http.request.mime_type","type":"keyword","description":"Mime type of the body of the request."},{"field":"http.request.referrer","type":"keyword","description":"Referrer for this HTTP request."},{"field":"http.response.body.bytes","type":"long","description":"Size in bytes of the response body."},{"field":"http.response.body.content","type":"keyword","description":"The full HTTP response body."},{"field":"http.response.body.content.text","type":"text","description":"The full HTTP response body."},{"field":"http.response.bytes","type":"long","description":"Total size in bytes of the response (body and headers)."},{"field":"http.response.mime_type","type":"keyword","description":"Mime type of the body of the response."},{"field":"http.response.status_code","type":"long","description":"HTTP response status code."},{"field":"http.version","type":"keyword","description":"HTTP version."},{"field":"log.file.path","type":"keyword","description":"Full path to the log file this event came from."},{"field":"log.level","type":"keyword","description":"Log level of the log event."},{"field":"log.logger","type":"keyword","description":"Name of the logger."},{"field":"log.origin.file.line","type":"integer","description":"The line number of the file which originated the log event."},{"field":"log.origin.file.name","type":"keyword","description":"The code file which originated the log event."},{"field":"log.origin.function","type":"keyword","description":"The function which originated the log event."},{"field":"log.original","type":"keyword","description":"Deprecated original log message with light interpretation only (encoding, newlines)."},{"field":"log.syslog","type":"object","description":"Syslog metadata"},{"field":"log.syslog.facility.code","type":"long","description":"Syslog numeric facility of the event."},{"field":"log.syslog.facility.name","type":"keyword","description":"Syslog text-based facility of the event."},{"field":"log.syslog.priority","type":"long","description":"Syslog priority of the event."},{"field":"log.syslog.severity.code","type":"long","description":"Syslog numeric severity of the event."},{"field":"log.syslog.severity.name","type":"keyword","description":"Syslog text-based severity of the event."},{"field":"network.application","type":"keyword","description":"Application level protocol name."},{"field":"network.bytes","type":"long","description":"Total bytes transferred in both directions."},{"field":"network.community_id","type":"keyword","description":"A hash of source and destination IPs and ports."},{"field":"network.direction","type":"keyword","description":"Direction of the network traffic."},{"field":"network.forwarded_ip","type":"ip","description":"Host IP address when the source IP address is the proxy."},{"field":"network.iana_number","type":"keyword","description":"IANA Protocol Number."},{"field":"network.inner","type":"object","description":"Inner VLAN tag information"},{"field":"network.inner.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"network.inner.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"network.name","type":"keyword","description":"Name given by operators to sections of their network."},{"field":"network.packets","type":"long","description":"Total packets transferred in both directions."},{"field":"network.protocol","type":"keyword","description":"L7 Network protocol name."},{"field":"network.transport","type":"keyword","description":"Protocol Name corresponding to the field `iana_number`."},{"field":"network.type","type":"keyword","description":"In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc"},{"field":"network.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"network.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress","type":"object","description":"Object field for egress information"},{"field":"observer.egress.interface.alias","type":"keyword","description":"Interface alias"},{"field":"observer.egress.interface.id","type":"keyword","description":"Interface ID"},{"field":"observer.egress.interface.name","type":"keyword","description":"Interface name"},{"field":"observer.egress.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"observer.egress.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress.zone","type":"keyword","description":"Observer Egress zone"},{"field":"observer.geo.city_name","type":"keyword","description":"City name."},{"field":"observer.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"observer.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"observer.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"observer.geo.country_name","type":"keyword","description":"Country name."},{"field":"observer.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"observer.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"observer.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"observer.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"observer.geo.region_name","type":"keyword","description":"Region name."},{"field":"observer.geo.timezone","type":"keyword","description":"Time zone."},{"field":"observer.hostname","type":"keyword","description":"Hostname of the observer."},{"field":"observer.ingress","type":"object","description":"Object field for ingress information"},{"field":"observer.ingress.interface.alias","type":"keyword","description":"Interface alias"},{"field":"observer.ingress.interface.id","type":"keyword","description":"Interface ID"},{"field":"observer.ingress.interface.name","type":"keyword","description":"Interface name"},{"field":"observer.ingress.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"observer.ingress.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.ingress.zone","type":"keyword","description":"Observer ingress zone"},{"field":"observer.ip","type":"ip","description":"IP addresses of the observer."},{"field":"observer.mac","type":"keyword","description":"MAC addresses of the observer."},{"field":"observer.name","type":"keyword","description":"Custom name of the observer."},{"field":"observer.os.family","type":"keyword","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"observer.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"observer.os.full.text","type":"text","description":"Operating system name, including the version or code name."},{"field":"observer.os.kernel","type":"keyword","description":"Operating system kernel version as a raw string."},{"field":"observer.os.name","type":"keyword","description":"Operating system name, without the version."},{"field":"observer.os.name.text","type":"text","description":"Operating system name, without the version."},{"field":"observer.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"observer.os.type","type":"keyword","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"observer.os.version","type":"keyword","description":"Operating system version as a raw string."},{"field":"observer.product","type":"keyword","description":"The product name of the observer."},{"field":"observer.serial_number","type":"keyword","description":"Observer serial number."},{"field":"observer.type","type":"keyword","description":"The type of the observer the data is coming from."},{"field":"observer.vendor","type":"keyword","description":"Vendor name of the observer."},{"field":"observer.version","type":"keyword","description":"Observer version."},{"field":"orchestrator.api_version","type":"keyword","description":"API version being used to carry out the action"},{"field":"orchestrator.cluster.name","type":"keyword","description":"Name of the cluster."},{"field":"orchestrator.cluster.url","type":"keyword","description":"URL of the API used to manage the cluster."},{"field":"orchestrator.cluster.version","type":"keyword","description":"The version of the cluster."},{"field":"orchestrator.namespace","type":"keyword","description":"Namespace in which the action is taking place."},{"field":"orchestrator.organization","type":"keyword","description":"Organization affected by the event (for multi-tenant orchestrator setups)."},{"field":"orchestrator.resource.name","type":"keyword","description":"Name of the resource being acted upon."},{"field":"orchestrator.resource.type","type":"keyword","description":"Type of resource being acted upon."},{"field":"orchestrator.type","type":"keyword","description":"Orchestrator cluster type (e.g. kubernetes, nomad or cloudfoundry)."},{"field":"organization.id","type":"keyword","description":"Unique identifier for the organization."},{"field":"organization.name","type":"keyword","description":"Organization name."},{"field":"organization.name.text","type":"text","description":"Organization name."},{"field":"package.architecture","type":"keyword","description":"Package architecture."},{"field":"package.build_version","type":"keyword","description":"Build version information"},{"field":"package.checksum","type":"keyword","description":"Checksum of the installed package for verification."},{"field":"package.description","type":"keyword","description":"Description of the package."},{"field":"package.install_scope","type":"keyword","description":"Indicating how the package was installed, e.g. user-local, global."},{"field":"package.installed","type":"date","description":"Time when package was installed."},{"field":"package.license","type":"keyword","description":"Package license"},{"field":"package.name","type":"keyword","description":"Package name"},{"field":"package.path","type":"keyword","description":"Path where the package is installed."},{"field":"package.reference","type":"keyword","description":"Package home page or reference URL"},{"field":"package.size","type":"long","description":"Package size in bytes."},{"field":"package.type","type":"keyword","description":"Package type"},{"field":"package.version","type":"keyword","description":"Package version"},{"field":"process.args","type":"keyword","description":"Array of process arguments."},{"field":"process.args_count","type":"long","description":"Length of the process.args array."},{"field":"process.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"process.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"process.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"process.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"process.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"process.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"process.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.command_line","type":"keyword","description":"Full command line that started the process."},{"field":"process.command_line.text","type":"text","description":"Full command line that started the process."},{"field":"process.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"process.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"process.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"process.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"process.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"process.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"process.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"process.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"process.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"process.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"process.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"process.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"process.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"process.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"process.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"process.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"process.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"process.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"process.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"process.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"process.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"process.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"process.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"process.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"process.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"process.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"process.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"process.entity_id","type":"keyword","description":"Unique identifier for the process."},{"field":"process.executable","type":"keyword","description":"Absolute path to the process executable."},{"field":"process.executable.text","type":"text","description":"Absolute path to the process executable."},{"field":"process.exit_code","type":"long","description":"The exit code of the process."},{"field":"process.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"process.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"process.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"process.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"process.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"process.name","type":"keyword","description":"Process name."},{"field":"process.name.text","type":"text","description":"Process name."},{"field":"process.parent.args","type":"keyword","description":"Array of process arguments."},{"field":"process.parent.args_count","type":"long","description":"Length of the process.args array."},{"field":"process.parent.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"process.parent.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"process.parent.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"process.parent.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"process.parent.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"process.parent.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"process.parent.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.parent.command_line","type":"keyword","description":"Full command line that started the process."},{"field":"process.parent.command_line.text","type":"text","description":"Full command line that started the process."},{"field":"process.parent.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"process.parent.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"process.parent.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"process.parent.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"process.parent.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"process.parent.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.parent.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"process.parent.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"process.parent.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"process.parent.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"process.parent.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.parent.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"process.parent.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"process.parent.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"process.parent.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"process.parent.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"process.parent.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"process.parent.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"process.parent.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"process.parent.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"process.parent.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"process.parent.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"process.parent.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"process.parent.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"process.parent.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"process.parent.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"process.parent.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"process.parent.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"process.parent.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"process.parent.entity_id","type":"keyword","description":"Unique identifier for the process."},{"field":"process.parent.executable","type":"keyword","description":"Absolute path to the process executable."},{"field":"process.parent.executable.text","type":"text","description":"Absolute path to the process executable."},{"field":"process.parent.exit_code","type":"long","description":"The exit code of the process."},{"field":"process.parent.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"process.parent.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"process.parent.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"process.parent.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"process.parent.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"process.parent.name","type":"keyword","description":"Process name."},{"field":"process.parent.name.text","type":"text","description":"Process name."},{"field":"process.parent.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"process.parent.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"process.parent.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"process.parent.pe.file_version","type":"keyword","description":"Process name."},{"field":"process.parent.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"process.parent.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"process.parent.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"process.parent.pgid","type":"long","description":"Identifier of the group of processes the process belongs to."},{"field":"process.parent.pid","type":"long","description":"Process id."},{"field":"process.parent.ppid","type":"long","description":"Parent process' pid."},{"field":"process.parent.start","type":"date","description":"The time the process started."},{"field":"process.parent.thread.id","type":"long","description":"Thread ID."},{"field":"process.parent.thread.name","type":"keyword","description":"Thread name."},{"field":"process.parent.title","type":"keyword","description":"Process title."},{"field":"process.parent.title.text","type":"text","description":"Process title."},{"field":"process.parent.uptime","type":"long","description":"Seconds the process has been up."},{"field":"process.parent.working_directory","type":"keyword","description":"The working directory of the process."},{"field":"process.parent.working_directory.text","type":"text","description":"The working directory of the process."},{"field":"process.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"process.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"process.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"process.pe.file_version","type":"keyword","description":"Process name."},{"field":"process.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"process.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"process.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"process.pgid","type":"long","description":"Identifier of the group of processes the process belongs to."},{"field":"process.pid","type":"long","description":"Process id."},{"field":"process.ppid","type":"long","description":"Parent process' pid."},{"field":"process.start","type":"date","description":"The time the process started."},{"field":"process.thread.id","type":"long","description":"Thread ID."},{"field":"process.thread.name","type":"keyword","description":"Thread name."},{"field":"process.title","type":"keyword","description":"Process title."},{"field":"process.title.text","type":"text","description":"Process title."},{"field":"process.uptime","type":"long","description":"Seconds the process has been up."},{"field":"process.working_directory","type":"keyword","description":"The working directory of the process."},{"field":"process.working_directory.text","type":"text","description":"The working directory of the process."},{"field":"registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"registry.data.strings","type":"keyword","description":"List of strings representing what was written to the registry."},{"field":"registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"registry.value","type":"keyword","description":"Name of the value written."},{"field":"related.hash","type":"keyword","description":"All the hashes seen on your event."},{"field":"related.hosts","type":"keyword","description":"All the host identifiers seen on your event."},{"field":"related.ip","type":"ip","description":"All of the IPs seen on your event."},{"field":"related.user","type":"keyword","description":"All the user names or other user identifiers seen on the event."},{"field":"rule.author","type":"keyword","description":"Rule author"},{"field":"rule.category","type":"keyword","description":"Rule category"},{"field":"rule.description","type":"keyword","description":"Rule description"},{"field":"rule.id","type":"keyword","description":"Rule ID"},{"field":"rule.license","type":"keyword","description":"Rule license"},{"field":"rule.name","type":"keyword","description":"Rule name"},{"field":"rule.reference","type":"keyword","description":"Rule reference URL"},{"field":"rule.ruleset","type":"keyword","description":"Rule ruleset"},{"field":"rule.uuid","type":"keyword","description":"Rule UUID"},{"field":"rule.version","type":"keyword","description":"Rule version"},{"field":"server.address","type":"keyword","description":"Server network address."},{"field":"server.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"server.as.organization.name","type":"keyword","description":"Organization name."},{"field":"server.as.organization.name.text","type":"text","description":"Organization name."},{"field":"server.bytes","type":"long","description":"Bytes sent from the server to the client."},{"field":"server.domain","type":"keyword","description":"Server domain."},{"field":"server.geo.city_name","type":"keyword","description":"City name."},{"field":"server.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"server.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"server.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"server.geo.country_name","type":"keyword","description":"Country name."},{"field":"server.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"server.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"server.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"server.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"server.geo.region_name","type":"keyword","description":"Region name."},{"field":"server.geo.timezone","type":"keyword","description":"Time zone."},{"field":"server.ip","type":"ip","description":"IP address of the server."},{"field":"server.mac","type":"keyword","description":"MAC address of the server."},{"field":"server.nat.ip","type":"ip","description":"Server NAT ip"},{"field":"server.nat.port","type":"long","description":"Server NAT port"},{"field":"server.packets","type":"long","description":"Packets sent from the server to the client."},{"field":"server.port","type":"long","description":"Port of the server."},{"field":"server.registered_domain","type":"keyword","description":"The highest registered server domain, stripped of the subdomain."},{"field":"server.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"server.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"server.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"server.user.email","type":"keyword","description":"User email address."},{"field":"server.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"server.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"server.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"server.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"server.user.group.name","type":"keyword","description":"Name of the group."},{"field":"server.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"server.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"server.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"server.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"server.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"service.ephemeral_id","type":"keyword","description":"Ephemeral identifier of this service."},{"field":"service.id","type":"keyword","description":"Unique identifier of the running service."},{"field":"service.name","type":"keyword","description":"Name of the service."},{"field":"service.node.name","type":"keyword","description":"Name of the service node."},{"field":"service.state","type":"keyword","description":"Current state of the service."},{"field":"service.type","type":"keyword","description":"The type of the service."},{"field":"service.version","type":"keyword","description":"Version of the service."},{"field":"source.address","type":"keyword","description":"Source network address."},{"field":"source.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"source.as.organization.name","type":"keyword","description":"Organization name."},{"field":"source.as.organization.name.text","type":"text","description":"Organization name."},{"field":"source.bytes","type":"long","description":"Bytes sent from the source to the destination."},{"field":"source.domain","type":"keyword","description":"Source domain."},{"field":"source.geo.city_name","type":"keyword","description":"City name."},{"field":"source.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"source.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"source.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"source.geo.country_name","type":"keyword","description":"Country name."},{"field":"source.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"source.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"source.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"source.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"source.geo.region_name","type":"keyword","description":"Region name."},{"field":"source.geo.timezone","type":"keyword","description":"Time zone."},{"field":"source.ip","type":"ip","description":"IP address of the source."},{"field":"source.mac","type":"keyword","description":"MAC address of the source."},{"field":"source.nat.ip","type":"ip","description":"Source NAT ip"},{"field":"source.nat.port","type":"long","description":"Source NAT port"},{"field":"source.packets","type":"long","description":"Packets sent from the source to the destination."},{"field":"source.port","type":"long","description":"Port of the source."},{"field":"source.registered_domain","type":"keyword","description":"The highest registered source domain, stripped of the subdomain."},{"field":"source.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"source.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"source.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"source.user.email","type":"keyword","description":"User email address."},{"field":"source.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"source.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"source.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"source.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"source.user.group.name","type":"keyword","description":"Name of the group."},{"field":"source.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"source.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"source.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"source.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"source.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"span.id","type":"keyword","description":"Unique identifier of the span within the scope of its trace."},{"field":"threat.enrichments","type":"nested","description":"List of objects containing indicators enriching the event."},{"field":"threat.enrichments.indicator","type":"object","description":"Object containing indicators enriching the event."},{"field":"threat.enrichments.indicator.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"threat.enrichments.indicator.as.organization.name","type":"keyword","description":"Organization name."},{"field":"threat.enrichments.indicator.as.organization.name.text","type":"text","description":"Organization name."},{"field":"threat.enrichments.indicator.confidence","type":"keyword","description":"Indicator confidence rating"},{"field":"threat.enrichments.indicator.description","type":"keyword","description":"Indicator description"},{"field":"threat.enrichments.indicator.email.address","type":"keyword","description":"Indicator email address"},{"field":"threat.enrichments.indicator.file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"threat.enrichments.indicator.file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"threat.enrichments.indicator.file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"threat.enrichments.indicator.file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"threat.enrichments.indicator.file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"threat.enrichments.indicator.file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"threat.enrichments.indicator.file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.enrichments.indicator.file.created","type":"date","description":"File creation time."},{"field":"threat.enrichments.indicator.file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"threat.enrichments.indicator.file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"threat.enrichments.indicator.file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"threat.enrichments.indicator.file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"threat.enrichments.indicator.file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"threat.enrichments.indicator.file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"threat.enrichments.indicator.file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"threat.enrichments.indicator.file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.enrichments.indicator.file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"threat.enrichments.indicator.file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.enrichments.indicator.file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"threat.enrichments.indicator.file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"threat.enrichments.indicator.file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"threat.enrichments.indicator.file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"threat.enrichments.indicator.file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"threat.enrichments.indicator.file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"threat.enrichments.indicator.file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"threat.enrichments.indicator.file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"threat.enrichments.indicator.file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"threat.enrichments.indicator.file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"threat.enrichments.indicator.file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"threat.enrichments.indicator.file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"threat.enrichments.indicator.file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"threat.enrichments.indicator.file.group","type":"keyword","description":"Primary group name of the file."},{"field":"threat.enrichments.indicator.file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"threat.enrichments.indicator.file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.enrichments.indicator.file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"threat.enrichments.indicator.file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"threat.enrichments.indicator.file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"threat.enrichments.indicator.file.owner","type":"keyword","description":"File owner's username."},{"field":"threat.enrichments.indicator.file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.path.text","type":"text","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.size","type":"long","description":"File size in bytes."},{"field":"threat.enrichments.indicator.file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.target_path.text","type":"text","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"threat.enrichments.indicator.file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.enrichments.indicator.first_seen","type":"date","description":"Date/time indicator was first reported."},{"field":"threat.enrichments.indicator.geo.city_name","type":"keyword","description":"City name."},{"field":"threat.enrichments.indicator.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"threat.enrichments.indicator.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"threat.enrichments.indicator.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"threat.enrichments.indicator.geo.country_name","type":"keyword","description":"Country name."},{"field":"threat.enrichments.indicator.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"threat.enrichments.indicator.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"threat.enrichments.indicator.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"threat.enrichments.indicator.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"threat.enrichments.indicator.geo.region_name","type":"keyword","description":"Region name."},{"field":"threat.enrichments.indicator.geo.timezone","type":"keyword","description":"Time zone."},{"field":"threat.enrichments.indicator.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"threat.enrichments.indicator.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"threat.enrichments.indicator.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"threat.enrichments.indicator.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"threat.enrichments.indicator.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"threat.enrichments.indicator.ip","type":"ip","description":"Indicator IP address"},{"field":"threat.enrichments.indicator.last_seen","type":"date","description":"Date/time indicator was last reported."},{"field":"threat.enrichments.indicator.marking.tlp","type":"keyword","description":"Indicator TLP marking"},{"field":"threat.enrichments.indicator.modified_at","type":"date","description":"Date/time indicator was last updated."},{"field":"threat.enrichments.indicator.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"threat.enrichments.indicator.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.pe.file_version","type":"keyword","description":"Process name."},{"field":"threat.enrichments.indicator.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"threat.enrichments.indicator.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.port","type":"long","description":"Indicator port"},{"field":"threat.enrichments.indicator.provider","type":"keyword","description":"Indicator provider"},{"field":"threat.enrichments.indicator.reference","type":"keyword","description":"Indicator reference URL"},{"field":"threat.enrichments.indicator.registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"threat.enrichments.indicator.registry.data.strings","type":"keyword","description":"List of strings representing what was written to the registry."},{"field":"threat.enrichments.indicator.registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"threat.enrichments.indicator.registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"threat.enrichments.indicator.registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"threat.enrichments.indicator.registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"threat.enrichments.indicator.registry.value","type":"keyword","description":"Name of the value written."},{"field":"threat.enrichments.indicator.scanner_stats","type":"long","description":"Scanner statistics"},{"field":"threat.enrichments.indicator.sightings","type":"long","description":"Number of times indicator observed"},{"field":"threat.enrichments.indicator.type","type":"keyword","description":"Type of indicator"},{"field":"threat.enrichments.indicator.url.domain","type":"keyword","description":"Domain of the url."},{"field":"threat.enrichments.indicator.url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.enrichments.indicator.url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"threat.enrichments.indicator.url.full","type":"keyword","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.full.text","type":"text","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.original","type":"keyword","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.original.text","type":"text","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.password","type":"keyword","description":"Password of the request."},{"field":"threat.enrichments.indicator.url.path","type":"keyword","description":"Path of the request, such as \"/search\"."},{"field":"threat.enrichments.indicator.url.port","type":"long","description":"Port of the request, such as 443."},{"field":"threat.enrichments.indicator.url.query","type":"keyword","description":"Query string of the request."},{"field":"threat.enrichments.indicator.url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.enrichments.indicator.url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"threat.enrichments.indicator.url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"threat.enrichments.indicator.url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.enrichments.indicator.url.username","type":"keyword","description":"Username of the request."},{"field":"threat.enrichments.indicator.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.enrichments.indicator.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.enrichments.indicator.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.enrichments.matched.atomic","type":"keyword","description":"Matched indicator value"},{"field":"threat.enrichments.matched.field","type":"keyword","description":"Matched indicator field"},{"field":"threat.enrichments.matched.id","type":"keyword","description":"Matched indicator identifier"},{"field":"threat.enrichments.matched.index","type":"keyword","description":"Matched indicator index"},{"field":"threat.enrichments.matched.type","type":"keyword","description":"Type of indicator match"},{"field":"threat.framework","type":"keyword","description":"Threat classification framework."},{"field":"threat.group.alias","type":"keyword","description":"Alias of the group."},{"field":"threat.group.id","type":"keyword","description":"ID of the group."},{"field":"threat.group.name","type":"keyword","description":"Name of the group."},{"field":"threat.group.reference","type":"keyword","description":"Reference URL of the group."},{"field":"threat.indicator.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"threat.indicator.as.organization.name","type":"keyword","description":"Organization name."},{"field":"threat.indicator.as.organization.name.text","type":"text","description":"Organization name."},{"field":"threat.indicator.confidence","type":"keyword","description":"Indicator confidence rating"},{"field":"threat.indicator.description","type":"keyword","description":"Indicator description"},{"field":"threat.indicator.email.address","type":"keyword","description":"Indicator email address"},{"field":"threat.indicator.file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"threat.indicator.file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"threat.indicator.file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"threat.indicator.file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"threat.indicator.file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"threat.indicator.file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"threat.indicator.file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.indicator.file.created","type":"date","description":"File creation time."},{"field":"threat.indicator.file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"threat.indicator.file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"threat.indicator.file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"threat.indicator.file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"threat.indicator.file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"threat.indicator.file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"threat.indicator.file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"threat.indicator.file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"threat.indicator.file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"threat.indicator.file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.indicator.file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"threat.indicator.file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"threat.indicator.file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"threat.indicator.file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"threat.indicator.file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.indicator.file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"threat.indicator.file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"threat.indicator.file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"threat.indicator.file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"threat.indicator.file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"threat.indicator.file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"threat.indicator.file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"threat.indicator.file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"threat.indicator.file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"threat.indicator.file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"threat.indicator.file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"threat.indicator.file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"threat.indicator.file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"threat.indicator.file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"threat.indicator.file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"threat.indicator.file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"threat.indicator.file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"threat.indicator.file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"threat.indicator.file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"threat.indicator.file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"threat.indicator.file.group","type":"keyword","description":"Primary group name of the file."},{"field":"threat.indicator.file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"threat.indicator.file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.indicator.file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"threat.indicator.file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"threat.indicator.file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"threat.indicator.file.owner","type":"keyword","description":"File owner's username."},{"field":"threat.indicator.file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.path.text","type":"text","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.size","type":"long","description":"File size in bytes."},{"field":"threat.indicator.file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"threat.indicator.file.target_path.text","type":"text","description":"Target path for symlinks."},{"field":"threat.indicator.file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"threat.indicator.file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.indicator.first_seen","type":"date","description":"Date/time indicator was first reported."},{"field":"threat.indicator.geo.city_name","type":"keyword","description":"City name."},{"field":"threat.indicator.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"threat.indicator.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"threat.indicator.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"threat.indicator.geo.country_name","type":"keyword","description":"Country name."},{"field":"threat.indicator.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"threat.indicator.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"threat.indicator.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"threat.indicator.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"threat.indicator.geo.region_name","type":"keyword","description":"Region name."},{"field":"threat.indicator.geo.timezone","type":"keyword","description":"Time zone."},{"field":"threat.indicator.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"threat.indicator.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"threat.indicator.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"threat.indicator.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"threat.indicator.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"threat.indicator.ip","type":"ip","description":"Indicator IP address"},{"field":"threat.indicator.last_seen","type":"date","description":"Date/time indicator was last reported."},{"field":"threat.indicator.marking.tlp","type":"keyword","description":"Indicator TLP marking"},{"field":"threat.indicator.modified_at","type":"date","description":"Date/time indicator was last updated."},{"field":"threat.indicator.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"threat.indicator.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.indicator.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"threat.indicator.pe.file_version","type":"keyword","description":"Process name."},{"field":"threat.indicator.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"threat.indicator.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"threat.indicator.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.indicator.port","type":"long","description":"Indicator port"},{"field":"threat.indicator.provider","type":"keyword","description":"Indicator provider"},{"field":"threat.indicator.reference","type":"keyword","description":"Indicator reference URL"},{"field":"threat.indicator.registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"threat.indicator.registry.data.strings","type":"keyword","description":"List of strings representing what was written to the registry."},{"field":"threat.indicator.registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"threat.indicator.registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"threat.indicator.registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"threat.indicator.registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"threat.indicator.registry.value","type":"keyword","description":"Name of the value written."},{"field":"threat.indicator.scanner_stats","type":"long","description":"Scanner statistics"},{"field":"threat.indicator.sightings","type":"long","description":"Number of times indicator observed"},{"field":"threat.indicator.type","type":"keyword","description":"Type of indicator"},{"field":"threat.indicator.url.domain","type":"keyword","description":"Domain of the url."},{"field":"threat.indicator.url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.indicator.url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"threat.indicator.url.full","type":"keyword","description":"Full unparsed URL."},{"field":"threat.indicator.url.full.text","type":"text","description":"Full unparsed URL."},{"field":"threat.indicator.url.original","type":"keyword","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.original.text","type":"text","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.password","type":"keyword","description":"Password of the request."},{"field":"threat.indicator.url.path","type":"keyword","description":"Path of the request, such as \"/search\"."},{"field":"threat.indicator.url.port","type":"long","description":"Port of the request, such as 443."},{"field":"threat.indicator.url.query","type":"keyword","description":"Query string of the request."},{"field":"threat.indicator.url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.indicator.url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"threat.indicator.url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"threat.indicator.url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.indicator.url.username","type":"keyword","description":"Username of the request."},{"field":"threat.indicator.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.indicator.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.indicator.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.indicator.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.indicator.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.indicator.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.software.id","type":"keyword","description":"ID of the software"},{"field":"threat.software.name","type":"keyword","description":"Name of the software."},{"field":"threat.software.platforms","type":"keyword","description":"Platforms of the software."},{"field":"threat.software.reference","type":"keyword","description":"Software reference URL."},{"field":"threat.software.type","type":"keyword","description":"Software type."},{"field":"threat.tactic.id","type":"keyword","description":"Threat tactic id."},{"field":"threat.tactic.name","type":"keyword","description":"Threat tactic."},{"field":"threat.tactic.reference","type":"keyword","description":"Threat tactic URL reference."},{"field":"threat.technique.id","type":"keyword","description":"Threat technique id."},{"field":"threat.technique.name","type":"keyword","description":"Threat technique name."},{"field":"threat.technique.name.text","type":"text","description":"Threat technique name."},{"field":"threat.technique.reference","type":"keyword","description":"Threat technique URL reference."},{"field":"threat.technique.subtechnique.id","type":"keyword","description":"Threat subtechnique id."},{"field":"threat.technique.subtechnique.name","type":"keyword","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.name.text","type":"text","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.reference","type":"keyword","description":"Threat subtechnique URL reference."},{"field":"tls.cipher","type":"keyword","description":"String indicating the cipher used during the current connection."},{"field":"tls.client.certificate","type":"keyword","description":"PEM-encoded stand-alone certificate offered by the client."},{"field":"tls.client.certificate_chain","type":"keyword","description":"Array of PEM-encoded certificates that make up the certificate chain offered by the client."},{"field":"tls.client.hash.md5","type":"keyword","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha1","type":"keyword","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha256","type":"keyword","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.issuer","type":"keyword","description":"Distinguished name of subject of the issuer of the x.509 certificate presented by the client."},{"field":"tls.client.ja3","type":"keyword","description":"A hash that identifies clients based on how they perform an SSL/TLS handshake."},{"field":"tls.client.not_after","type":"date","description":"Date/Time indicating when client certificate is no longer considered valid."},{"field":"tls.client.not_before","type":"date","description":"Date/Time indicating when client certificate is first considered valid."},{"field":"tls.client.server_name","type":"keyword","description":"Hostname the client is trying to connect to. Also called the SNI."},{"field":"tls.client.subject","type":"keyword","description":"Distinguished name of subject of the x.509 certificate presented by the client."},{"field":"tls.client.supported_ciphers","type":"keyword","description":"Array of ciphers offered by the client during the client hello."},{"field":"tls.client.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"tls.client.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"tls.client.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.client.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.client.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.client.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.client.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"tls.client.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"tls.client.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.client.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.client.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"tls.client.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"tls.client.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"tls.client.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"tls.client.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"tls.client.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.client.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.client.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"tls.client.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"tls.client.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"tls.curve","type":"keyword","description":"String indicating the curve used for the given cipher, when applicable."},{"field":"tls.established","type":"boolean","description":"Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel."},{"field":"tls.next_protocol","type":"keyword","description":"String indicating the protocol being tunneled."},{"field":"tls.resumed","type":"boolean","description":"Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation."},{"field":"tls.server.certificate","type":"keyword","description":"PEM-encoded stand-alone certificate offered by the server."},{"field":"tls.server.certificate_chain","type":"keyword","description":"Array of PEM-encoded certificates that make up the certificate chain offered by the server."},{"field":"tls.server.hash.md5","type":"keyword","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha1","type":"keyword","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha256","type":"keyword","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.issuer","type":"keyword","description":"Subject of the issuer of the x.509 certificate presented by the server."},{"field":"tls.server.ja3s","type":"keyword","description":"A hash that identifies servers based on how they perform an SSL/TLS handshake."},{"field":"tls.server.not_after","type":"date","description":"Timestamp indicating when server certificate is no longer considered valid."},{"field":"tls.server.not_before","type":"date","description":"Timestamp indicating when server certificate is first considered valid."},{"field":"tls.server.subject","type":"keyword","description":"Subject of the x.509 certificate presented by the server."},{"field":"tls.server.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"tls.server.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"tls.server.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.server.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.server.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.server.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.server.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"tls.server.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"tls.server.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.server.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.server.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"tls.server.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"tls.server.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"tls.server.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"tls.server.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"tls.server.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.server.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.server.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"tls.server.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"tls.server.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"tls.version","type":"keyword","description":"Numeric part of the version parsed from the original string."},{"field":"tls.version_protocol","type":"keyword","description":"Normalized lowercase protocol name parsed from original string."},{"field":"trace.id","type":"keyword","description":"Unique identifier of the trace."},{"field":"transaction.id","type":"keyword","description":"Unique identifier of the transaction within the scope of its trace."},{"field":"url.domain","type":"keyword","description":"Domain of the url."},{"field":"url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"url.full","type":"keyword","description":"Full unparsed URL."},{"field":"url.full.text","type":"text","description":"Full unparsed URL."},{"field":"url.original","type":"keyword","description":"Unmodified original url as seen in the event source."},{"field":"url.original.text","type":"text","description":"Unmodified original url as seen in the event source."},{"field":"url.password","type":"keyword","description":"Password of the request."},{"field":"url.path","type":"keyword","description":"Path of the request, such as \"/search\"."},{"field":"url.port","type":"long","description":"Port of the request, such as 443."},{"field":"url.query","type":"keyword","description":"Query string of the request."},{"field":"url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"url.username","type":"keyword","description":"Username of the request."},{"field":"user.changes.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.changes.email","type":"keyword","description":"User email address."},{"field":"user.changes.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.changes.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.changes.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.changes.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.changes.group.name","type":"keyword","description":"Name of the group."},{"field":"user.changes.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.changes.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.changes.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.changes.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.changes.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.effective.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.effective.email","type":"keyword","description":"User email address."},{"field":"user.effective.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.effective.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.effective.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.effective.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.effective.group.name","type":"keyword","description":"Name of the group."},{"field":"user.effective.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.effective.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.effective.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.effective.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.effective.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.email","type":"keyword","description":"User email address."},{"field":"user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.group.name","type":"keyword","description":"Name of the group."},{"field":"user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.target.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.target.email","type":"keyword","description":"User email address."},{"field":"user.target.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.target.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.target.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.target.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.target.group.name","type":"keyword","description":"Name of the group."},{"field":"user.target.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.target.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.target.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.target.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.target.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user_agent.device.name","type":"keyword","description":"Name of the device."},{"field":"user_agent.name","type":"keyword","description":"Name of the user agent."},{"field":"user_agent.original","type":"keyword","description":"Unparsed user_agent string."},{"field":"user_agent.original.text","type":"text","description":"Unparsed user_agent string."},{"field":"user_agent.os.family","type":"keyword","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"user_agent.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.full.text","type":"text","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.kernel","type":"keyword","description":"Operating system kernel version as a raw string."},{"field":"user_agent.os.name","type":"keyword","description":"Operating system name, without the version."},{"field":"user_agent.os.name.text","type":"text","description":"Operating system name, without the version."},{"field":"user_agent.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"user_agent.os.type","type":"keyword","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"user_agent.os.version","type":"keyword","description":"Operating system version as a raw string."},{"field":"user_agent.version","type":"keyword","description":"Version of the user agent."},{"field":"vulnerability.category","type":"keyword","description":"Category of a vulnerability."},{"field":"vulnerability.classification","type":"keyword","description":"Classification of the vulnerability."},{"field":"vulnerability.description","type":"keyword","description":"Description of the vulnerability."},{"field":"vulnerability.description.text","type":"text","description":"Description of the vulnerability."},{"field":"vulnerability.enumeration","type":"keyword","description":"Identifier of the vulnerability."},{"field":"vulnerability.id","type":"keyword","description":"ID of the vulnerability."},{"field":"vulnerability.reference","type":"keyword","description":"Reference of the vulnerability."},{"field":"vulnerability.report_id","type":"keyword","description":"Scan identification number."},{"field":"vulnerability.scanner.vendor","type":"keyword","description":"Name of the scanner vendor."},{"field":"vulnerability.score.base","type":"float","description":"Vulnerability Base score."},{"field":"vulnerability.score.environmental","type":"float","description":"Vulnerability Environmental score."},{"field":"vulnerability.score.temporal","type":"float","description":"Vulnerability Temporal score."},{"field":"vulnerability.score.version","type":"keyword","description":"CVSS version."},{"field":"vulnerability.severity","type":"keyword","description":"Severity of the vulnerability."}] \ No newline at end of file +[{"field":"labels","type":"object","description":"Custom key/value pairs."},{"field":"message","type":"text","description":"Log message optimized for viewing in a log viewer."},{"field":"tags","type":"keyword","description":"List of keywords used to tag each event."},{"field":"agent.build.original","type":"keyword","description":"Extended build information for the agent."},{"field":"client.address","type":"keyword","description":"Client network address."},{"field":"client.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"client.as.organization.name","type":"keyword","description":"Organization name."},{"field":"client.as.organization.name.text","type":"text","description":"Organization name."},{"field":"client.bytes","type":"long","description":"Bytes sent from the client to the server."},{"field":"client.domain","type":"keyword","description":"Client domain."},{"field":"client.geo.city_name","type":"keyword","description":"City name."},{"field":"client.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"client.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"client.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"client.geo.country_name","type":"keyword","description":"Country name."},{"field":"client.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"client.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"client.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"client.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"client.geo.region_name","type":"keyword","description":"Region name."},{"field":"client.geo.timezone","type":"keyword","description":"Time zone."},{"field":"client.ip","type":"ip","description":"IP address of the client."},{"field":"client.mac","type":"keyword","description":"MAC address of the client."},{"field":"client.nat.ip","type":"ip","description":"Client NAT ip address"},{"field":"client.nat.port","type":"long","description":"Client NAT port"},{"field":"client.packets","type":"long","description":"Packets sent from the client to the server."},{"field":"client.port","type":"long","description":"Port of the client."},{"field":"client.registered_domain","type":"keyword","description":"The highest registered client domain, stripped of the subdomain."},{"field":"client.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"client.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"client.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"client.user.email","type":"keyword","description":"User email address."},{"field":"client.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"client.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"client.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"client.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"client.user.group.name","type":"keyword","description":"Name of the group."},{"field":"client.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"client.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"client.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"client.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"client.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"cloud.account.id","type":"keyword","description":"The cloud account or organization id."},{"field":"cloud.account.name","type":"keyword","description":"The cloud account name."},{"field":"cloud.availability_zone","type":"keyword","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.instance.id","type":"keyword","description":"Instance ID of the host machine."},{"field":"cloud.instance.name","type":"keyword","description":"Instance name of the host machine."},{"field":"cloud.machine.type","type":"keyword","description":"Machine type of the host machine."},{"field":"cloud.project.id","type":"keyword","description":"The cloud project id."},{"field":"cloud.project.name","type":"keyword","description":"The cloud project name."},{"field":"cloud.provider","type":"keyword","description":"Name of the cloud provider."},{"field":"cloud.region","type":"keyword","description":"Region in which this host, resource, or service is located."},{"field":"cloud.service.name","type":"keyword","description":"The cloud service name."},{"field":"container.id","type":"keyword","description":"Unique container id."},{"field":"container.image.name","type":"keyword","description":"Name of the image the container was built on."},{"field":"container.image.tag","type":"keyword","description":"Container image tags."},{"field":"container.labels","type":"object","description":"Image labels."},{"field":"container.name","type":"keyword","description":"Container name."},{"field":"container.runtime","type":"keyword","description":"Runtime managing this container."},{"field":"data_stream.dataset","type":"constant_keyword","description":"The field can contain anything that makes sense to signify the source of the data."},{"field":"data_stream.namespace","type":"constant_keyword","description":"A user defined namespace. Namespaces are useful to allow grouping of data."},{"field":"data_stream.type","type":"constant_keyword","description":"An overarching type for the data stream."},{"field":"destination.address","type":"keyword","description":"Destination network address."},{"field":"destination.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"destination.as.organization.name","type":"keyword","description":"Organization name."},{"field":"destination.as.organization.name.text","type":"text","description":"Organization name."},{"field":"destination.bytes","type":"long","description":"Bytes sent from the destination to the source."},{"field":"destination.domain","type":"keyword","description":"Destination domain."},{"field":"destination.geo.city_name","type":"keyword","description":"City name."},{"field":"destination.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"destination.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"destination.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"destination.geo.country_name","type":"keyword","description":"Country name."},{"field":"destination.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"destination.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"destination.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"destination.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"destination.geo.region_name","type":"keyword","description":"Region name."},{"field":"destination.geo.timezone","type":"keyword","description":"Time zone."},{"field":"destination.ip","type":"ip","description":"IP address of the destination."},{"field":"destination.mac","type":"keyword","description":"MAC address of the destination."},{"field":"destination.nat.ip","type":"ip","description":"Destination NAT ip"},{"field":"destination.nat.port","type":"long","description":"Destination NAT Port"},{"field":"destination.packets","type":"long","description":"Packets sent from the destination to the source."},{"field":"destination.port","type":"long","description":"Port of the destination."},{"field":"destination.registered_domain","type":"keyword","description":"The highest registered destination domain, stripped of the subdomain."},{"field":"destination.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"destination.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"destination.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"destination.user.email","type":"keyword","description":"User email address."},{"field":"destination.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"destination.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"destination.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"destination.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"destination.user.group.name","type":"keyword","description":"Name of the group."},{"field":"destination.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"destination.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"destination.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"destination.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"destination.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"dll.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"dll.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"dll.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"dll.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"dll.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"dll.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"dll.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"dll.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"dll.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"dll.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"dll.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"dll.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"dll.name","type":"keyword","description":"Name of the library."},{"field":"dll.path","type":"keyword","description":"Full file path of the library."},{"field":"dll.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"dll.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"dll.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"dll.pe.file_version","type":"keyword","description":"Process name."},{"field":"dll.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"dll.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"dll.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"dns.answers","type":"object","description":"Array of DNS answers."},{"field":"dns.answers.class","type":"keyword","description":"The class of DNS data contained in this resource record."},{"field":"dns.answers.data","type":"keyword","description":"The data describing the resource."},{"field":"dns.answers.name","type":"keyword","description":"The domain name to which this resource record pertains."},{"field":"dns.answers.ttl","type":"long","description":"The time interval in seconds that this resource record may be cached before it should be discarded."},{"field":"dns.answers.type","type":"keyword","description":"The type of data contained in this resource record."},{"field":"dns.header_flags","type":"keyword","description":"Array of DNS header flags."},{"field":"dns.id","type":"keyword","description":"The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response."},{"field":"dns.op_code","type":"keyword","description":"The DNS operation code that specifies the kind of query in the message."},{"field":"dns.question.class","type":"keyword","description":"The class of records being queried."},{"field":"dns.question.name","type":"keyword","description":"The name being queried."},{"field":"dns.question.registered_domain","type":"keyword","description":"The highest registered domain, stripped of the subdomain."},{"field":"dns.question.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"dns.question.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"dns.question.type","type":"keyword","description":"The type of record being queried."},{"field":"dns.resolved_ip","type":"ip","description":"Array containing all IPs seen in answers.data"},{"field":"dns.response_code","type":"keyword","description":"The DNS response code."},{"field":"dns.type","type":"keyword","description":"The type of DNS event captured, query or answer."},{"field":"error.code","type":"keyword","description":"Error code describing the error."},{"field":"error.id","type":"keyword","description":"Unique identifier for the error."},{"field":"error.message","type":"text","description":"Error message."},{"field":"error.stack_trace","type":"keyword","description":"The stack trace of this error in plain text."},{"field":"error.stack_trace.text","type":"text","description":"The stack trace of this error in plain text."},{"field":"error.type","type":"keyword","description":"The type of the error, for example the class name of the exception."},{"field":"event.action","type":"keyword","description":"The action captured by the event."},{"field":"event.category","type":"keyword","description":"Event category. The second categorization field in the hierarchy."},{"field":"event.code","type":"keyword","description":"Identification code for this event."},{"field":"event.created","type":"date","description":"Time when the event was first read by an agent or by your pipeline."},{"field":"event.dataset","type":"keyword","description":"Name of the dataset."},{"field":"event.duration","type":"long","description":"Duration of the event in nanoseconds."},{"field":"event.end","type":"date","description":"event.end contains the date when the event ended or when the activity was last observed."},{"field":"event.hash","type":"keyword","description":"Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity."},{"field":"event.id","type":"keyword","description":"Unique ID to describe the event."},{"field":"event.kind","type":"keyword","description":"The kind of the event. The highest categorization field in the hierarchy."},{"field":"event.original","type":"keyword","description":"Raw text message of entire event."},{"field":"event.outcome","type":"keyword","description":"The outcome of the event. The lowest level categorization field in the hierarchy."},{"field":"event.provider","type":"keyword","description":"Source of the event."},{"field":"event.reason","type":"keyword","description":"Reason why this event happened, according to the source"},{"field":"event.reference","type":"keyword","description":"Event reference URL"},{"field":"event.risk_score","type":"float","description":"Risk score or priority of the event (e.g. security solutions). Use your system's original value here."},{"field":"event.risk_score_norm","type":"float","description":"Normalized risk score or priority of the event (0-100)."},{"field":"event.sequence","type":"long","description":"Sequence number of the event."},{"field":"event.severity","type":"long","description":"Numeric severity of the event."},{"field":"event.start","type":"date","description":"event.start contains the date when the event started or when the activity was first observed."},{"field":"event.timezone","type":"keyword","description":"Event time zone."},{"field":"event.type","type":"keyword","description":"Event type. The third categorization field in the hierarchy."},{"field":"event.url","type":"keyword","description":"Event investigation URL"},{"field":"file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"file.created","type":"date","description":"File creation time."},{"field":"file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"file.group","type":"keyword","description":"Primary group name of the file."},{"field":"file.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"file.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"file.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"file.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"file.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"file.owner","type":"keyword","description":"File owner's username."},{"field":"file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"file.path.text","type":"text","description":"Full path to the file, including the file name."},{"field":"file.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"file.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"file.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"file.pe.file_version","type":"keyword","description":"Process name."},{"field":"file.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"file.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"file.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"file.size","type":"long","description":"File size in bytes."},{"field":"file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"file.target_path.text","type":"text","description":"Target path for symlinks."},{"field":"file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"file.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"file.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"file.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"file.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"file.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"file.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"file.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"file.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"file.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"file.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"file.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"file.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"file.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"file.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"file.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"file.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"file.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"file.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"file.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"file.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"file.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"file.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"group.name","type":"keyword","description":"Name of the group."},{"field":"host.cpu.usage","type":"scaled_float","description":"Percent CPU used, between 0 and 1."},{"field":"host.disk.read.bytes","type":"long","description":"The number of bytes read by all disks."},{"field":"host.disk.write.bytes","type":"long","description":"The number of bytes written on all disks."},{"field":"host.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"host.geo.city_name","type":"keyword","description":"City name."},{"field":"host.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"host.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"host.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"host.geo.country_name","type":"keyword","description":"Country name."},{"field":"host.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"host.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"host.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"host.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"host.geo.region_name","type":"keyword","description":"Region name."},{"field":"host.geo.timezone","type":"keyword","description":"Time zone."},{"field":"host.name","type":"keyword","description":"Name of the host."},{"field":"host.network.egress.bytes","type":"long","description":"The number of bytes sent on all network interfaces."},{"field":"host.network.egress.packets","type":"long","description":"The number of packets sent on all network interfaces."},{"field":"host.network.ingress.bytes","type":"long","description":"The number of bytes received on all network interfaces."},{"field":"host.network.ingress.packets","type":"long","description":"The number of packets received on all network interfaces."},{"field":"host.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"host.os.full.text","type":"text","description":"Operating system name, including the version or code name."},{"field":"host.os.name.text","type":"text","description":"Operating system name, without the version."},{"field":"host.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"host.type","type":"keyword","description":"Type of host."},{"field":"host.uptime","type":"long","description":"Seconds the host has been up."},{"field":"host.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"host.user.email","type":"keyword","description":"User email address."},{"field":"host.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"host.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"host.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"host.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"host.user.group.name","type":"keyword","description":"Name of the group."},{"field":"host.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"host.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"host.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"host.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"host.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"http.request.body.bytes","type":"long","description":"Size in bytes of the request body."},{"field":"http.request.body.content","type":"keyword","description":"The full HTTP request body."},{"field":"http.request.body.content.text","type":"text","description":"The full HTTP request body."},{"field":"http.request.bytes","type":"long","description":"Total size in bytes of the request (body and headers)."},{"field":"http.request.id","type":"keyword","description":"HTTP request ID."},{"field":"http.request.method","type":"keyword","description":"HTTP request method."},{"field":"http.request.mime_type","type":"keyword","description":"Mime type of the body of the request."},{"field":"http.request.referrer","type":"keyword","description":"Referrer for this HTTP request."},{"field":"http.response.body.bytes","type":"long","description":"Size in bytes of the response body."},{"field":"http.response.body.content","type":"keyword","description":"The full HTTP response body."},{"field":"http.response.body.content.text","type":"text","description":"The full HTTP response body."},{"field":"http.response.bytes","type":"long","description":"Total size in bytes of the response (body and headers)."},{"field":"http.response.mime_type","type":"keyword","description":"Mime type of the body of the response."},{"field":"http.response.status_code","type":"long","description":"HTTP response status code."},{"field":"http.version","type":"keyword","description":"HTTP version."},{"field":"log.file.path","type":"keyword","description":"Full path to the log file this event came from."},{"field":"log.level","type":"keyword","description":"Log level of the log event."},{"field":"log.logger","type":"keyword","description":"Name of the logger."},{"field":"log.origin.file.line","type":"integer","description":"The line number of the file which originated the log event."},{"field":"log.origin.file.name","type":"keyword","description":"The code file which originated the log event."},{"field":"log.origin.function","type":"keyword","description":"The function which originated the log event."},{"field":"log.original","type":"keyword","description":"Deprecated original log message with light interpretation only (encoding, newlines)."},{"field":"log.syslog","type":"object","description":"Syslog metadata"},{"field":"log.syslog.facility.code","type":"long","description":"Syslog numeric facility of the event."},{"field":"log.syslog.facility.name","type":"keyword","description":"Syslog text-based facility of the event."},{"field":"log.syslog.priority","type":"long","description":"Syslog priority of the event."},{"field":"log.syslog.severity.code","type":"long","description":"Syslog numeric severity of the event."},{"field":"log.syslog.severity.name","type":"keyword","description":"Syslog text-based severity of the event."},{"field":"network.application","type":"keyword","description":"Application level protocol name."},{"field":"network.bytes","type":"long","description":"Total bytes transferred in both directions."},{"field":"network.community_id","type":"keyword","description":"A hash of source and destination IPs and ports."},{"field":"network.direction","type":"keyword","description":"Direction of the network traffic."},{"field":"network.forwarded_ip","type":"ip","description":"Host IP address when the source IP address is the proxy."},{"field":"network.iana_number","type":"keyword","description":"IANA Protocol Number."},{"field":"network.inner","type":"object","description":"Inner VLAN tag information"},{"field":"network.inner.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"network.inner.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"network.name","type":"keyword","description":"Name given by operators to sections of their network."},{"field":"network.packets","type":"long","description":"Total packets transferred in both directions."},{"field":"network.protocol","type":"keyword","description":"L7 Network protocol name."},{"field":"network.transport","type":"keyword","description":"Protocol Name corresponding to the field `iana_number`."},{"field":"network.type","type":"keyword","description":"In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc"},{"field":"network.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"network.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress","type":"object","description":"Object field for egress information"},{"field":"observer.egress.interface.alias","type":"keyword","description":"Interface alias"},{"field":"observer.egress.interface.id","type":"keyword","description":"Interface ID"},{"field":"observer.egress.interface.name","type":"keyword","description":"Interface name"},{"field":"observer.egress.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"observer.egress.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress.zone","type":"keyword","description":"Observer Egress zone"},{"field":"observer.geo.city_name","type":"keyword","description":"City name."},{"field":"observer.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"observer.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"observer.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"observer.geo.country_name","type":"keyword","description":"Country name."},{"field":"observer.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"observer.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"observer.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"observer.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"observer.geo.region_name","type":"keyword","description":"Region name."},{"field":"observer.geo.timezone","type":"keyword","description":"Time zone."},{"field":"observer.hostname","type":"keyword","description":"Hostname of the observer."},{"field":"observer.ingress","type":"object","description":"Object field for ingress information"},{"field":"observer.ingress.interface.alias","type":"keyword","description":"Interface alias"},{"field":"observer.ingress.interface.id","type":"keyword","description":"Interface ID"},{"field":"observer.ingress.interface.name","type":"keyword","description":"Interface name"},{"field":"observer.ingress.vlan.id","type":"keyword","description":"VLAN ID as reported by the observer."},{"field":"observer.ingress.vlan.name","type":"keyword","description":"Optional VLAN name as reported by the observer."},{"field":"observer.ingress.zone","type":"keyword","description":"Observer ingress zone"},{"field":"observer.ip","type":"ip","description":"IP addresses of the observer."},{"field":"observer.mac","type":"keyword","description":"MAC addresses of the observer."},{"field":"observer.name","type":"keyword","description":"Custom name of the observer."},{"field":"observer.os.family","type":"keyword","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"observer.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"observer.os.full.text","type":"text","description":"Operating system name, including the version or code name."},{"field":"observer.os.kernel","type":"keyword","description":"Operating system kernel version as a raw string."},{"field":"observer.os.name","type":"keyword","description":"Operating system name, without the version."},{"field":"observer.os.name.text","type":"text","description":"Operating system name, without the version."},{"field":"observer.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"observer.os.type","type":"keyword","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"observer.os.version","type":"keyword","description":"Operating system version as a raw string."},{"field":"observer.product","type":"keyword","description":"The product name of the observer."},{"field":"observer.serial_number","type":"keyword","description":"Observer serial number."},{"field":"observer.type","type":"keyword","description":"The type of the observer the data is coming from."},{"field":"observer.vendor","type":"keyword","description":"Vendor name of the observer."},{"field":"observer.version","type":"keyword","description":"Observer version."},{"field":"orchestrator.api_version","type":"keyword","description":"API version being used to carry out the action"},{"field":"orchestrator.cluster.name","type":"keyword","description":"Name of the cluster."},{"field":"orchestrator.cluster.url","type":"keyword","description":"URL of the API used to manage the cluster."},{"field":"orchestrator.cluster.version","type":"keyword","description":"The version of the cluster."},{"field":"orchestrator.namespace","type":"keyword","description":"Namespace in which the action is taking place."},{"field":"orchestrator.organization","type":"keyword","description":"Organization affected by the event (for multi-tenant orchestrator setups)."},{"field":"orchestrator.resource.name","type":"keyword","description":"Name of the resource being acted upon."},{"field":"orchestrator.resource.type","type":"keyword","description":"Type of resource being acted upon."},{"field":"orchestrator.type","type":"keyword","description":"Orchestrator cluster type (e.g. kubernetes, nomad or cloudfoundry)."},{"field":"organization.id","type":"keyword","description":"Unique identifier for the organization."},{"field":"organization.name","type":"keyword","description":"Organization name."},{"field":"organization.name.text","type":"text","description":"Organization name."},{"field":"package.architecture","type":"keyword","description":"Package architecture."},{"field":"package.build_version","type":"keyword","description":"Build version information"},{"field":"package.checksum","type":"keyword","description":"Checksum of the installed package for verification."},{"field":"package.description","type":"keyword","description":"Description of the package."},{"field":"package.install_scope","type":"keyword","description":"Indicating how the package was installed, e.g. user-local, global."},{"field":"package.installed","type":"date","description":"Time when package was installed."},{"field":"package.license","type":"keyword","description":"Package license"},{"field":"package.name","type":"keyword","description":"Package name"},{"field":"package.path","type":"keyword","description":"Path where the package is installed."},{"field":"package.reference","type":"keyword","description":"Package home page or reference URL"},{"field":"package.size","type":"long","description":"Package size in bytes."},{"field":"package.type","type":"keyword","description":"Package type"},{"field":"package.version","type":"keyword","description":"Package version"},{"field":"process.args","type":"keyword","description":"Array of process arguments."},{"field":"process.args_count","type":"long","description":"Length of the process.args array."},{"field":"process.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"process.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"process.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"process.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"process.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"process.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"process.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.command_line","type":"keyword","description":"Full command line that started the process."},{"field":"process.command_line.text","type":"text","description":"Full command line that started the process."},{"field":"process.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"process.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"process.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"process.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"process.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"process.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"process.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"process.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"process.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"process.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"process.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"process.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"process.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"process.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"process.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"process.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"process.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"process.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"process.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"process.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"process.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"process.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"process.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"process.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"process.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"process.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"process.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"process.entity_id","type":"keyword","description":"Unique identifier for the process."},{"field":"process.executable","type":"keyword","description":"Absolute path to the process executable."},{"field":"process.executable.text","type":"text","description":"Absolute path to the process executable."},{"field":"process.exit_code","type":"long","description":"The exit code of the process."},{"field":"process.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"process.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"process.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"process.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"process.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"process.name","type":"keyword","description":"Process name."},{"field":"process.name.text","type":"text","description":"Process name."},{"field":"process.parent.args","type":"keyword","description":"Array of process arguments."},{"field":"process.parent.args_count","type":"long","description":"Length of the process.args array."},{"field":"process.parent.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"process.parent.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"process.parent.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"process.parent.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"process.parent.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"process.parent.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"process.parent.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.parent.command_line","type":"keyword","description":"Full command line that started the process."},{"field":"process.parent.command_line.text","type":"text","description":"Full command line that started the process."},{"field":"process.parent.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"process.parent.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"process.parent.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"process.parent.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"process.parent.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"process.parent.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.parent.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"process.parent.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"process.parent.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"process.parent.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"process.parent.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.parent.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"process.parent.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"process.parent.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"process.parent.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"process.parent.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"process.parent.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"process.parent.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"process.parent.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"process.parent.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"process.parent.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"process.parent.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"process.parent.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"process.parent.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"process.parent.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"process.parent.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"process.parent.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"process.parent.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"process.parent.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"process.parent.entity_id","type":"keyword","description":"Unique identifier for the process."},{"field":"process.parent.executable","type":"keyword","description":"Absolute path to the process executable."},{"field":"process.parent.executable.text","type":"text","description":"Absolute path to the process executable."},{"field":"process.parent.exit_code","type":"long","description":"The exit code of the process."},{"field":"process.parent.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"process.parent.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"process.parent.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"process.parent.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"process.parent.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"process.parent.name","type":"keyword","description":"Process name."},{"field":"process.parent.name.text","type":"text","description":"Process name."},{"field":"process.parent.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"process.parent.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"process.parent.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"process.parent.pe.file_version","type":"keyword","description":"Process name."},{"field":"process.parent.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"process.parent.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"process.parent.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"process.parent.pgid","type":"long","description":"Identifier of the group of processes the process belongs to."},{"field":"process.parent.pid","type":"long","description":"Process id."},{"field":"process.parent.ppid","type":"long","description":"Parent process' pid."},{"field":"process.parent.start","type":"date","description":"The time the process started."},{"field":"process.parent.thread.id","type":"long","description":"Thread ID."},{"field":"process.parent.thread.name","type":"keyword","description":"Thread name."},{"field":"process.parent.title","type":"keyword","description":"Process title."},{"field":"process.parent.title.text","type":"text","description":"Process title."},{"field":"process.parent.uptime","type":"long","description":"Seconds the process has been up."},{"field":"process.parent.working_directory","type":"keyword","description":"The working directory of the process."},{"field":"process.parent.working_directory.text","type":"text","description":"The working directory of the process."},{"field":"process.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"process.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"process.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"process.pe.file_version","type":"keyword","description":"Process name."},{"field":"process.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"process.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"process.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"process.pgid","type":"long","description":"Identifier of the group of processes the process belongs to."},{"field":"process.pid","type":"long","description":"Process id."},{"field":"process.ppid","type":"long","description":"Parent process' pid."},{"field":"process.start","type":"date","description":"The time the process started."},{"field":"process.thread.id","type":"long","description":"Thread ID."},{"field":"process.thread.name","type":"keyword","description":"Thread name."},{"field":"process.title","type":"keyword","description":"Process title."},{"field":"process.title.text","type":"text","description":"Process title."},{"field":"process.uptime","type":"long","description":"Seconds the process has been up."},{"field":"process.working_directory","type":"keyword","description":"The working directory of the process."},{"field":"process.working_directory.text","type":"text","description":"The working directory of the process."},{"field":"registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"registry.data.strings","type":"keyword","description":"List of strings representing what was written to the registry."},{"field":"registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"registry.value","type":"keyword","description":"Name of the value written."},{"field":"related.hash","type":"keyword","description":"All the hashes seen on your event."},{"field":"related.hosts","type":"keyword","description":"All the host identifiers seen on your event."},{"field":"related.ip","type":"ip","description":"All of the IPs seen on your event."},{"field":"related.user","type":"keyword","description":"All the user names or other user identifiers seen on the event."},{"field":"rule.author","type":"keyword","description":"Rule author"},{"field":"rule.category","type":"keyword","description":"Rule category"},{"field":"rule.description","type":"keyword","description":"Rule description"},{"field":"rule.id","type":"keyword","description":"Rule ID"},{"field":"rule.license","type":"keyword","description":"Rule license"},{"field":"rule.name","type":"keyword","description":"Rule name"},{"field":"rule.reference","type":"keyword","description":"Rule reference URL"},{"field":"rule.ruleset","type":"keyword","description":"Rule ruleset"},{"field":"rule.uuid","type":"keyword","description":"Rule UUID"},{"field":"rule.version","type":"keyword","description":"Rule version"},{"field":"server.address","type":"keyword","description":"Server network address."},{"field":"server.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"server.as.organization.name","type":"keyword","description":"Organization name."},{"field":"server.as.organization.name.text","type":"text","description":"Organization name."},{"field":"server.bytes","type":"long","description":"Bytes sent from the server to the client."},{"field":"server.domain","type":"keyword","description":"Server domain."},{"field":"server.geo.city_name","type":"keyword","description":"City name."},{"field":"server.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"server.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"server.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"server.geo.country_name","type":"keyword","description":"Country name."},{"field":"server.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"server.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"server.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"server.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"server.geo.region_name","type":"keyword","description":"Region name."},{"field":"server.geo.timezone","type":"keyword","description":"Time zone."},{"field":"server.ip","type":"ip","description":"IP address of the server."},{"field":"server.mac","type":"keyword","description":"MAC address of the server."},{"field":"server.nat.ip","type":"ip","description":"Server NAT ip"},{"field":"server.nat.port","type":"long","description":"Server NAT port"},{"field":"server.packets","type":"long","description":"Packets sent from the server to the client."},{"field":"server.port","type":"long","description":"Port of the server."},{"field":"server.registered_domain","type":"keyword","description":"The highest registered server domain, stripped of the subdomain."},{"field":"server.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"server.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"server.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"server.user.email","type":"keyword","description":"User email address."},{"field":"server.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"server.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"server.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"server.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"server.user.group.name","type":"keyword","description":"Name of the group."},{"field":"server.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"server.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"server.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"server.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"server.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"service.ephemeral_id","type":"keyword","description":"Ephemeral identifier of this service."},{"field":"service.id","type":"keyword","description":"Unique identifier of the running service."},{"field":"service.name","type":"keyword","description":"Name of the service."},{"field":"service.node.name","type":"keyword","description":"Name of the service node."},{"field":"service.state","type":"keyword","description":"Current state of the service."},{"field":"service.type","type":"keyword","description":"The type of the service."},{"field":"service.version","type":"keyword","description":"Version of the service."},{"field":"source.address","type":"keyword","description":"Source network address."},{"field":"source.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"source.as.organization.name","type":"keyword","description":"Organization name."},{"field":"source.as.organization.name.text","type":"text","description":"Organization name."},{"field":"source.bytes","type":"long","description":"Bytes sent from the source to the destination."},{"field":"source.domain","type":"keyword","description":"Source domain."},{"field":"source.geo.city_name","type":"keyword","description":"City name."},{"field":"source.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"source.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"source.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"source.geo.country_name","type":"keyword","description":"Country name."},{"field":"source.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"source.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"source.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"source.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"source.geo.region_name","type":"keyword","description":"Region name."},{"field":"source.geo.timezone","type":"keyword","description":"Time zone."},{"field":"source.ip","type":"ip","description":"IP address of the source."},{"field":"source.mac","type":"keyword","description":"MAC address of the source."},{"field":"source.nat.ip","type":"ip","description":"Source NAT ip"},{"field":"source.nat.port","type":"long","description":"Source NAT port"},{"field":"source.packets","type":"long","description":"Packets sent from the source to the destination."},{"field":"source.port","type":"long","description":"Port of the source."},{"field":"source.registered_domain","type":"keyword","description":"The highest registered source domain, stripped of the subdomain."},{"field":"source.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"source.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"source.user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"source.user.email","type":"keyword","description":"User email address."},{"field":"source.user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"source.user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"source.user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"source.user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"source.user.group.name","type":"keyword","description":"Name of the group."},{"field":"source.user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"source.user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"source.user.name","type":"keyword","description":"Short name or login of the user."},{"field":"source.user.name.text","type":"text","description":"Short name or login of the user."},{"field":"source.user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"span.id","type":"keyword","description":"Unique identifier of the span within the scope of its trace."},{"field":"threat.enrichments","type":"nested","description":"List of objects containing indicators enriching the event."},{"field":"threat.enrichments.indicator","type":"object","description":"Object containing indicators enriching the event."},{"field":"threat.enrichments.indicator.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"threat.enrichments.indicator.as.organization.name","type":"keyword","description":"Organization name."},{"field":"threat.enrichments.indicator.as.organization.name.text","type":"text","description":"Organization name."},{"field":"threat.enrichments.indicator.confidence","type":"keyword","description":"Indicator confidence rating"},{"field":"threat.enrichments.indicator.description","type":"keyword","description":"Indicator description"},{"field":"threat.enrichments.indicator.email.address","type":"keyword","description":"Indicator email address"},{"field":"threat.enrichments.indicator.file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"threat.enrichments.indicator.file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"threat.enrichments.indicator.file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"threat.enrichments.indicator.file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"threat.enrichments.indicator.file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"threat.enrichments.indicator.file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"threat.enrichments.indicator.file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.enrichments.indicator.file.created","type":"date","description":"File creation time."},{"field":"threat.enrichments.indicator.file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"threat.enrichments.indicator.file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"threat.enrichments.indicator.file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"threat.enrichments.indicator.file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"threat.enrichments.indicator.file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"threat.enrichments.indicator.file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"threat.enrichments.indicator.file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"threat.enrichments.indicator.file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.enrichments.indicator.file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"threat.enrichments.indicator.file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.enrichments.indicator.file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"threat.enrichments.indicator.file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"threat.enrichments.indicator.file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"threat.enrichments.indicator.file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"threat.enrichments.indicator.file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"threat.enrichments.indicator.file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"threat.enrichments.indicator.file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"threat.enrichments.indicator.file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"threat.enrichments.indicator.file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"threat.enrichments.indicator.file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"threat.enrichments.indicator.file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"threat.enrichments.indicator.file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"threat.enrichments.indicator.file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"threat.enrichments.indicator.file.group","type":"keyword","description":"Primary group name of the file."},{"field":"threat.enrichments.indicator.file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"threat.enrichments.indicator.file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.enrichments.indicator.file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"threat.enrichments.indicator.file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"threat.enrichments.indicator.file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"threat.enrichments.indicator.file.owner","type":"keyword","description":"File owner's username."},{"field":"threat.enrichments.indicator.file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.path.text","type":"text","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.size","type":"long","description":"File size in bytes."},{"field":"threat.enrichments.indicator.file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.target_path.text","type":"text","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"threat.enrichments.indicator.file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.enrichments.indicator.first_seen","type":"date","description":"Date/time indicator was first reported."},{"field":"threat.enrichments.indicator.geo.city_name","type":"keyword","description":"City name."},{"field":"threat.enrichments.indicator.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"threat.enrichments.indicator.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"threat.enrichments.indicator.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"threat.enrichments.indicator.geo.country_name","type":"keyword","description":"Country name."},{"field":"threat.enrichments.indicator.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"threat.enrichments.indicator.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"threat.enrichments.indicator.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"threat.enrichments.indicator.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"threat.enrichments.indicator.geo.region_name","type":"keyword","description":"Region name."},{"field":"threat.enrichments.indicator.geo.timezone","type":"keyword","description":"Time zone."},{"field":"threat.enrichments.indicator.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"threat.enrichments.indicator.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"threat.enrichments.indicator.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"threat.enrichments.indicator.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"threat.enrichments.indicator.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"threat.enrichments.indicator.ip","type":"ip","description":"Indicator IP address"},{"field":"threat.enrichments.indicator.last_seen","type":"date","description":"Date/time indicator was last reported."},{"field":"threat.enrichments.indicator.marking.tlp","type":"keyword","description":"Indicator TLP marking"},{"field":"threat.enrichments.indicator.modified_at","type":"date","description":"Date/time indicator was last updated."},{"field":"threat.enrichments.indicator.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"threat.enrichments.indicator.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.pe.file_version","type":"keyword","description":"Process name."},{"field":"threat.enrichments.indicator.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"threat.enrichments.indicator.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.port","type":"long","description":"Indicator port"},{"field":"threat.enrichments.indicator.provider","type":"keyword","description":"Indicator provider"},{"field":"threat.enrichments.indicator.reference","type":"keyword","description":"Indicator reference URL"},{"field":"threat.enrichments.indicator.registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"threat.enrichments.indicator.registry.data.strings","type":"keyword","description":"List of strings representing what was written to the registry."},{"field":"threat.enrichments.indicator.registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"threat.enrichments.indicator.registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"threat.enrichments.indicator.registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"threat.enrichments.indicator.registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"threat.enrichments.indicator.registry.value","type":"keyword","description":"Name of the value written."},{"field":"threat.enrichments.indicator.scanner_stats","type":"long","description":"Scanner statistics"},{"field":"threat.enrichments.indicator.sightings","type":"long","description":"Number of times indicator observed"},{"field":"threat.enrichments.indicator.type","type":"keyword","description":"Type of indicator"},{"field":"threat.enrichments.indicator.url.domain","type":"keyword","description":"Domain of the url."},{"field":"threat.enrichments.indicator.url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.enrichments.indicator.url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"threat.enrichments.indicator.url.full","type":"keyword","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.full.text","type":"text","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.original","type":"keyword","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.original.text","type":"text","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.password","type":"keyword","description":"Password of the request."},{"field":"threat.enrichments.indicator.url.path","type":"keyword","description":"Path of the request, such as \"/search\"."},{"field":"threat.enrichments.indicator.url.port","type":"long","description":"Port of the request, such as 443."},{"field":"threat.enrichments.indicator.url.query","type":"keyword","description":"Query string of the request."},{"field":"threat.enrichments.indicator.url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.enrichments.indicator.url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"threat.enrichments.indicator.url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"threat.enrichments.indicator.url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.enrichments.indicator.url.username","type":"keyword","description":"Username of the request."},{"field":"threat.enrichments.indicator.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.enrichments.indicator.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.enrichments.indicator.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.enrichments.matched.atomic","type":"keyword","description":"Matched indicator value"},{"field":"threat.enrichments.matched.field","type":"keyword","description":"Matched indicator field"},{"field":"threat.enrichments.matched.id","type":"keyword","description":"Matched indicator identifier"},{"field":"threat.enrichments.matched.index","type":"keyword","description":"Matched indicator index"},{"field":"threat.enrichments.matched.type","type":"keyword","description":"Type of indicator match"},{"field":"threat.framework","type":"keyword","description":"Threat classification framework."},{"field":"threat.group.alias","type":"keyword","description":"Alias of the group."},{"field":"threat.group.id","type":"keyword","description":"ID of the group."},{"field":"threat.group.name","type":"keyword","description":"Name of the group."},{"field":"threat.group.reference","type":"keyword","description":"Reference URL of the group."},{"field":"threat.indicator.as.number","type":"long","description":"Unique number allocated to the autonomous system."},{"field":"threat.indicator.as.organization.name","type":"keyword","description":"Organization name."},{"field":"threat.indicator.as.organization.name.text","type":"text","description":"Organization name."},{"field":"threat.indicator.confidence","type":"keyword","description":"Indicator confidence rating"},{"field":"threat.indicator.description","type":"keyword","description":"Indicator description"},{"field":"threat.indicator.email.address","type":"keyword","description":"Indicator email address"},{"field":"threat.indicator.file.accessed","type":"date","description":"Last time the file was accessed."},{"field":"threat.indicator.file.attributes","type":"keyword","description":"Array of file attributes."},{"field":"threat.indicator.file.code_signature.exists","type":"boolean","description":"Boolean to capture if a signature is present."},{"field":"threat.indicator.file.code_signature.signing_id","type":"keyword","description":"The identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.status","type":"keyword","description":"Additional information about the certificate status."},{"field":"threat.indicator.file.code_signature.subject_name","type":"keyword","description":"Subject name of the code signer"},{"field":"threat.indicator.file.code_signature.team_id","type":"keyword","description":"The team identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.trusted","type":"boolean","description":"Stores the trust status of the certificate chain."},{"field":"threat.indicator.file.code_signature.valid","type":"boolean","description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.indicator.file.created","type":"date","description":"File creation time."},{"field":"threat.indicator.file.ctime","type":"date","description":"Last time the file attributes or metadata changed."},{"field":"threat.indicator.file.device","type":"keyword","description":"Device that is the source of the file."},{"field":"threat.indicator.file.directory","type":"keyword","description":"Directory where the file is located."},{"field":"threat.indicator.file.drive_letter","type":"keyword","description":"Drive letter where the file is located."},{"field":"threat.indicator.file.elf.architecture","type":"keyword","description":"Machine architecture of the ELF file."},{"field":"threat.indicator.file.elf.byte_order","type":"keyword","description":"Byte sequence of ELF file."},{"field":"threat.indicator.file.elf.cpu_type","type":"keyword","description":"CPU type of the ELF file."},{"field":"threat.indicator.file.elf.creation_date","type":"date","description":"Build or compile date."},{"field":"threat.indicator.file.elf.exports","type":"flattened","description":"List of exported element names and types."},{"field":"threat.indicator.file.elf.header.abi_version","type":"keyword","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.indicator.file.elf.header.class","type":"keyword","description":"Header class of the ELF file."},{"field":"threat.indicator.file.elf.header.data","type":"keyword","description":"Data table of the ELF header."},{"field":"threat.indicator.file.elf.header.entrypoint","type":"long","description":"Header entrypoint of the ELF file."},{"field":"threat.indicator.file.elf.header.object_version","type":"keyword","description":"0x1\" for original ELF files."},{"field":"threat.indicator.file.elf.header.os_abi","type":"keyword","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.indicator.file.elf.header.type","type":"keyword","description":"Header type of the ELF file."},{"field":"threat.indicator.file.elf.header.version","type":"keyword","description":"Version of the ELF header."},{"field":"threat.indicator.file.elf.imports","type":"flattened","description":"List of imported element names and types."},{"field":"threat.indicator.file.elf.sections","type":"nested","description":"Section information of the ELF file."},{"field":"threat.indicator.file.elf.sections.chi2","type":"long","description":"Chi-square probability distribution of the section."},{"field":"threat.indicator.file.elf.sections.entropy","type":"long","description":"Shannon entropy calculation from the section."},{"field":"threat.indicator.file.elf.sections.flags","type":"keyword","description":"ELF Section List flags."},{"field":"threat.indicator.file.elf.sections.name","type":"keyword","description":"ELF Section List name."},{"field":"threat.indicator.file.elf.sections.physical_offset","type":"keyword","description":"ELF Section List offset."},{"field":"threat.indicator.file.elf.sections.physical_size","type":"long","description":"ELF Section List physical size."},{"field":"threat.indicator.file.elf.sections.type","type":"keyword","description":"ELF Section List type."},{"field":"threat.indicator.file.elf.sections.virtual_address","type":"long","description":"ELF Section List virtual address."},{"field":"threat.indicator.file.elf.sections.virtual_size","type":"long","description":"ELF Section List virtual size."},{"field":"threat.indicator.file.elf.segments","type":"nested","description":"ELF object segment list."},{"field":"threat.indicator.file.elf.segments.sections","type":"keyword","description":"ELF object segment sections."},{"field":"threat.indicator.file.elf.segments.type","type":"keyword","description":"ELF object segment type."},{"field":"threat.indicator.file.elf.shared_libraries","type":"keyword","description":"List of shared libraries used by this ELF object."},{"field":"threat.indicator.file.elf.telfhash","type":"keyword","description":"telfhash hash for ELF file."},{"field":"threat.indicator.file.extension","type":"keyword","description":"File extension, excluding the leading dot."},{"field":"threat.indicator.file.gid","type":"keyword","description":"Primary group ID (GID) of the file."},{"field":"threat.indicator.file.group","type":"keyword","description":"Primary group name of the file."},{"field":"threat.indicator.file.inode","type":"keyword","description":"Inode representing the file in the filesystem."},{"field":"threat.indicator.file.mime_type","type":"keyword","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.indicator.file.mode","type":"keyword","description":"Mode of the file in octal representation."},{"field":"threat.indicator.file.mtime","type":"date","description":"Last time the file content was modified."},{"field":"threat.indicator.file.name","type":"keyword","description":"Name of the file including the extension, without the directory."},{"field":"threat.indicator.file.owner","type":"keyword","description":"File owner's username."},{"field":"threat.indicator.file.path","type":"keyword","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.path.text","type":"text","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.size","type":"long","description":"File size in bytes."},{"field":"threat.indicator.file.target_path","type":"keyword","description":"Target path for symlinks."},{"field":"threat.indicator.file.target_path.text","type":"text","description":"Target path for symlinks."},{"field":"threat.indicator.file.type","type":"keyword","description":"File type (file, dir, or symlink)."},{"field":"threat.indicator.file.uid","type":"keyword","description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.indicator.first_seen","type":"date","description":"Date/time indicator was first reported."},{"field":"threat.indicator.geo.city_name","type":"keyword","description":"City name."},{"field":"threat.indicator.geo.continent_code","type":"keyword","description":"Continent code."},{"field":"threat.indicator.geo.continent_name","type":"keyword","description":"Name of the continent."},{"field":"threat.indicator.geo.country_iso_code","type":"keyword","description":"Country ISO code."},{"field":"threat.indicator.geo.country_name","type":"keyword","description":"Country name."},{"field":"threat.indicator.geo.location","type":"geo_point","description":"Longitude and latitude."},{"field":"threat.indicator.geo.name","type":"keyword","description":"User-defined description of a location."},{"field":"threat.indicator.geo.postal_code","type":"keyword","description":"Postal code."},{"field":"threat.indicator.geo.region_iso_code","type":"keyword","description":"Region ISO code."},{"field":"threat.indicator.geo.region_name","type":"keyword","description":"Region name."},{"field":"threat.indicator.geo.timezone","type":"keyword","description":"Time zone."},{"field":"threat.indicator.hash.md5","type":"keyword","description":"MD5 hash."},{"field":"threat.indicator.hash.sha1","type":"keyword","description":"SHA1 hash."},{"field":"threat.indicator.hash.sha256","type":"keyword","description":"SHA256 hash."},{"field":"threat.indicator.hash.sha512","type":"keyword","description":"SHA512 hash."},{"field":"threat.indicator.hash.ssdeep","type":"keyword","description":"SSDEEP hash."},{"field":"threat.indicator.ip","type":"ip","description":"Indicator IP address"},{"field":"threat.indicator.last_seen","type":"date","description":"Date/time indicator was last reported."},{"field":"threat.indicator.marking.tlp","type":"keyword","description":"Indicator TLP marking"},{"field":"threat.indicator.modified_at","type":"date","description":"Date/time indicator was last updated."},{"field":"threat.indicator.pe.architecture","type":"keyword","description":"CPU architecture target for the file."},{"field":"threat.indicator.pe.company","type":"keyword","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.indicator.pe.description","type":"keyword","description":"Internal description of the file, provided at compile-time."},{"field":"threat.indicator.pe.file_version","type":"keyword","description":"Process name."},{"field":"threat.indicator.pe.imphash","type":"keyword","description":"A hash of the imports in a PE file."},{"field":"threat.indicator.pe.original_file_name","type":"keyword","description":"Internal name of the file, provided at compile-time."},{"field":"threat.indicator.pe.product","type":"keyword","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.indicator.port","type":"long","description":"Indicator port"},{"field":"threat.indicator.provider","type":"keyword","description":"Indicator provider"},{"field":"threat.indicator.reference","type":"keyword","description":"Indicator reference URL"},{"field":"threat.indicator.registry.data.bytes","type":"keyword","description":"Original bytes written with base64 encoding."},{"field":"threat.indicator.registry.data.strings","type":"keyword","description":"List of strings representing what was written to the registry."},{"field":"threat.indicator.registry.data.type","type":"keyword","description":"Standard registry type for encoding contents"},{"field":"threat.indicator.registry.hive","type":"keyword","description":"Abbreviated name for the hive."},{"field":"threat.indicator.registry.key","type":"keyword","description":"Hive-relative path of keys."},{"field":"threat.indicator.registry.path","type":"keyword","description":"Full path, including hive, key and value"},{"field":"threat.indicator.registry.value","type":"keyword","description":"Name of the value written."},{"field":"threat.indicator.scanner_stats","type":"long","description":"Scanner statistics"},{"field":"threat.indicator.sightings","type":"long","description":"Number of times indicator observed"},{"field":"threat.indicator.type","type":"keyword","description":"Type of indicator"},{"field":"threat.indicator.url.domain","type":"keyword","description":"Domain of the url."},{"field":"threat.indicator.url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.indicator.url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"threat.indicator.url.full","type":"keyword","description":"Full unparsed URL."},{"field":"threat.indicator.url.full.text","type":"text","description":"Full unparsed URL."},{"field":"threat.indicator.url.original","type":"keyword","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.original.text","type":"text","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.password","type":"keyword","description":"Password of the request."},{"field":"threat.indicator.url.path","type":"keyword","description":"Path of the request, such as \"/search\"."},{"field":"threat.indicator.url.port","type":"long","description":"Port of the request, such as 443."},{"field":"threat.indicator.url.query","type":"keyword","description":"Query string of the request."},{"field":"threat.indicator.url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.indicator.url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"threat.indicator.url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"threat.indicator.url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.indicator.url.username","type":"keyword","description":"Username of the request."},{"field":"threat.indicator.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"threat.indicator.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"threat.indicator.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"threat.indicator.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"threat.indicator.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"threat.indicator.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"threat.indicator.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"threat.software.id","type":"keyword","description":"ID of the software"},{"field":"threat.software.name","type":"keyword","description":"Name of the software."},{"field":"threat.software.platforms","type":"keyword","description":"Platforms of the software."},{"field":"threat.software.reference","type":"keyword","description":"Software reference URL."},{"field":"threat.software.type","type":"keyword","description":"Software type."},{"field":"threat.tactic.id","type":"keyword","description":"Threat tactic id."},{"field":"threat.tactic.name","type":"keyword","description":"Threat tactic."},{"field":"threat.tactic.reference","type":"keyword","description":"Threat tactic URL reference."},{"field":"threat.technique.id","type":"keyword","description":"Threat technique id."},{"field":"threat.technique.name","type":"keyword","description":"Threat technique name."},{"field":"threat.technique.name.text","type":"text","description":"Threat technique name."},{"field":"threat.technique.reference","type":"keyword","description":"Threat technique URL reference."},{"field":"threat.technique.subtechnique.id","type":"keyword","description":"Threat subtechnique id."},{"field":"threat.technique.subtechnique.name","type":"keyword","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.name.text","type":"text","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.reference","type":"keyword","description":"Threat subtechnique URL reference."},{"field":"tls.cipher","type":"keyword","description":"String indicating the cipher used during the current connection."},{"field":"tls.client.certificate","type":"keyword","description":"PEM-encoded stand-alone certificate offered by the client."},{"field":"tls.client.certificate_chain","type":"keyword","description":"Array of PEM-encoded certificates that make up the certificate chain offered by the client."},{"field":"tls.client.hash.md5","type":"keyword","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha1","type":"keyword","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha256","type":"keyword","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.issuer","type":"keyword","description":"Distinguished name of subject of the issuer of the x.509 certificate presented by the client."},{"field":"tls.client.ja3","type":"keyword","description":"A hash that identifies clients based on how they perform an SSL/TLS handshake."},{"field":"tls.client.not_after","type":"date","description":"Date/Time indicating when client certificate is no longer considered valid."},{"field":"tls.client.not_before","type":"date","description":"Date/Time indicating when client certificate is first considered valid."},{"field":"tls.client.server_name","type":"keyword","description":"Hostname the client is trying to connect to. Also called the SNI."},{"field":"tls.client.subject","type":"keyword","description":"Distinguished name of subject of the x.509 certificate presented by the client."},{"field":"tls.client.supported_ciphers","type":"keyword","description":"Array of ciphers offered by the client during the client hello."},{"field":"tls.client.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"tls.client.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"tls.client.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.client.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.client.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.client.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.client.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"tls.client.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"tls.client.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.client.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.client.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"tls.client.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"tls.client.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"tls.client.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"tls.client.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"tls.client.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.client.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.client.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"tls.client.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"tls.client.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"tls.curve","type":"keyword","description":"String indicating the curve used for the given cipher, when applicable."},{"field":"tls.established","type":"boolean","description":"Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel."},{"field":"tls.next_protocol","type":"keyword","description":"String indicating the protocol being tunneled."},{"field":"tls.resumed","type":"boolean","description":"Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation."},{"field":"tls.server.certificate","type":"keyword","description":"PEM-encoded stand-alone certificate offered by the server."},{"field":"tls.server.certificate_chain","type":"keyword","description":"Array of PEM-encoded certificates that make up the certificate chain offered by the server."},{"field":"tls.server.hash.md5","type":"keyword","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha1","type":"keyword","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha256","type":"keyword","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.issuer","type":"keyword","description":"Subject of the issuer of the x.509 certificate presented by the server."},{"field":"tls.server.ja3s","type":"keyword","description":"A hash that identifies servers based on how they perform an SSL/TLS handshake."},{"field":"tls.server.not_after","type":"date","description":"Timestamp indicating when server certificate is no longer considered valid."},{"field":"tls.server.not_before","type":"date","description":"Timestamp indicating when server certificate is first considered valid."},{"field":"tls.server.subject","type":"keyword","description":"Subject of the x.509 certificate presented by the server."},{"field":"tls.server.x509.alternative_names","type":"keyword","description":"List of subject alternative names (SAN)."},{"field":"tls.server.x509.issuer.common_name","type":"keyword","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.country","type":"keyword","description":"List of country (C) codes"},{"field":"tls.server.x509.issuer.distinguished_name","type":"keyword","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.server.x509.issuer.organization","type":"keyword","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.server.x509.issuer.organizational_unit","type":"keyword","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.server.x509.issuer.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.not_after","type":"date","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.server.x509.not_before","type":"date","description":"Time at which the certificate is first considered valid."},{"field":"tls.server.x509.public_key_algorithm","type":"keyword","description":"Algorithm used to generate the public key."},{"field":"tls.server.x509.public_key_curve","type":"keyword","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.server.x509.public_key_exponent","type":"long","description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.server.x509.public_key_size","type":"long","description":"The size of the public key space in bits."},{"field":"tls.server.x509.serial_number","type":"keyword","description":"Unique serial number issued by the certificate authority."},{"field":"tls.server.x509.signature_algorithm","type":"keyword","description":"Identifier for certificate signature algorithm."},{"field":"tls.server.x509.subject.common_name","type":"keyword","description":"List of common names (CN) of subject."},{"field":"tls.server.x509.subject.country","type":"keyword","description":"List of country (C) code"},{"field":"tls.server.x509.subject.distinguished_name","type":"keyword","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.server.x509.subject.locality","type":"keyword","description":"List of locality names (L)"},{"field":"tls.server.x509.subject.organization","type":"keyword","description":"List of organizations (O) of subject."},{"field":"tls.server.x509.subject.organizational_unit","type":"keyword","description":"List of organizational units (OU) of subject."},{"field":"tls.server.x509.subject.state_or_province","type":"keyword","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.version_number","type":"keyword","description":"Version of x509 format."},{"field":"tls.version","type":"keyword","description":"Numeric part of the version parsed from the original string."},{"field":"tls.version_protocol","type":"keyword","description":"Normalized lowercase protocol name parsed from original string."},{"field":"trace.id","type":"keyword","description":"Unique identifier of the trace."},{"field":"transaction.id","type":"keyword","description":"Unique identifier of the transaction within the scope of its trace."},{"field":"url.domain","type":"keyword","description":"Domain of the url."},{"field":"url.extension","type":"keyword","description":"File extension from the request url, excluding the leading dot."},{"field":"url.fragment","type":"keyword","description":"Portion of the url after the `#`."},{"field":"url.full","type":"keyword","description":"Full unparsed URL."},{"field":"url.full.text","type":"text","description":"Full unparsed URL."},{"field":"url.original","type":"keyword","description":"Unmodified original url as seen in the event source."},{"field":"url.original.text","type":"text","description":"Unmodified original url as seen in the event source."},{"field":"url.password","type":"keyword","description":"Password of the request."},{"field":"url.path","type":"keyword","description":"Path of the request, such as \"/search\"."},{"field":"url.port","type":"long","description":"Port of the request, such as 443."},{"field":"url.query","type":"keyword","description":"Query string of the request."},{"field":"url.registered_domain","type":"keyword","description":"The highest registered url domain, stripped of the subdomain."},{"field":"url.scheme","type":"keyword","description":"Scheme of the url."},{"field":"url.subdomain","type":"keyword","description":"The subdomain of the domain."},{"field":"url.top_level_domain","type":"keyword","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"url.username","type":"keyword","description":"Username of the request."},{"field":"user.changes.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.changes.email","type":"keyword","description":"User email address."},{"field":"user.changes.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.changes.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.changes.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.changes.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.changes.group.name","type":"keyword","description":"Name of the group."},{"field":"user.changes.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.changes.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.changes.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.changes.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.changes.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.effective.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.effective.email","type":"keyword","description":"User email address."},{"field":"user.effective.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.effective.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.effective.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.effective.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.effective.group.name","type":"keyword","description":"Name of the group."},{"field":"user.effective.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.effective.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.effective.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.effective.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.effective.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.email","type":"keyword","description":"User email address."},{"field":"user.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.group.name","type":"keyword","description":"Name of the group."},{"field":"user.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user.target.domain","type":"keyword","description":"Name of the directory the user is a member of."},{"field":"user.target.email","type":"keyword","description":"User email address."},{"field":"user.target.full_name","type":"keyword","description":"User's full name, if available."},{"field":"user.target.full_name.text","type":"text","description":"User's full name, if available."},{"field":"user.target.group.domain","type":"keyword","description":"Name of the directory the group is a member of."},{"field":"user.target.group.id","type":"keyword","description":"Unique identifier for the group on the system/platform."},{"field":"user.target.group.name","type":"keyword","description":"Name of the group."},{"field":"user.target.hash","type":"keyword","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.target.id","type":"keyword","description":"Unique identifier of the user."},{"field":"user.target.name","type":"keyword","description":"Short name or login of the user."},{"field":"user.target.name.text","type":"text","description":"Short name or login of the user."},{"field":"user.target.roles","type":"keyword","description":"Array of user roles at the time of the event."},{"field":"user_agent.device.name","type":"keyword","description":"Name of the device."},{"field":"user_agent.name","type":"keyword","description":"Name of the user agent."},{"field":"user_agent.original","type":"keyword","description":"Unparsed user_agent string."},{"field":"user_agent.original.text","type":"text","description":"Unparsed user_agent string."},{"field":"user_agent.os.family","type":"keyword","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"user_agent.os.full","type":"keyword","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.full.text","type":"text","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.kernel","type":"keyword","description":"Operating system kernel version as a raw string."},{"field":"user_agent.os.name","type":"keyword","description":"Operating system name, without the version."},{"field":"user_agent.os.name.text","type":"text","description":"Operating system name, without the version."},{"field":"user_agent.os.platform","type":"keyword","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"user_agent.os.type","type":"keyword","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"user_agent.os.version","type":"keyword","description":"Operating system version as a raw string."},{"field":"user_agent.version","type":"keyword","description":"Version of the user agent."},{"field":"vulnerability.category","type":"keyword","description":"Category of a vulnerability."},{"field":"vulnerability.classification","type":"keyword","description":"Classification of the vulnerability."},{"field":"vulnerability.description","type":"keyword","description":"Description of the vulnerability."},{"field":"vulnerability.description.text","type":"text","description":"Description of the vulnerability."},{"field":"vulnerability.enumeration","type":"keyword","description":"Identifier of the vulnerability."},{"field":"vulnerability.id","type":"keyword","description":"ID of the vulnerability."},{"field":"vulnerability.reference","type":"keyword","description":"Reference of the vulnerability."},{"field":"vulnerability.report_id","type":"keyword","description":"Scan identification number."},{"field":"vulnerability.scanner.vendor","type":"keyword","description":"Name of the scanner vendor."},{"field":"vulnerability.score.base","type":"float","description":"Vulnerability Base score."},{"field":"vulnerability.score.environmental","type":"float","description":"Vulnerability Environmental score."},{"field":"vulnerability.score.temporal","type":"float","description":"Vulnerability Temporal score."},{"field":"vulnerability.score.version","type":"keyword","description":"CVSS version."},{"field":"vulnerability.severity","type":"keyword","description":"Severity of the vulnerability."}] \ No newline at end of file diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx index df61b116a5647d..44407139ab4926 100644 --- a/x-pack/plugins/osquery/public/components/app.tsx +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -26,12 +26,6 @@ const OsqueryAppComponent = () => { - {/* - - */} ( - + diff --git a/x-pack/plugins/osquery/public/live_queries/index.tsx b/x-pack/plugins/osquery/public/live_queries/index.tsx index 9feb05bc05207c..8d87d59828ee37 100644 --- a/x-pack/plugins/osquery/public/live_queries/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/index.tsx @@ -7,6 +7,7 @@ import { EuiCode, EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui'; import React, { useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { LiveQueryForm } from './form'; import { useActionResultsPrivileges } from '../action_results/use_action_privileges'; @@ -52,13 +53,26 @@ const LiveQueryComponent: React.FC = ({ return ( } - title={

Permission denied

} + title={ +

+ +

+ } titleSize="xs" body={

- To view query results, ask your administrator to update your user role to have index{' '} - read privileges on the{' '} - logs-{OSQUERY_INTEGRATION_NAME}.result* index. + read, + logs: logs-{OSQUERY_INTEGRATION_NAME}.result*, + }} + />

} /> diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx index 401966460a7e70..abed9fc1bce487 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx @@ -125,16 +125,33 @@ const EditSavedQueryPageComponent = () => { )} {isDeleteModalVisible ? ( + } onCancel={handleCloseDeleteConfirmationModal} onConfirm={handleDeleteConfirmClick} - cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + cancelButtonText={ + + } + confirmButtonText={ + + } buttonColor="danger" defaultFocusedButton="confirm" > -

You’re about to delete this query.

-

Are you sure you want to do this?

+
) : null} diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx index 5bc4bf32bcefdc..685960ecd202eb 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx @@ -14,12 +14,11 @@ import { EuiButton, EuiDescribedFormGroup, EuiSpacer, - EuiAccordion, EuiBottomBar, EuiHorizontalRule, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; -import { useMutation } from 'react-query'; +import { useMutation, useQueryClient } from 'react-query'; import { produce } from 'immer'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -64,6 +63,7 @@ const ScheduledQueryGroupFormComponent: React.FC = packageInfo, editMode = false, }) => { + const queryClient = useQueryClient(); const { application: { navigateToApp }, http, @@ -112,6 +112,10 @@ const ScheduledQueryGroupFormComponent: React.FC = return; } + queryClient.invalidateQueries([ + 'scheduledQueryGroup', + { scheduledQueryGroupId: data.item.id }, + ]); setErrorToast(); navigateToApp(PLUGIN_ID, { path: `scheduled_query_groups/${data.item.id}` }); toasts.addSuccess( @@ -328,16 +332,7 @@ const ScheduledQueryGroupFormComponent: React.FC = agentPoliciesById={agentPoliciesById} /> - - - - + diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/pack_uploader.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/pack_uploader.tsx index 83e64ed6e6f3d3..6c3d90473c8096 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/pack_uploader.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/pack_uploader.tsx @@ -61,7 +61,7 @@ const OsqueryPackUploaderComponent: React.FC = ({ onCh return; } - onChange(parsedContent?.queries, packName.current); + onChange(parsedContent, packName.current); // @ts-expect-error update types filePickerRef.current?.removeFiles(new Event('fake')); }; diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx index 079b9ddacc9d0e..15acfcf4fbdc76 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx @@ -211,7 +211,7 @@ const QueriesFieldComponent: React.FC = ({ }, [setValue, tableSelectedItems]); const handlePackUpload = useCallback( - (newQueries, packName) => { + (parsedContent, packName) => { /* Osquery scheduled packs are supported since osquery_manager@0.5.0 */ const isOsqueryPackSupported = integrationPackageVersion ? satisfies(integrationPackageVersion, '>=0.5.0') @@ -219,14 +219,14 @@ const QueriesFieldComponent: React.FC = ({ setValue( produce((draft) => { - forEach(newQueries, (newQuery, newQueryId) => { + forEach(parsedContent.queries, (newQuery, newQueryId) => { draft[0].streams.push( getNewStream({ id: isOsqueryPackSupported ? newQueryId : `pack_${packName}_${newQueryId}`, - interval: newQuery.interval, + interval: newQuery.interval ?? parsedContent.interval, query: newQuery.query, - version: newQuery.version, - platform: getSupportedPlatforms(newQuery.platform), + version: newQuery.version ?? parsedContent.version, + platform: getSupportedPlatforms(newQuery.platform ?? parsedContent.platform), scheduledQueryGroupId, }) ); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts index bedca9d5ef8d75..cbd52fb418853f 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts @@ -6,6 +6,9 @@ */ export const ALL_OSQUERY_VERSIONS_OPTIONS = [ + { + label: '4.9.0', + }, { label: '4.8.0', }, diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx index fe54a46df8c057..35344f2503eb57 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx @@ -60,6 +60,7 @@ interface ViewResultsInDiscoverActionProps { buttonType: ViewResultsActionButtonType; endDate?: string; startDate?: string; + mode?: string; } function getLensAttributes( @@ -200,6 +201,7 @@ const ViewResultsInLensActionComponent: React.FC { const lensService = useKibana().services.lens; @@ -215,7 +217,7 @@ const ViewResultsInLensActionComponent: React.FC ), [agentIds, scheduledQueryGroupName] @@ -545,12 +550,15 @@ const ScheduledQueryGroupQueriesStatusTableComponent: React.FC ( ), - [agentIds] + [agentIds, scheduledQueryGroupName] ); const getItemId = useCallback( diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts index 6b6acc30036cc4..c3458698dd517e 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts @@ -33,10 +33,8 @@ export const useScheduledQueryGroup = ({ keepPreviousData: true, enabled: !skip || !scheduledQueryGroupId, select: (response) => response.item, - refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - staleTime: Infinity, } ); }; diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_errors.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_errors.ts index 31d98ee6204e9d..64e5a20de68c4b 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_errors.ts +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_errors.ts @@ -82,10 +82,8 @@ export const useScheduledQueryGroupQueryErrors = ({ keepPreviousData: true, enabled: !!(!skip && actionId && interval && agentIds?.length), select: (response) => response.rawResponse.hits ?? [], - refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - staleTime: Infinity, } ); }; diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts index 21117a4a0f83d3..f972640e259864 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group_query_last_results.ts @@ -79,10 +79,8 @@ export const useScheduledQueryGroupQueryLastResults = ({ enabled: !!(!skip && actionId && interval && agentIds?.length), // @ts-expect-error update types select: (response) => response.rawResponse.aggregations?.runs?.buckets[0] ?? [], - refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - staleTime: Infinity, } ); }; diff --git a/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts b/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts index 7a37fcc4cd8032..e53750080ef76c 100644 --- a/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts +++ b/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { map, partialRight, pick } from 'lodash'; +import { filter, map, partialRight, pick } from 'lodash'; import { promises as fs } from 'fs'; import path from 'path'; @@ -13,13 +13,42 @@ import { run } from '@kbn/dev-utils'; const ECS_COLUMN_SCHEMA_FIELDS = ['field', 'type', 'description']; +const RESTRICTED_FIELDS = [ + 'agent.name', + 'agent.id', + 'agent.ephemeral_id', + 'agent.type', + 'agent.version', + 'ecs.version', + 'event.agent_id_status', + 'event.ingested', + 'event.module', + 'host.hostname', + 'host.os.build', + 'host.os.kernel', + 'host.os.name', + 'host.os.family', + 'host.os.type', + 'host.os.version', + 'host.platform', + 'host.ip', + 'host.id', + 'host.mac', + 'host.architecture', + '@timestamp', +]; + run( async ({ flags }) => { - const schemaPath = path.resolve(`../public/common/schemas/ecs/`); + const schemaPath = path.resolve(`public/common/schemas/ecs/`); const schemaFile = path.join(schemaPath, flags.schema_version as string); const schemaData = await require(schemaFile); - const formattedSchema = map(schemaData, partialRight(pick, ECS_COLUMN_SCHEMA_FIELDS)); + const filteredSchemaData = filter( + schemaData, + (field) => !RESTRICTED_FIELDS.includes(field.field) + ); + const formattedSchema = map(filteredSchemaData, partialRight(pick, ECS_COLUMN_SCHEMA_FIELDS)); await fs.writeFile( path.join(schemaPath, `v${flags.schema_version}-formatted.json`), diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts index e35e776cb1958f..08b5b4314f1f18 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts @@ -5,7 +5,9 @@ * 2.0. */ +import bluebird from 'bluebird'; import { schema } from '@kbn/config-schema'; +import { GetAgentPoliciesResponseItem, AGENT_SAVED_OBJECT_TYPE } from '../../../../fleet/common'; import { PLUGIN_ID } from '../../../common'; import { IRouter } from '../../../../../../src/core/server'; import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; @@ -22,13 +24,32 @@ export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAp }, async (context, request, response) => { const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asInternalUser; - const agentPolicies = await osqueryContext.service.getAgentPolicyService()?.list(soClient, { + // TODO: Use getAgentPoliciesHandler from x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts + const body = await osqueryContext.service.getAgentPolicyService()?.list(soClient, { ...(request.query || {}), perPage: 100, }); - return response.ok({ body: agentPolicies }); + if (body?.items) { + await bluebird.map( + body.items, + (agentPolicy: GetAgentPoliciesResponseItem) => + osqueryContext.service + .getAgentService() + ?.listAgents(esClient, { + showInactive: false, + perPage: 0, + page: 1, + kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id:${agentPolicy.id}`, + }) + .then(({ total: agentTotal }) => (agentPolicy.agents = agentTotal)), + { concurrency: 10 } + ); + } + + return response.ok({ body }); } ); }; diff --git a/x-pack/plugins/reporting/server/lib/content_stream.test.ts b/x-pack/plugins/reporting/server/lib/content_stream.test.ts index 34b8982a5257dc..da55b4728d10ec 100644 --- a/x-pack/plugins/reporting/server/lib/content_stream.test.ts +++ b/x-pack/plugins/reporting/server/lib/content_stream.test.ts @@ -9,21 +9,26 @@ import { set } from 'lodash'; import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { createMockLevelLogger } from '../test_helpers'; import { ContentStream } from './content_stream'; -import { ExportTypesRegistry } from './export_types_registry'; describe('ContentStream', () => { let client: ReturnType['asInternalUser']; - let exportTypesRegistry: jest.Mocked; let logger: ReturnType; let stream: ContentStream; + let base64Stream: ContentStream; beforeEach(() => { client = elasticsearchServiceMock.createClusterClient().asInternalUser; - exportTypesRegistry = ({ - get: jest.fn(() => ({})), - } as unknown) as typeof exportTypesRegistry; logger = createMockLevelLogger(); - stream = new ContentStream(client, exportTypesRegistry, logger, { + stream = new ContentStream( + client, + logger, + { + id: 'something', + index: 'somewhere', + }, + { encoding: 'raw' } + ); + base64Stream = new ContentStream(client, logger, { id: 'something', index: 'somewhere', }); @@ -79,9 +84,6 @@ describe('ContentStream', () => { }); it('should decode base64 encoded content', async () => { - exportTypesRegistry.get.mockReturnValueOnce({ jobContentEncoding: 'base64' } as ReturnType< - typeof exportTypesRegistry.get - >); client.search.mockResolvedValueOnce( set( {}, @@ -89,7 +91,7 @@ describe('ContentStream', () => { Buffer.from('encoded content').toString('base64') ) ); - const data = await new Promise((resolve) => stream.once('data', resolve)); + const data = await new Promise((resolve) => base64Stream.once('data', resolve)); expect(data).toEqual(Buffer.from('encoded content')); }); @@ -184,9 +186,6 @@ describe('ContentStream', () => { }); it('should decode every chunk separately', async () => { - exportTypesRegistry.get.mockReturnValueOnce({ jobContentEncoding: 'base64' } as ReturnType< - typeof exportTypesRegistry.get - >); client.search.mockResolvedValueOnce( set({}, 'body.hits.hits.0._source', { jobtype: 'pdf', @@ -211,7 +210,7 @@ describe('ContentStream', () => { ) ); let data = ''; - for await (const chunk of stream) { + for await (const chunk of base64Stream) { data += chunk; } @@ -275,12 +274,8 @@ describe('ContentStream', () => { }); it('should encode using base64', async () => { - exportTypesRegistry.get.mockReturnValueOnce({ jobContentEncoding: 'base64' } as ReturnType< - typeof exportTypesRegistry.get - >); - - stream.end('12345'); - await new Promise((resolve) => stream.once('finish', resolve)); + base64Stream.end('12345'); + await new Promise((resolve) => base64Stream.once('finish', resolve)); expect(client.update).toHaveBeenCalledTimes(1); @@ -347,14 +342,11 @@ describe('ContentStream', () => { }); it('should encode every chunk separately', async () => { - exportTypesRegistry.get.mockReturnValueOnce({ jobContentEncoding: 'base64' } as ReturnType< - typeof exportTypesRegistry.get - >); client.cluster.getSettings.mockResolvedValueOnce( set({}, 'body.defaults.http.max_content_length', 1028) ); - stream.end('12345678'); - await new Promise((resolve) => stream.once('finish', resolve)); + base64Stream.end('12345678'); + await new Promise((resolve) => base64Stream.once('finish', resolve)); expect(client.update).toHaveBeenCalledTimes(1); expect(client.update).toHaveBeenCalledWith( diff --git a/x-pack/plugins/reporting/server/lib/content_stream.ts b/x-pack/plugins/reporting/server/lib/content_stream.ts index 1d8ebf2bdfdbe0..79ff9a6812137b 100644 --- a/x-pack/plugins/reporting/server/lib/content_stream.ts +++ b/x-pack/plugins/reporting/server/lib/content_stream.ts @@ -12,7 +12,6 @@ import { ByteSizeValue } from '@kbn/config-schema'; import type { ElasticsearchClient } from 'src/core/server'; import { ReportingCore } from '..'; import { ReportSource } from '../../common/types'; -import { ExportTypesRegistry } from './export_types_registry'; import { LevelLogger } from './level_logger'; /** @@ -42,6 +41,15 @@ interface ChunkSource { output: ChunkOutput; } +type ContentStreamEncoding = 'base64' | 'raw'; + +interface ContentStreamParameters { + /** + * Content encoding. By default, it is Base64. + */ + encoding?: ContentStreamEncoding; +} + export class ContentStream extends Duplex { /** * @see https://en.wikipedia.org/wiki/Base64#Output_padding @@ -62,10 +70,9 @@ export class ContentStream extends Duplex { private bytesRead = 0; private chunksRead = 0; private chunksWritten = 0; - private jobContentEncoding?: string; private jobSize?: number; - private jobType?: string; private maxChunkSize?: number; + private parameters: Required; private puid = new Puid(); private primaryTerm?: number; private seqNo?: number; @@ -78,60 +85,20 @@ export class ContentStream extends Duplex { constructor( private client: ElasticsearchClient, - private exportTypesRegistry: ExportTypesRegistry, private logger: LevelLogger, - private document: ContentStreamDocument + private document: ContentStreamDocument, + { encoding = 'base64' }: ContentStreamParameters = {} ) { super(); - } - - private async getJobType() { - if (!this.jobType) { - const { id, index } = this.document; - const body: SearchRequest['body'] = { - _source: { includes: ['jobtype'] }, - query: { - constant_score: { - filter: { - bool: { - must: [{ term: { _id: id } }], - }, - }, - }, - }, - size: 1, - }; - - const response = await this.client.search({ body, index }); - const hits = response?.body.hits?.hits?.[0]; - this.jobType = hits?._source?.jobtype; - } - - return this.jobType; - } - - private async getJobContentEncoding() { - if (!this.jobContentEncoding) { - const jobType = await this.getJobType(); - - ({ jobContentEncoding: this.jobContentEncoding } = this.exportTypesRegistry.get( - ({ jobType: item }) => item === jobType - )); - } - - return this.jobContentEncoding; + this.parameters = { encoding }; } private async decode(content: string) { - const contentEncoding = await this.getJobContentEncoding(); - - return Buffer.from(content, contentEncoding === 'base64' ? 'base64' : undefined); + return Buffer.from(content, this.parameters.encoding === 'base64' ? 'base64' : undefined); } private async encode(buffer: Buffer) { - const contentEncoding = await this.getJobContentEncoding(); - - return buffer.toString(contentEncoding === 'base64' ? 'base64' : undefined); + return buffer.toString(this.parameters.encoding === 'base64' ? 'base64' : undefined); } private async getMaxContentSize() { @@ -146,10 +113,9 @@ export class ContentStream extends Duplex { private async getMaxChunkSize() { if (!this.maxChunkSize) { const maxContentSize = (await this.getMaxContentSize()) - REQUEST_SPAN_SIZE_IN_BYTES; - const jobContentEncoding = await this.getJobContentEncoding(); this.maxChunkSize = - jobContentEncoding === 'base64' + this.parameters.encoding === 'base64' ? ContentStream.getMaxBase64EncodedSize(maxContentSize) : ContentStream.getMaxJsonEscapedSize(maxContentSize); @@ -180,7 +146,6 @@ export class ContentStream extends Duplex { const response = await this.client.search({ body, index }); const hits = response?.body.hits?.hits?.[0]; - this.jobType = hits?._source?.jobtype; this.jobSize = hits?._source?.output?.size; return hits?._source?.output?.content; @@ -341,15 +306,18 @@ export class ContentStream extends Duplex { } } -export async function getContentStream(reporting: ReportingCore, document: ContentStreamDocument) { +export async function getContentStream( + reporting: ReportingCore, + document: ContentStreamDocument, + parameters?: ContentStreamParameters +) { const { asInternalUser: client } = await reporting.getEsClient(); - const exportTypesRegistry = reporting.getExportTypesRegistry(); const { logger } = reporting.getPluginSetupDeps(); return new ContentStream( client, - exportTypesRegistry, logger.clone(['content_stream', document.id]), - document + document, + parameters ); } diff --git a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts index 0602c45b8016ac..1e29efd9cce0bc 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts @@ -22,7 +22,7 @@ import { CancellationToken } from '../../../common'; import { durationToNumber, numberToDuration } from '../../../common/schema_utils'; import { ReportOutput } from '../../../common/types'; import { ReportingConfigType } from '../../config'; -import { BasePayload, RunTaskFn } from '../../types'; +import { BasePayload, ExportTypeDefinition, RunTaskFn } from '../../types'; import { Report, ReportDocument, ReportingStore, SavedReport } from '../store'; import { ReportFailedFields, ReportProcessingFields } from '../store/store'; import { @@ -43,6 +43,10 @@ interface ReportingExecuteTaskInstance { runAt?: Date; } +interface TaskExecutor extends Pick { + jobExecutor: RunTaskFn; +} + function isOutput(output: any): output is CompletedReportOutput { return output?.size != null; } @@ -56,7 +60,7 @@ export class ExecuteReportTask implements ReportingTask { private logger: LevelLogger; private taskManagerStart?: TaskManagerStartContract; - private taskExecutors?: Map>; + private taskExecutors?: Map; private kibanaId?: string; private kibanaName?: string; private store?: ReportingStore; @@ -78,13 +82,16 @@ export class ExecuteReportTask implements ReportingTask { const { reporting } = this; const exportTypesRegistry = reporting.getExportTypesRegistry(); - const executors = new Map>(); + const executors = new Map(); for (const exportType of exportTypesRegistry.getAll()) { const exportTypeLogger = this.logger.clone([exportType.id]); const jobExecutor = exportType.runTaskFnFactory(reporting, exportTypeLogger); // The task will run the function with the job type as a param. // This allows us to retrieve the specific export type runFn when called to run an export - executors.set(exportType.jobType, jobExecutor); + executors.set(exportType.jobType, { + jobExecutor, + jobContentEncoding: exportType.jobContentEncoding, + }); } this.taskExecutors = executors; @@ -113,6 +120,10 @@ export class ExecuteReportTask implements ReportingTask { return this.taskManagerStart; } + private getJobContentEncoding(jobType: string) { + return this.taskExecutors?.get(jobType)?.jobContentEncoding; + } + public async _claimJob(task: ReportTaskParams): Promise { if (this.kibanaId == null) { throw new Error(`Kibana instance ID is undefined!`); @@ -241,7 +252,7 @@ export class ExecuteReportTask implements ReportingTask { // run the report // if workerFn doesn't finish before timeout, call the cancellationToken and throw an error const queueTimeout = durationToNumber(this.config.queue.timeout); - return Rx.from(runner(task.id, task.payload, cancellationToken, stream)) + return Rx.from(runner.jobExecutor(task.id, task.payload, cancellationToken, stream)) .pipe(timeout(queueTimeout)) // throw an error if a value is not emitted before timeout .toPromise(); } @@ -323,12 +334,19 @@ export class ExecuteReportTask implements ReportingTask { this.logger.debug(`Reports running: ${this.reporting.countConcurrentReports()}.`); try { - const stream = await getContentStream(this.reporting, { - id: report._id, - index: report._index, - if_primary_term: report._primary_term, - if_seq_no: report._seq_no, - }); + const jobContentEncoding = this.getJobContentEncoding(jobType); + const stream = await getContentStream( + this.reporting, + { + id: report._id, + index: report._index, + if_primary_term: report._primary_term, + if_seq_no: report._seq_no, + }, + { + encoding: jobContentEncoding === 'base64' ? 'base64' : 'raw', + } + ); const output = await this._performJob(task, cancellationToken, stream); stream.end(); diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index 0d7a249daa5ac6..c083849686ff0d 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -51,15 +51,18 @@ const getReportingHeaders = (output: TaskRunResult, exportType: ExportTypeDefini export function getDocumentPayloadFactory(reporting: ReportingCore) { const exportTypesRegistry = reporting.getExportTypesRegistry(); - async function getCompleted( - output: TaskRunResult, - jobType: string, - title: string, - content: Stream - ): Promise { + async function getCompleted({ + id, + index, + output, + jobtype: jobType, + payload: { title }, + }: Required): Promise { const exportType = exportTypesRegistry.get( (item: ExportTypeDefinition) => item.jobType === jobType ); + const encoding = exportType.jobContentEncoding === 'base64' ? 'base64' : 'raw'; + const content = await getContentStream(reporting, { id, index }, { encoding }); const filename = getTitle(exportType, title); const headers = getReportingHeaders(output, exportType); @@ -77,18 +80,21 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { // @TODO: These should be semantic HTTP codes as 500/503's indicate // error then these are really operating properly. - function getFailure(content: string): Payload { + async function getFailure({ id }: ReportApiJSON): Promise { + const jobsQuery = jobsQueryFactory(reporting); + const error = await jobsQuery.getError(id); + return { statusCode: 500, content: { - message: `Reporting generation failed: ${content}`, + message: `Reporting generation failed: ${error}`, }, contentType: 'application/json', headers: {}, }; } - function getIncomplete(status: string) { + function getIncomplete({ status }: ReportApiJSON): Payload { return { statusCode: 503, content: status, @@ -97,30 +103,18 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { }; } - return async function getDocumentPayload({ - id, - index, - output, - status, - jobtype: jobType, - payload: { title }, - }: ReportApiJSON): Promise { - if (output) { - if (status === statuses.JOB_STATUS_COMPLETED || status === statuses.JOB_STATUS_WARNINGS) { - const stream = await getContentStream(reporting, { id, index }); - - return getCompleted(output, jobType, title, stream); + return async function getDocumentPayload(report: ReportApiJSON): Promise { + if (report.output) { + if ([statuses.JOB_STATUS_COMPLETED, statuses.JOB_STATUS_WARNINGS].includes(report.status)) { + return getCompleted(report as Required); } - if (status === statuses.JOB_STATUS_FAILED) { - const jobsQuery = jobsQueryFactory(reporting); - const error = await jobsQuery.getError(id); - - return getFailure(error); + if (statuses.JOB_STATUS_FAILED === report.status) { + return getFailure(report); } } // send a 503 indicating that the report isn't completed yet - return getIncomplete(status); + return getIncomplete(report); }; } diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index c1a4fccaf205ba..ad092285806378 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -17,6 +17,7 @@ import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, + ALERT_WORKFLOW_STATUS, ALERT_UUID, EVENT_ACTION, EVENT_KIND, @@ -118,7 +119,7 @@ describe('createLifecycleExecutor', () => { ); }); - it('overwrites existing documents for repeatedly firing alerts', async () => { + it('updates existing documents for repeatedly firing alerts', async () => { const logger = loggerMock.create(); const ruleDataClientMock = createRuleDataClientMock(); ruleDataClientMock.getReader().search.mockResolvedValue({ @@ -126,17 +127,35 @@ describe('createLifecycleExecutor', () => { hits: [ { fields: { + '@timestamp': '', [ALERT_ID]: 'TEST_ALERT_0', + [ALERT_UUID]: 'ALERT_0_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', - labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must not show up in the written doc + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [ALERT_WORKFLOW_STATUS]: 'closed', + [SPACE_IDS]: ['fake-space-id'], + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must show up in the written doc }, }, { fields: { + '@timestamp': '', [ALERT_ID]: 'TEST_ALERT_1', + [ALERT_UUID]: 'ALERT_1_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [ALERT_WORKFLOW_STATUS]: 'open', + [SPACE_IDS]: ['fake-space-id'], labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must not show up in the written doc }, }, @@ -188,14 +207,19 @@ describe('createLifecycleExecutor', () => { { index: { _id: 'TEST_ALERT_0_UUID' } }, expect.objectContaining({ [ALERT_ID]: 'TEST_ALERT_0', + [ALERT_WORKFLOW_STATUS]: 'closed', [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, + [EVENT_ACTION]: 'active', [EVENT_KIND]: 'signal', }), { index: { _id: 'TEST_ALERT_1_UUID' } }, expect.objectContaining({ [ALERT_ID]: 'TEST_ALERT_1', + [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [EVENT_ACTION]: 'active', [EVENT_KIND]: 'signal', }), @@ -216,8 +240,6 @@ describe('createLifecycleExecutor', () => { }); it('updates existing documents for recovered alerts', async () => { - // NOTE: the documents should actually also be updated for recurring, - // active alerts (see elastic/kibana#108670) const logger = loggerMock.create(); const ruleDataClientMock = createRuleDataClientMock(); ruleDataClientMock.getReader().search.mockResolvedValue({ diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 97337e3a5e09e1..231d3060a2c542 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -182,19 +182,17 @@ export const createLifecycleExecutor = ( const allAlertIds = [...new Set(currentAlertIds.concat(trackedAlertIds))]; - const trackedAlertStatesOfRecovered = Object.values(state.trackedAlerts).filter( - (trackedAlertState) => !currentAlerts[trackedAlertState.alertId] - ); + const trackedAlertStates = Object.values(state.trackedAlerts); logger.debug( - `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStatesOfRecovered.length} recovered)` + `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStates.length} previous)` ); const alertsDataMap: Record> = { ...currentAlerts, }; - if (trackedAlertStatesOfRecovered.length) { + if (trackedAlertStates.length) { const { hits } = await ruleDataClient.getReader().search({ body: { query: { @@ -207,7 +205,7 @@ export const createLifecycleExecutor = ( }, { terms: { - [ALERT_UUID]: trackedAlertStatesOfRecovered.map( + [ALERT_UUID]: trackedAlertStates.map( (trackedAlertState) => trackedAlertState.alertUuid ), }, @@ -215,7 +213,7 @@ export const createLifecycleExecutor = ( ], }, }, - size: trackedAlertStatesOfRecovered.length, + size: trackedAlertStates.length, collapse: { field: ALERT_UUID, }, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 2b138ae723305d..b2cd69be7fdadc 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -263,6 +263,36 @@ describe('createLifecycleRuleTypeFactory', () => { }, ]); + // TODO mock the resolved value before calling alertWithLifecycle again + const lastOpbeansNodeDoc = helpers.ruleDataClientMock + .getWriter() + .bulk.mock.calls[0][0].body?.concat() + .reverse() + .find( + (doc: any) => !('index' in doc) && doc['service.name'] === 'opbeans-node' + ) as Record; + + const stored = mapValues(lastOpbeansNodeDoc, (val) => { + return castArray(val); + }); + + helpers.ruleDataClientMock.getReader().search.mockResolvedValueOnce({ + hits: { + hits: [{ fields: stored } as any], + total: { + value: 1, + relation: 'eq', + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 1, + total: 1, + }, + }); + await helpers.alertWithLifecycle([ { id: 'opbeans-java', @@ -274,6 +304,7 @@ describe('createLifecycleRuleTypeFactory', () => { id: 'opbeans-node', fields: { 'service.name': 'opbeans-node', + 'kibana.alert.workflow_status': 'closed', }, }, ]); @@ -281,7 +312,6 @@ describe('createLifecycleRuleTypeFactory', () => { it('writes the correct alerts', () => { expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(2); - const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[1][0].body!; const documents = body.filter((op: any) => !('index' in op)) as any[]; diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/cypress/screens/alerts.ts index fe7bf959fe0596..675a25641a2bd2 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts.ts @@ -41,8 +41,6 @@ export const ACKNOWLEDGED_ALERTS_FILTER_BTN = '[data-test-subj="acknowledgedAler export const LOADING_ALERTS_PANEL = '[data-test-subj="loading-alerts-panel"]'; -export const LOADING_SPINNER = '[data-test-subj="LoadingPanelTimeline"]'; - export const MANAGE_ALERT_DETECTION_RULES_BTN = '[data-test-subj="manage-alert-detection-rules"]'; export const MARK_ALERT_ACKNOWLEDGED_BTN = '[data-test-subj="acknowledged-alert-status"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 98f9b3455841f7..f34c3f598e934c 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -14,7 +14,6 @@ import { ThreatIndicatorRule, ThresholdRule, } from '../objects/rule'; -import { LOADING_SPINNER } from '../screens/alerts'; import { ABOUT_CONTINUE_BTN, ABOUT_EDIT_TAB, @@ -94,6 +93,7 @@ import { EMAIL_CONNECTOR_USER_INPUT, EMAIL_CONNECTOR_PASSWORD_INPUT, } from '../screens/create_new_rule'; +import { LOADING_INDICATOR } from '../screens/security_header'; import { TOAST_ERROR } from '../screens/shared'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; import { TIMELINE } from '../screens/timelines'; @@ -532,7 +532,8 @@ export const waitForAlertsToPopulate = async (alertCountThreshold = 1) => { cy.waitUntil( () => { refreshPage(); - cy.get(LOADING_SPINNER).should('not.exist'); + cy.get(LOADING_INDICATOR).should('exist'); + cy.get(LOADING_INDICATOR).should('not.exist'); return cy .get(SERVER_SIDE_EVENT_COUNT) .invoke('text') diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx index 46d05d9712227c..94abbca1da908c 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx @@ -84,27 +84,29 @@ export interface HeaderPageProps extends HeaderProps { titleNode?: React.ReactElement; } -const HeaderLinkBack: React.FC<{ backOptions: BackOptions }> = React.memo(({ backOptions }) => { - const { navigateToUrl } = useKibana().services.application; - const { formatUrl } = useFormatUrl(backOptions.pageId); +export const HeaderLinkBack: React.FC<{ backOptions: BackOptions }> = React.memo( + ({ backOptions }) => { + const { navigateToUrl } = useKibana().services.application; + const { formatUrl } = useFormatUrl(backOptions.pageId); - const backUrl = formatUrl(backOptions.path ?? ''); - return ( - - { - ev.preventDefault(); - navigateToUrl(backUrl); - }} - href={backUrl} - iconType="arrowLeft" - > - {backOptions.text} - - - ); -}); + const backUrl = formatUrl(backOptions.path ?? ''); + return ( + + { + ev.preventDefault(); + navigateToUrl(backUrl); + }} + href={backUrl} + iconType="arrowLeft" + > + {backOptions.text} + + + ); + } +); HeaderLinkBack.displayName = 'HeaderLinkBack'; const HeaderPageComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx index 93bd5f16634b29..581df61efc3e4c 100644 --- a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx @@ -9,6 +9,8 @@ import React, { FC, memo, useMemo } from 'react'; import { CommonProps, EuiPageHeader, + EuiPageContent, + EuiPageContentBody, EuiFlexGroup, EuiFlexItem, EuiTitle, @@ -20,13 +22,22 @@ import { useTestIdGenerator } from './hooks/use_test_id_generator'; interface AdministrationListPageProps { title: React.ReactNode; - subtitle: React.ReactNode; + subtitle?: React.ReactNode; actions?: React.ReactNode; + restrictWidth?: boolean | number; headerBackComponent?: React.ReactNode; } export const AdministrationListPage: FC = memo( - ({ title, subtitle, actions, children, headerBackComponent, ...otherProps }) => { + ({ + title, + subtitle, + actions, + children, + restrictWidth = false, + headerBackComponent, + ...otherProps + }) => { const header = useMemo(() => { return ( @@ -43,7 +54,7 @@ export const AdministrationListPage: FC { - return {subtitle}; + return subtitle ? {subtitle} : undefined; }, [subtitle]); const getTestId = useTestIdGenerator(otherProps['data-test-subj']); @@ -55,11 +66,19 @@ export const AdministrationListPage: FC - {children} + + {children} + diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts index 17d836695bcf7e..e51fe15e7130ff 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts @@ -286,22 +286,15 @@ describe('endpoint list middleware', () => { it('should set ActivityLog state to loading', async () => { dispatchUserChangedUrl(); - dispatchGetActivityLogLoading(); const loadingDispatched = waitForAction('endpointDetailsActivityLogChanged', { validate(action) { return isLoadingResourceState(action.payload); }, }); + dispatchGetActivityLogLoading(); const loadingDispatchedResponse = await loadingDispatched; - expect(mockedApis.responseProvider.activityLogResponse).toHaveBeenCalledWith({ - path: expect.any(String), - query: { - page: 1, - page_size: 50, - }, - }); expect(loadingDispatchedResponse.payload.type).toEqual('LoadingResourceState'); }); @@ -343,6 +336,25 @@ describe('endpoint list middleware', () => { expect(failedAction.error).toBe(apiError); }); + it('should not call API again if it fails', async () => { + dispatchUserChangedUrl(); + + const apiError = new Error('oh oh'); + const failedDispatched = waitForAction('endpointDetailsActivityLogChanged', { + validate(action) { + return isFailedResourceState(action.payload); + }, + }); + + mockedApis.responseProvider.activityLogResponse.mockImplementation(() => { + throw apiError; + }); + + await failedDispatched; + + expect(mockedApis.responseProvider.activityLogResponse).toHaveBeenCalledTimes(1); + }); + it('should not fetch Activity Log with invalid date ranges', async () => { dispatchUserChangedUrl(); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 1be9ff5be55ef6..df4361a6048a84 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -33,11 +33,13 @@ import { getActivityLogData, getActivityLogDataPaging, getLastLoadedActivityLogData, + getActivityLogError, detailsData, getIsEndpointPackageInfoUninitialized, getIsOnEndpointDetailsActivityLog, getMetadataTransformStats, isMetadataTransformStatsLoading, + getActivityLogIsUninitializedOrHasSubsequentAPIError, } from './selectors'; import { AgentIdsPendingActions, @@ -124,7 +126,8 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory { - const pagingOptions = + const updatedActivityLog = action.payload.type === 'LoadedResourceState' ? { ...state.endpointDetails.activityLog, @@ -53,7 +53,7 @@ const handleEndpointDetailsActivityLogChanged: CaseReducer +) => boolean = createSelector(getActivityLogData, (activityLog) => + isUninitialisedResourceState(activityLog) +); + export const getActivityLogRequestLoading: ( state: Immutable ) => boolean = createSelector(getActivityLogData, (activityLog) => @@ -413,6 +419,19 @@ export const getActivityLogError: ( } }); +// returns a true if either lgo is uninitialised +// or if it has failed an api call after having fetched a non empty log list earlier +export const getActivityLogIsUninitializedOrHasSubsequentAPIError: ( + state: Immutable +) => boolean = createSelector( + getActivityLogUninitialized, + getLastLoadedActivityLogData, + getActivityLogError, + (isUninitialized, lastLoadedLogData, isAPIError) => { + return isUninitialized || (!isAPIError && !!lastLoadedLogData?.data.length); + } +); + export const getIsEndpointHostIsolated = createSelector(detailsData, (details) => { return (details && isEndpointHostIsolated(details)) || false; }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx index a6e571dbd7df9a..8f044959f4c903 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx @@ -5,13 +5,10 @@ * 2.0. */ -import { useDispatch } from 'react-redux'; -import React, { memo, useCallback, useMemo } from 'react'; -import { EuiTab, EuiTabs, EuiFlyoutBody, EuiTabbedContentTab, EuiSpacer } from '@elastic/eui'; +import React, { memo, useMemo } from 'react'; +import { EuiTab, EuiTabs, EuiFlyoutBody, EuiSpacer } from '@elastic/eui'; import { EndpointIndexUIQueryParams } from '../../../types'; -import { EndpointAction } from '../../../store/action'; -import { useEndpointSelector } from '../../hooks'; -import { getActivityLogDataPaging } from '../../../store/selectors'; + import { EndpointDetailsFlyoutHeader } from './flyout_header'; import { useNavigateByRouterEventHandler } from '../../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; import { useAppUrl } from '../../../../../../common/lib/kibana'; @@ -33,17 +30,9 @@ interface EndpointDetailsTabs { } const EndpointDetailsTab = memo( - ({ - tab, - isSelected, - handleTabClick, - }: { - tab: EndpointDetailsTabs; - isSelected: boolean; - handleTabClick: () => void; - }) => { + ({ tab, isSelected }: { tab: EndpointDetailsTabs; isSelected: boolean }) => { const { getAppUrl } = useAppUrl(); - const onClick = useNavigateByRouterEventHandler(tab.route, handleTabClick); + const onClick = useNavigateByRouterEventHandler(tab.route); return ( { - const dispatch = useDispatch<(action: EndpointAction) => void>(); - const { pageSize } = useEndpointSelector(getActivityLogDataPaging); - - const handleTabClick = useCallback( - (tab: EuiTabbedContentTab) => { - if (tab.id === EndpointDetailsTabsTypes.activityLog) { - dispatch({ - type: 'endpointDetailsActivityLogUpdatePaging', - payload: { - disabled: false, - page: 1, - pageSize, - startDate: undefined, - endDate: undefined, - }, - }); - } - }, - [dispatch, pageSize] - ); - const selectedTab = useMemo(() => tabs.find((tab) => tab.id === show), [tabs, show]); const renderTabs = tabs.map((tab) => ( - handleTabClick(tab)} - isSelected={tab.id === selectedTab?.id} - /> + )); return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx index 121f23fdb3a9e7..5172b59450e03d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx @@ -30,6 +30,7 @@ import { getActivityLogRequestLoaded, getLastLoadedActivityLogData, getActivityLogRequestLoading, + getActivityLogUninitialized, } from '../../store/selectors'; const StyledEuiFlexGroup = styled(EuiFlexGroup)<{ isShorter: boolean }>` @@ -42,6 +43,7 @@ const LoadMoreTrigger = styled.div` export const EndpointActivityLog = memo( ({ activityLog }: { activityLog: AsyncResourceState> }) => { + const activityLogUninitialized = useEndpointSelector(getActivityLogUninitialized); const activityLogLoading = useEndpointSelector(getActivityLogRequestLoading); const activityLogLoaded = useEndpointSelector(getActivityLogRequestLoaded); const activityLastLogData = useEndpointSelector(getLastLoadedActivityLogData); @@ -96,7 +98,9 @@ export const EndpointActivityLog = memo( return ( <> - {showEmptyState ? ( + {(activityLogLoading && !activityLastLogData?.data.length) || activityLogUninitialized ? ( + + ) : showEmptyState ? ( ((props) => { title: i18n.translate( 'xpack.securitySolution.endpoint.policyDetails.agentsSummary.totalTitle', { - defaultMessage: 'Agents', + defaultMessage: 'Total agents', } ), health: '', @@ -73,20 +74,33 @@ export const AgentsSummary = memo((props) => { ]; }, []); + const theme = useContext(ThemeContext); + return ( - + {stats.map(({ key, title, health }) => { return ( - + - {health && } + {health && ( + + )} ), diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx index d0a246291fbe97..f589ec628befb2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC, ReactNode, memo } from 'react'; +import React, { FC, ReactNode, memo, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -20,6 +20,7 @@ import { EuiIconTip, } from '@elastic/eui'; +import { ThemeContext } from 'styled-components'; import { OperatingSystem } from '../../../../../../../common/endpoint/types'; import { OS_TITLES } from '../../../../../common/translations'; @@ -28,7 +29,7 @@ const TITLES = { defaultMessage: 'Type', }), os: i18n.translate('xpack.securitySolution.endpoint.policyDetailOS', { - defaultMessage: 'Operating System', + defaultMessage: 'Operating system', }), }; @@ -56,55 +57,63 @@ export const ConfigFormHeading: FC = memo(({ children }) => ( ConfigFormHeading.displayName = 'ConfigFormHeading'; export const ConfigForm: FC = memo( - ({ type, supportedOss, osRestriction, dataTestSubj, rightCorner, children }) => ( - - - - {TITLES.type} - {type} - - - {TITLES.os} - - - {supportedOss.map((os) => OS_TITLES[os]).join(', ')} - - {osRestriction && ( + ({ type, supportedOss, osRestriction, dataTestSubj, rightCorner, children }) => { + const paddingSize = useContext(ThemeContext).eui.euiPanelPaddingModifiers.paddingMedium; + return ( + + + + {TITLES.type} + {type} + + + {TITLES.os} + - - - - - - - - - - + {supportedOss.map((os) => OS_TITLES[os]).join(', ')} - )} - - - - - - {rightCorner} + {osRestriction && ( + + + + + + + + + + + + + )} - - - {rightCorner} - - + + + + {rightCorner} + + + + + {rightCorner} + + - + - {children} - - ) +
{children}
+ + ); + } ); ConfigForm.displayName = 'ConfigForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx index 1f4d6a6571aeb6..9bdc5aa4939352 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx @@ -54,7 +54,7 @@ export const EventsForm = ({ }: EventsFormProps) => ( { expect(backToListLink.prop('href')).toBe(`/app/security${endpointListPath}`); expect(backToListLink.text()).toBe('Back to endpoint hosts'); - const pageTitle = policyView.find('h1[data-test-subj="header-page-title"]'); + const pageTitle = policyView.find('span[data-test-subj="header-page-title"]'); expect(pageTitle).toHaveLength(1); expect(pageTitle.text()).toEqual(policyPackagePolicy.name); }); @@ -150,7 +150,7 @@ describe('Policy Details', () => { const agentsSummary = policyView.find('EuiFlexGroup[data-test-subj="policyAgentsSummary"]'); expect(agentsSummary).toHaveLength(1); - expect(agentsSummary.text()).toBe('Agents5Healthy3Unhealthy1Offline1'); + expect(agentsSummary.text()).toBe('Total agents5Healthy3Unhealthy1Offline1'); }); it('should display cancel button', async () => { await asyncActions; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 8c587858a1f2aa..c8b34f97ee6d66 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState, useContext } from 'react'; -import styled, { ThemeContext } from 'styled-components'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -42,26 +41,9 @@ import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/ import { APP_ID } from '../../../../../common/constants'; import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types'; import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; -import { HeaderPage } from '../../../../common/components/header_page'; +import { HeaderLinkBack } from '../../../../common/components/header_page'; import { PolicyDetailsForm } from './policy_details_form'; - -const maxFormWidth = '770px'; -const PolicyDetailsHeader = styled.div` - padding: ${(props) => props.theme.eui.paddingSizes.xl} 0; - border-bottom: 1px solid #d3dae6; - .securitySolutionHeaderPage { - max-width: ${maxFormWidth}; - margin: 0 auto; - } -`; - -const PolicyDetailsFormDiv = styled.div` - background-color: ${(props) => props.theme.eui.euiHeaderBackgroundColor}; - padding: ${(props) => props.theme.eui.paddingSizes.l} 0; - max-width: ${maxFormWidth}; - flex: 1; - align-self: center; -`; +import { AdministrationListPage } from '../../../components/administration_list_page'; export const PolicyDetails = React.memo(() => { const dispatch = useDispatch<(action: AppAction) => void>(); @@ -84,8 +66,8 @@ export const PolicyDetails = React.memo(() => { const [showConfirm, setShowConfirm] = useState(false); const [routeState, setRouteState] = useState(); const policyName = policyItem?.name ?? ''; + const policyDescription = policyItem?.description ?? undefined; const hostListRouterPath = getEndpointListPath({ name: 'endpointList' }); - const theme = useContext(ThemeContext); // Handle showing update statuses useEffect(() => { @@ -178,6 +160,18 @@ export const PolicyDetails = React.memo(() => { /> ); + const backToEndpointList = ( + + ); + return ( <> {showConfirm && ( @@ -187,69 +181,47 @@ export const PolicyDetails = React.memo(() => { onConfirm={handleSaveConfirmation} /> )} - - - - {headerRightContent} - - - - - - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx index e21e7117762e0b..c9d1b3b7882a0c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx @@ -58,17 +58,17 @@ export const PolicyDetailsForm = memo(() => { - + - + {isPlatinumPlus ? : } - + {isPlatinumPlus ? ( ) : ( )} - + {isPlatinumPlus ? ( ) : ( @@ -85,13 +85,13 @@ export const PolicyDetailsForm = memo(() => { - + - + - + - + diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx index 793c24a0c4d0cb..acdbb7e9790922 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/radio_buttons.tsx @@ -64,12 +64,12 @@ export const RadioButtons = React.memo( - + @@ -112,15 +112,15 @@ export const UserNotification = React.memo( checked={userNotificationSelected} disabled={selected === ProtectionModes.off} label={i18n.translate('xpack.securitySolution.endpoint.policyDetail.notifyUser', { - defaultMessage: 'Notify User', + defaultMessage: 'Notify user', })} /> {userNotificationSelected && ( <> - - + + - +

{ const protectionLabel = i18n.translate( 'xpack.securitySolution.endpoint.policy.protections.behavior', { - defaultMessage: 'Behavior protections', + defaultMessage: 'Behavior', } ); return ( { const protectionLabel = i18n.translate( 'xpack.securitySolution.endpoint.policy.protections.memory', { - defaultMessage: 'Memory Manipulation Protection', + defaultMessage: 'Memory manipulation', } ); return ( boolean + hasAlertsCrudPermissionsByRule?: ({ + ruleConsumer, + ruleProducer, + }: { + ruleConsumer: string; + ruleProducer?: string; + }) => boolean ): Record => timelineData.reduce((acc, v) => { // FUTURE DEVELOPER // We only have one featureId for security solution therefore we can just use hasAlertsCrud // but for o11y we can multiple featureIds so we need to check every consumer // of the alert to see if they have the permission to update the alert - const alertConsumers = v.data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? []; - const hasPermissions = hasAlertsCrudPermissionsByFeatureId - ? alertConsumers.some((consumer) => hasAlertsCrudPermissionsByFeatureId(consumer)) + const ruleConsumers = v.data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? []; + const ruleProducers = v.data.find((d) => d.field === ALERT_RULE_PRODUCER)?.value ?? []; + const hasPermissions = hasAlertsCrudPermissionsByRule + ? hasAlertsCrudPermissionsByRule({ + ruleConsumer: ruleConsumers.length > 0 ? ruleConsumers[0] : '', + ruleProducer: ruleProducers.length > 0 ? ruleProducers[0] : undefined, + }) : hasAlertsCrud; const fvm = diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 74d5d54e96526a..d39d7569a9ed93 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -32,7 +32,7 @@ import React, { import { connect, ConnectedProps, useDispatch } from 'react-redux'; import styled, { ThemeContext } from 'styled-components'; -import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils'; +import { ALERT_RULE_CONSUMER, ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils'; import { TGridCellAction, BulkActionsProp, @@ -105,7 +105,13 @@ interface OwnProps { trailingControlColumns?: ControlColumnProps[]; unit?: (total: number) => React.ReactNode; hasAlertsCrud?: boolean; - hasAlertsCrudPermissions?: (featureId: string) => boolean; + hasAlertsCrudPermissions?: ({ + ruleConsumer, + ruleProducer, + }: { + ruleConsumer: string; + ruleProducer?: string; + }) => boolean; totalSelectAllAlerts?: number; } @@ -180,7 +186,13 @@ const transformControlColumns = ({ theme: EuiTheme; setEventsLoading: SetEventsLoading; setEventsDeleted: SetEventsDeleted; - hasAlertsCrudPermissions?: (featureId: string) => boolean; + hasAlertsCrudPermissions?: ({ + ruleConsumer, + ruleProducer, + }: { + ruleConsumer: string; + ruleProducer?: string; + }) => boolean; }): EuiDataGridControlColumn[] => controlColumns.map( ({ id: columnId, headerCellRender = EmptyHeaderCellRender, rowCellRender, width }, i) => ({ @@ -208,7 +220,6 @@ const transformControlColumns = ({ ); }, - // eslint-disable-next-line react/display-name rowCellRender: ({ isDetails, @@ -224,9 +235,15 @@ const transformControlColumns = ({ if (rowData) { addBuildingBlockStyle(rowData.ecs, theme, setCellProps); if (columnId === 'checkbox-control-column' && hasAlertsCrudPermissions != null) { - const alertConsumers = + // FUTURE ENGINEER, the assumption here is you can only have one producer and consumer at this time + const ruleConsumers = rowData.data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? []; - disabled = alertConsumers.some((consumer) => !hasAlertsCrudPermissions(consumer)); + const ruleProducers = + rowData.data.find((d) => d.field === ALERT_RULE_PRODUCER)?.value ?? []; + disabled = !hasAlertsCrudPermissions({ + ruleConsumer: ruleConsumers.length > 0 ? ruleConsumers[0] : '', + ruleProducer: ruleProducers.length > 0 ? ruleProducers[0] : undefined, + }); } } else { // disable the cell when it has no data diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index ad77ad177a9fe5..d1d8662c567cc5 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -94,7 +94,13 @@ export interface TGridStandaloneProps { filters: Filter[]; footerText: React.ReactNode; filterStatus: AlertStatus; - hasAlertsCrudPermissions: (featureId: string) => boolean; + hasAlertsCrudPermissions: ({ + ruleConsumer, + ruleProducer, + }: { + ruleConsumer: string; + ruleProducer?: string; + }) => boolean; height?: number; indexNames: string[]; itemsPerPageOptions: number[]; @@ -229,8 +235,8 @@ const TGridStandaloneComponent: React.FC = ({ hasAlertsCrud: boolean; totalSelectAllAlerts: number; }>( - (acc, [featureId, nbrAlerts]) => { - const featureHasPermission = hasAlertsCrudPermissions(featureId); + (acc, [ruleConsumer, nbrAlerts]) => { + const featureHasPermission = hasAlertsCrudPermissions({ ruleConsumer }); return { hasAlertsCrud: featureHasPermission || acc.hasAlertsCrud, totalSelectAllAlerts: featureHasPermission diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/category_columns.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/category_columns.test.tsx index 30a6c44a08a165..a94ffee597c791 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/category_columns.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/category_columns.test.tsx @@ -8,10 +8,11 @@ import { mount } from 'enzyme'; import React from 'react'; -import { mockBrowserFields } from '../../../../mock'; +import { mockBrowserFields, TestProviders } from '../../../../mock'; -import { CATEGORY_PANE_WIDTH, getFieldCount } from './helpers'; +import { CATEGORY_PANE_WIDTH, getFieldCount, VIEW_ALL_BUTTON_CLASS_NAME } from './helpers'; import { CategoriesPane } from './categories_pane'; +import { ViewAllButton } from './category_columns'; const timelineId = 'test'; @@ -121,3 +122,32 @@ describe('getCategoryColumns', () => { expect(onCategorySelected).toHaveBeenCalledWith(notTheSelectedCategoryId); }); }); + +describe('ViewAllButton', () => { + it(`should update fields with the timestamp and category fields`, () => { + const onUpdateColumns = jest.fn(); + + const wrapper = mount( + + + + ); + + wrapper.find(`.${VIEW_ALL_BUTTON_CLASS_NAME}`).first().simulate('click'); + + expect(onUpdateColumns).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ id: '@timestamp' }), + expect.objectContaining({ id: 'agent.ephemeral_id' }), + expect.objectContaining({ id: 'agent.hostname' }), + expect.objectContaining({ id: 'agent.id' }), + expect.objectContaining({ id: 'agent.name' }), + ]) + ); + }); +}); diff --git a/x-pack/plugins/timelines/public/components/utils/helpers.ts b/x-pack/plugins/timelines/public/components/utils/helpers.ts index 0c531a9c483bc3..f02c86aecc108e 100644 --- a/x-pack/plugins/timelines/public/components/utils/helpers.ts +++ b/x-pack/plugins/timelines/public/components/utils/helpers.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { get, getOr, isEmpty, uniqBy } from 'lodash/fp'; +import { getOr, isEmpty, uniqBy } from 'lodash/fp'; import { BrowserField, BrowserFields, ColumnHeaderOptions } from '../../../common'; -import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../t_grid/body/constants'; +import { defaultHeaders } from '../t_grid/body/column_headers/default_headers'; +import { DEFAULT_COLUMN_MIN_WIDTH } from '../t_grid/body/constants'; export const getColumnHeaderFromBrowserField = ({ browserField, @@ -39,17 +40,14 @@ export const getColumnsWithTimestamp = ({ category: string; }): ColumnHeaderOptions[] => { const emptyFields: Record> = {}; - const timestamp = get('base.fields.@timestamp', browserFields); + const timestamp = defaultHeaders.find(({ id }) => id === '@timestamp'); const categoryFields: Array> = [ ...Object.values(getOr(emptyFields, `${category}.fields`, browserFields)), ]; - return timestamp != null && categoryFields.length + return timestamp != null ? uniqBy('id', [ - getColumnHeaderFromBrowserField({ - browserField: timestamp, - width: DEFAULT_DATE_COLUMN_MIN_WIDTH, - }), + timestamp, ...categoryFields.map((f) => getColumnHeaderFromBrowserField({ browserField: f })), ]) : []; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts index 8f4861ab43b475..3ab924440152d8 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts @@ -38,7 +38,7 @@ export const timelineEventsAll: TimelineFactory = { let { fieldRequested, ...queryOptions } = cloneDeep(options); queryOptions.fields = buildFieldsRequest(fieldRequested, queryOptions.excludeEcsData); const { activePage, querySize } = options.pagination; - const buckets = getOr([], 'aggregations.consumers.buckets', response.rawResponse); + const producerBuckets = getOr([], 'aggregations.producers.buckets', response.rawResponse); const totalCount = response.rawResponse.hits.total || 0; const hits = response.rawResponse.hits.hits; @@ -62,7 +62,7 @@ export const timelineEventsAll: TimelineFactory = { ) ); - const consumers = buckets.reduce( + const consumers: Record = producerBuckets.reduce( (acc: Record, b: { key: string; doc_count: number }) => ({ ...acc, [b.key]: b.doc_count, diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts index e9261e8b116bec..72a7d6e2692e8a 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils'; +import { ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash/fp'; import { @@ -69,8 +69,8 @@ export const buildTimelineEventsAllQuery = ({ body: { ...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), aggregations: { - consumers: { - terms: { field: ALERT_RULE_CONSUMER }, + producers: { + terms: { field: ALERT_RULE_PRODUCER, exclude: ['alerts'] }, }, }, query: { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 214167fe6d729d..6bf1abc2cc61fc 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -45,7 +45,7 @@ import { useApi } from '../../../../hooks/use_api'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { RedirectToTransformManagement } from '../../../../common/navigation'; import { ToastNotificationText } from '../../../../components'; -import { DuplicateIndexPatternError } from '../../../../../../../../../src/plugins/data/public'; +import { DuplicateDataViewError } from '../../../../../../../../../src/plugins/data/public'; import { PutTransformsLatestRequestSchema, PutTransformsPivotRequestSchema, @@ -257,7 +257,7 @@ export const StepCreateForm: FC = React.memo( setLoading(false); return true; } catch (e) { - if (e instanceof DuplicateIndexPatternError) { + if (e instanceof DuplicateDataViewError) { toastNotifications.addDanger( i18n.translate('xpack.transform.stepCreateForm.duplicateIndexPatternErrorMessage', { defaultMessage: diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6115e07189ed1f..1350b9d799a7ca 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13863,10 +13863,6 @@ "xpack.maps.deprecation.proxyEMS.message": "map.proxyElasticMapsServiceInMapsは廃止予定であり、8.0で削除されます。", "xpack.maps.deprecation.proxyEMS.step1": "Kibana構成ファイル、CLIフラグ、または環境変数 (Dockerのみ) で「map.proxyElasticMapsServiceInMaps」を削除します。", "xpack.maps.deprecation.proxyEMS.step2": "Elastic Maps Serviceをローカルでホストします。", - "xpack.maps.deprecation.regionmap.message": "map.regionmapは廃止予定であり、8.0 では削除されます。", - "xpack.maps.deprecation.regionmap.step1": "Kibana構成ファイル、CLIフラグ、または環境変数 (Dockerのみ) で「map.regionmap」を削除します。", - "xpack.maps.deprecation.regionmap.step2": "[GeoJSONのアップロード]を使用して、「map.regionmap.layers」で定義された各レイヤーをアップロードします。", - "xpack.maps.deprecation.regionmap.step3": "「構成されたGeoJSON」レイヤーですべてのマップを更新します。Choroplethレイヤーウィザードを使用して、置換レイヤーを構築します。「構成されたGeoJSON」レイヤーをマップから削除します。", "xpack.maps.discover.visualizeFieldLabel": "Mapsで可視化", "xpack.maps.distanceFilterForm.filterLabelLabel": "ラベルでフィルタリング", "xpack.maps.drawFeatureControl.invalidGeometry": "無効なジオメトリが検出されました", @@ -14233,11 +14229,6 @@ "xpack.maps.source.esTopHitsSearch.sortFieldLabel": "並べ替えフィールド", "xpack.maps.source.esTopHitsSearch.sortOrderLabel": "並べ替え順", "xpack.maps.source.geofieldLabel": "地理空間フィールド", - "xpack.maps.source.kbnRegionMap.deprecationTooltipMessage": "「構成されたGeoJSON」レイヤーは廃止予定です。1) [GeoJSONのアップロード]を使用して、'{vectorLayer}'をアップロードします。2) Choroplethレイヤーウィザードを使用して、置換レイヤーを構築します。3) 最後にこのレイヤーをマップから削除します。", - "xpack.maps.source.kbnRegionMap.noConfigErrorMessage": "{name} の map.regionmap 構成が見つかりません", - "xpack.maps.source.kbnRegionMap.vectorLayerLabel": "ベクターレイヤー", - "xpack.maps.source.kbnRegionMap.vectorLayerUrlLabel": "ベクターレイヤーURL", - "xpack.maps.source.kbnRegionMapTitle": "カスタムベクターシェイプ", "xpack.maps.source.kbnTMS.kbnTMS.urlLabel": "タイルマップ URL", "xpack.maps.source.kbnTMS.noConfigErrorMessage": "kibana.yml に map.tilemap.url 構成が見つかりません", "xpack.maps.source.kbnTMS.noLayerAvailableHelptext": "タイルマップレイヤーが利用できません。システム管理者に、kibana.yml で「map.tilemap.url」を設定するよう依頼してください。", @@ -14420,8 +14411,6 @@ "xpack.maps.tutorials.ems.shortDescription": "Elastic Maps Service からの管理ベクターシェイプ。", "xpack.maps.tutorials.ems.uploadStepText": "1.[Maps] ({newMapUrl}) を開きます。\n2.[Add layer]をクリックしてから[Upload GeoJSON]を選択します。\n3.GeoJSON ファイルをアップロードして[Import file]をクリックします。", "xpack.maps.tutorials.ems.uploadStepTitle": "Elastic Maps Service境界のインデックス作成", - "xpack.maps.util.formatErrorMessage": "URL からベクターシェイプを取得できません:{format}", - "xpack.maps.util.requestFailedErrorMessage": "URL からベクターシェイプを取得できません:{fetchUrl}", "xpack.maps.validatedNumberInput.invalidClampErrorMessage": "{min} と {max} の間でなければなりません", "xpack.maps.validatedRange.rangeErrorMessage": "{min} と {max} の間でなければなりません", "xpack.maps.vector.dualSize.unitLabel": "px", @@ -18455,7 +18444,6 @@ "xpack.osquery.scheduledQueryDetails.pageTitle": "{queryName}詳細", "xpack.osquery.scheduledQueryDetails.viewAllScheduledQueriesListTitle": "すべてのスケジュールされたクエリグループを表示", "xpack.osquery.scheduledQueryDetailsPage.editQueryButtonLabel": "編集", - "xpack.osquery.scheduledQueryGroup.form.advancedSectionToggleButtonLabel": "高度な設定", "xpack.osquery.scheduledQueryGroup.form.agentPolicyFieldLabel": "エージェントポリシー", "xpack.osquery.scheduledQueryGroup.form.cancelButtonLabel": "キャンセル", "xpack.osquery.scheduledQueryGroup.form.createSuccessToastMessageText": "正常に{scheduledQueryGroupName}をスケジュールしました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c7aff32f16dbd4..85889a40940360 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -14225,10 +14225,6 @@ "xpack.maps.deprecation.proxyEMS.message": "map.proxyElasticMapsServiceInMaps 已弃用,将在 8.0 中移除。", "xpack.maps.deprecation.proxyEMS.step1": "在 Kibana 配置文件、CLI 标志或环境变量中中移除“map.proxyElasticMapsServiceInMaps”(仅适用于 Docker)。", "xpack.maps.deprecation.proxyEMS.step2": "本地托管 Elastic Maps Service。", - "xpack.maps.deprecation.regionmap.message": "map.regionmap 已过时,将在 8.0 中移除。", - "xpack.maps.deprecation.regionmap.step1": "在 Kibana 配置文件、CLI 标志或环境变量中中移除“map.regionmap”(仅适用于 Docker)。", - "xpack.maps.deprecation.regionmap.step2": "使用“上传 GeoJSON”上传“map.regionmap.layers”定义的每个图层。", - "xpack.maps.deprecation.regionmap.step3": "使用“已配置 GeoJSON”图层更新所有地图。使用 Choropleth 图层向导构建替代图层。从您的图层中删除“已配置 GeoJSON”图层。", "xpack.maps.discover.visualizeFieldLabel": "在 Maps 中可视化", "xpack.maps.distanceFilterForm.filterLabelLabel": "筛选标签", "xpack.maps.drawFeatureControl.invalidGeometry": "检测到无效的几何形状", @@ -14596,11 +14592,6 @@ "xpack.maps.source.esTopHitsSearch.sortFieldLabel": "排序字段", "xpack.maps.source.esTopHitsSearch.sortOrderLabel": "排序顺序", "xpack.maps.source.geofieldLabel": "地理空间字段", - "xpack.maps.source.kbnRegionMap.deprecationTooltipMessage": "“已配置 GeoJSON”图层已弃用。1) 请使用“上传 GeoJSON”上传“{vectorLayer}”。2) 请使用 Choropleth 图层向导构建替代图层。3) 最后,请从地图中删除此图层。", - "xpack.maps.source.kbnRegionMap.noConfigErrorMessage": "找不到 {name} 的 map.regionmap 配置", - "xpack.maps.source.kbnRegionMap.vectorLayerLabel": "矢量图层", - "xpack.maps.source.kbnRegionMap.vectorLayerUrlLabel": "矢量图层 URL", - "xpack.maps.source.kbnRegionMapTitle": "定制矢量形状", "xpack.maps.source.kbnTMS.kbnTMS.urlLabel": "磁贴地图 URL", "xpack.maps.source.kbnTMS.noConfigErrorMessage": "在 kibana.yml 中找不到 map.tilemap.url 配置", "xpack.maps.source.kbnTMS.noLayerAvailableHelptext": "没有可用的磁贴地图图层。让您的系统管理员在 kibana.yml 中设置“map.tilemap.url”。", @@ -14783,8 +14774,6 @@ "xpack.maps.tutorials.ems.shortDescription": "来自 Elastic Maps Service 的管理边界。", "xpack.maps.tutorials.ems.uploadStepText": "1.打开 [Maps]({newMapUrl}).\n2.单击`添加图层`,然后选择`上传 GeoJSON`。\n3.上传 GeoJSON 文件,然后单击`导入文件`。", "xpack.maps.tutorials.ems.uploadStepTitle": "索引 Elastic Maps Service 边界", - "xpack.maps.util.formatErrorMessage": "无法从以下 URL 获取矢量形状:{format}", - "xpack.maps.util.requestFailedErrorMessage": "无法从以下 URL 获取矢量形状:{fetchUrl}", "xpack.maps.validatedNumberInput.invalidClampErrorMessage": "必须介于 {min} 和 {max} 之间", "xpack.maps.validatedRange.rangeErrorMessage": "必须介于 {min} 和 {max} 之间", "xpack.maps.vector.dualSize.unitLabel": "px", @@ -18880,7 +18869,6 @@ "xpack.osquery.scheduledQueryDetails.pageTitle": "{queryName} 详细信息", "xpack.osquery.scheduledQueryDetails.viewAllScheduledQueriesListTitle": "查看所有已计划查询组", "xpack.osquery.scheduledQueryDetailsPage.editQueryButtonLabel": "编辑", - "xpack.osquery.scheduledQueryGroup.form.advancedSectionToggleButtonLabel": "高级", "xpack.osquery.scheduledQueryGroup.form.agentPolicyFieldLabel": "代理策略", "xpack.osquery.scheduledQueryGroup.form.cancelButtonLabel": "取消", "xpack.osquery.scheduledQueryGroup.form.createSuccessToastMessageText": "已成功计划 {scheduledQueryGroupName}", diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts b/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts index 722b1625f023d3..fd90c53dac12a9 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/formatters.ts @@ -15,7 +15,7 @@ export const browserFormatters: BrowserFormatMap = { [ConfigKeys.SOURCE_ZIP_USERNAME]: null, [ConfigKeys.SOURCE_ZIP_PASSWORD]: null, [ConfigKeys.SOURCE_ZIP_FOLDER]: null, - [ConfigKeys.SOURCE_INLINE]: null, + [ConfigKeys.SOURCE_INLINE]: (fields) => JSON.stringify(fields[ConfigKeys.SOURCE_INLINE]), [ConfigKeys.PARAMS]: null, [ConfigKeys.SCREENSHOTS]: null, [ConfigKeys.SYNTHETICS_ARGS]: (fields) => diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/normalizers.ts b/x-pack/plugins/uptime/public/components/fleet_package/browser/normalizers.ts index 2b742a188782ad..53bbf611d490c1 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/normalizers.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/normalizers.ts @@ -10,7 +10,7 @@ import { Normalizer, commonNormalizers, getNormalizer, - getJsonToArrayOrObjectNormalizer, + getJsonToJavascriptNormalizer, } from '../common/normalizers'; import { defaultBrowserSimpleFields, defaultBrowserAdvancedFields } from '../contexts'; @@ -26,8 +26,8 @@ export const getBrowserNormalizer = (key: ConfigKeys) => { return getNormalizer(key, defaultBrowserFields); }; -export const getBrowserJsonToArrayOrObjectNormalizer = (key: ConfigKeys) => { - return getJsonToArrayOrObjectNormalizer(key, defaultBrowserFields); +export const getBrowserJsonToJavascriptNormalizer = (key: ConfigKeys) => { + return getJsonToJavascriptNormalizer(key, defaultBrowserFields); }; export const browserNormalizers: BrowserNormalizerMap = { @@ -35,9 +35,9 @@ export const browserNormalizers: BrowserNormalizerMap = { [ConfigKeys.SOURCE_ZIP_USERNAME]: getBrowserNormalizer(ConfigKeys.SOURCE_ZIP_USERNAME), [ConfigKeys.SOURCE_ZIP_PASSWORD]: getBrowserNormalizer(ConfigKeys.SOURCE_ZIP_PASSWORD), [ConfigKeys.SOURCE_ZIP_FOLDER]: getBrowserNormalizer(ConfigKeys.SOURCE_ZIP_FOLDER), - [ConfigKeys.SOURCE_INLINE]: getBrowserNormalizer(ConfigKeys.SOURCE_INLINE), + [ConfigKeys.SOURCE_INLINE]: getBrowserJsonToJavascriptNormalizer(ConfigKeys.SOURCE_INLINE), [ConfigKeys.PARAMS]: getBrowserNormalizer(ConfigKeys.PARAMS), [ConfigKeys.SCREENSHOTS]: getBrowserNormalizer(ConfigKeys.SCREENSHOTS), - [ConfigKeys.SYNTHETICS_ARGS]: getBrowserJsonToArrayOrObjectNormalizer(ConfigKeys.SYNTHETICS_ARGS), + [ConfigKeys.SYNTHETICS_ARGS]: getBrowserJsonToJavascriptNormalizer(ConfigKeys.SYNTHETICS_ARGS), ...commonNormalizers, }; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.test.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.test.ts index 055e829858a16a..caeeac915a911f 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.test.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { cronToSecondsNormalizer, jsonToArrayOrObjectNormalizer } from './normalizers'; +import { cronToSecondsNormalizer, jsonToJavascriptNormalizer } from './normalizers'; describe('normalizers', () => { describe('cronToSecondsNormalizer', () => { @@ -14,15 +14,15 @@ describe('normalizers', () => { }); }); - describe('jsonToArrayOrObjectNormalizer', () => { + describe('jsonToJavascriptNormalizer', () => { it('takes a json object string and returns an object', () => { - expect(jsonToArrayOrObjectNormalizer('{\n "key": "value"\n}')).toEqual({ + expect(jsonToJavascriptNormalizer('{\n "key": "value"\n}')).toEqual({ key: 'value', }); }); it('takes a json array string and returns an array', () => { - expect(jsonToArrayOrObjectNormalizer('["tag1","tag2"]')).toEqual(['tag1', 'tag2']); + expect(jsonToJavascriptNormalizer('["tag1","tag2"]')).toEqual(['tag1', 'tag2']); }); }); }); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts index 69121ca4bd70e2..57c4904d711c7a 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts @@ -23,19 +23,19 @@ export type CommonNormalizerMap = Record value ? value.slice(0, value.length - 1) : null; -export const jsonToArrayOrObjectNormalizer = (value: string) => (value ? JSON.parse(value) : null); +export const jsonToJavascriptNormalizer = (value: string) => (value ? JSON.parse(value) : null); export function getNormalizer(key: string, defaultValues: Fields): Normalizer { return (fields: NewPackagePolicyInput['vars']) => fields?.[key]?.value ?? defaultValues[key as keyof Fields]; } -export function getJsonToArrayOrObjectNormalizer( +export function getJsonToJavascriptNormalizer( key: string, defaultValues: Fields ): Normalizer { return (fields: NewPackagePolicyInput['vars']) => - jsonToArrayOrObjectNormalizer(fields?.[key]?.value) ?? defaultValues[key as keyof Fields]; + jsonToJavascriptNormalizer(fields?.[key]?.value) ?? defaultValues[key as keyof Fields]; } export function getCronNormalizer(key: string, defaultValues: Fields): Normalizer { @@ -47,8 +47,8 @@ export const getCommonNormalizer = (key: ConfigKeys) => { return getNormalizer(key, commonDefaultValues); }; -export const getCommonJsonToArrayOrObjectNormalizer = (key: ConfigKeys) => { - return getJsonToArrayOrObjectNormalizer(key, commonDefaultValues); +export const getCommonjsonToJavascriptNormalizer = (key: ConfigKeys) => { + return getJsonToJavascriptNormalizer(key, commonDefaultValues); }; export const getCommonCronToSecondsNormalizer = (key: ConfigKeys) => { @@ -74,6 +74,6 @@ export const commonNormalizers: CommonNormalizerMap = { } }, [ConfigKeys.APM_SERVICE_NAME]: getCommonNormalizer(ConfigKeys.APM_SERVICE_NAME), - [ConfigKeys.TAGS]: getCommonJsonToArrayOrObjectNormalizer(ConfigKeys.TAGS), + [ConfigKeys.TAGS]: getCommonjsonToJavascriptNormalizer(ConfigKeys.TAGS), [ConfigKeys.TIMEOUT]: getCommonCronToSecondsNormalizer(ConfigKeys.TIMEOUT), }; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/http/normalizers.ts b/x-pack/plugins/uptime/public/components/fleet_package/http/normalizers.ts index 10c52c295c9c49..ca86fd5bdc35bc 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/http/normalizers.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/http/normalizers.ts @@ -10,7 +10,7 @@ import { Normalizer, commonNormalizers, getNormalizer, - getJsonToArrayOrObjectNormalizer, + getJsonToJavascriptNormalizer, } from '../common/normalizers'; import { tlsNormalizers } from '../tls/normalizers'; import { defaultHTTPSimpleFields, defaultHTTPAdvancedFields } from '../contexts'; @@ -26,8 +26,8 @@ export const getHTTPNormalizer = (key: ConfigKeys) => { return getNormalizer(key, defaultHTTPValues); }; -export const getHTTPJsonToArrayOrObjectNormalizer = (key: ConfigKeys) => { - return getJsonToArrayOrObjectNormalizer(key, defaultHTTPValues); +export const getHTTPJsonToJavascriptNormalizer = (key: ConfigKeys) => { + return getJsonToJavascriptNormalizer(key, defaultHTTPValues); }; export const httpNormalizers: HTTPNormalizerMap = { @@ -36,18 +36,18 @@ export const httpNormalizers: HTTPNormalizerMap = { [ConfigKeys.USERNAME]: getHTTPNormalizer(ConfigKeys.USERNAME), [ConfigKeys.PASSWORD]: getHTTPNormalizer(ConfigKeys.PASSWORD), [ConfigKeys.PROXY_URL]: getHTTPNormalizer(ConfigKeys.PROXY_URL), - [ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE]: getHTTPJsonToArrayOrObjectNormalizer( + [ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE]: getHTTPJsonToJavascriptNormalizer( ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE ), - [ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE]: getHTTPJsonToArrayOrObjectNormalizer( + [ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE]: getHTTPJsonToJavascriptNormalizer( ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE ), [ConfigKeys.RESPONSE_BODY_INDEX]: getHTTPNormalizer(ConfigKeys.RESPONSE_BODY_INDEX), - [ConfigKeys.RESPONSE_HEADERS_CHECK]: getHTTPJsonToArrayOrObjectNormalizer( + [ConfigKeys.RESPONSE_HEADERS_CHECK]: getHTTPJsonToJavascriptNormalizer( ConfigKeys.RESPONSE_HEADERS_CHECK ), [ConfigKeys.RESPONSE_HEADERS_INDEX]: getHTTPNormalizer(ConfigKeys.RESPONSE_HEADERS_INDEX), - [ConfigKeys.RESPONSE_STATUS_CHECK]: getHTTPJsonToArrayOrObjectNormalizer( + [ConfigKeys.RESPONSE_STATUS_CHECK]: getHTTPJsonToJavascriptNormalizer( ConfigKeys.RESPONSE_STATUS_CHECK ), [ConfigKeys.REQUEST_BODY_CHECK]: (fields) => { @@ -76,7 +76,7 @@ export const httpNormalizers: HTTPNormalizerMap = { return defaultHTTPAdvancedFields[ConfigKeys.REQUEST_BODY_CHECK]; } }, - [ConfigKeys.REQUEST_HEADERS_CHECK]: getHTTPJsonToArrayOrObjectNormalizer( + [ConfigKeys.REQUEST_HEADERS_CHECK]: getHTTPJsonToJavascriptNormalizer( ConfigKeys.REQUEST_HEADERS_CHECK ), [ConfigKeys.REQUEST_METHOD_CHECK]: getHTTPNormalizer(ConfigKeys.REQUEST_METHOD_CHECK), diff --git a/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx index d57a69860311c0..05d45da8d38ac9 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx @@ -655,7 +655,9 @@ describe('useBarChartsHooks', () => { config[ConfigKeys.SOURCE_ZIP_PASSWORD] ); expect(vars?.[ConfigKeys.SOURCE_ZIP_URL].value).toEqual(config[ConfigKeys.SOURCE_ZIP_URL]); - expect(vars?.[ConfigKeys.SOURCE_INLINE].value).toEqual(config[ConfigKeys.SOURCE_INLINE]); + expect(vars?.[ConfigKeys.SOURCE_INLINE].value).toEqual( + JSON.stringify(config[ConfigKeys.SOURCE_INLINE]) + ); expect(vars?.[ConfigKeys.SOURCE_ZIP_PASSWORD].value).toEqual( config[ConfigKeys.SOURCE_ZIP_PASSWORD] ); diff --git a/x-pack/test/accessibility/apps/kibana_overview.ts b/x-pack/test/accessibility/apps/kibana_overview.ts index 9d21f08a900cca..9f5d91e5b4d54a 100644 --- a/x-pack/test/accessibility/apps/kibana_overview.ts +++ b/x-pack/test/accessibility/apps/kibana_overview.ts @@ -11,7 +11,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'home']); const a11y = getService('a11y'); - describe('Kibana overview', () => { + // FLAKY: https://github.com/elastic/kibana/issues/98463 + describe.skip('Kibana overview', () => { const esArchiver = getService('esArchiver'); before(async () => { diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts index fd49e2b2372170..b312ba67692727 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts @@ -20,7 +20,8 @@ export default function ({ getService }: FtrProviderContext) { const reportingAPI = getService('reportingAPI'); const security = getService('security'); - describe('ILM policy migration APIs', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/110483 + describe.skip('ILM policy migration APIs', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); diff --git a/yarn.lock b/yarn.lock index fd917bdccfd1f2..4d1520537ef6d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -110,6 +110,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.12.11": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785" + integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA== + dependencies: + "@babel/types" "^7.14.5" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.10": version "7.12.10" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz#54ab9b000e60a93644ce17b3f37d313aaf1d115d" @@ -300,6 +309,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== +"@babel/helper-validator-identifier@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" + integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== + "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" @@ -1217,6 +1231,14 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" + integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" @@ -1432,10 +1454,10 @@ dependencies: "@elastic/ecs-helpers" "^1.1.0" -"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary.18": - version "8.0.0-canary.18" - resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.18.tgz#d17e3c74809079c7272bb5ee0f8f96c16d723bae" - integrity sha512-YxFeoOWLWRMOLLSv1Zp5TyGe6hwc6FQtrLZT1TjsXucm9LVnxQSpg9tUPnYUfUNDwoY3FZwthUxH3+gb3Lqedw== +"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary.19": + version "8.0.0-canary.19" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.19.tgz#07e5f57a361d38b3eb747564a278d3d3bbf4882d" + integrity sha512-LVtnCPTC6bdI/r5Yh4TGVa0frzyoHBhQz3q72qvnmlkXW8pHlvaDQ46+46M017CERZxTK8BBYb1/TYs5puWRWQ== dependencies: debug "^4.3.1" hpagent "^0.1.1" @@ -4617,6 +4639,11 @@ dependencies: "@babel/runtime" "^7.12.5" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@ts-morph/common@~0.7.0": version "0.7.3" resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.7.3.tgz#380020c278e4aa6cecedf362a1157591d1003267" @@ -5524,13 +5551,6 @@ resolved "https://registry.yarnpkg.com/@types/lz-string/-/lz-string-1.3.34.tgz#69bfadde419314b4a374bf2c8e58659c035ed0a5" integrity sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow== -"@types/mapbox-gl@1.13.1": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-1.13.1.tgz#bd8108f912f32c895117e2970b6d4fbbecbe42a1" - integrity sha512-Yqv1eFAzG2gdecc94higNC8KE+BR6t8QhFgbQGGEpKr3OgSVVtr2qaBNBPaGlIAtCoKDF6JGB2haOhvijYC4Bg== - dependencies: - "@types/geojson" "*" - "@types/markdown-it@^0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.7.tgz#75070485a3d8ad11e7deb8287f4430be15bf4d39" @@ -6788,6 +6808,13 @@ agent-base@6: dependencies: debug "4" +agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agentkeepalive@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.4.1.tgz#aa95aebc3a749bca5ed53e3880a09f5235b48f0c" @@ -6795,6 +6822,15 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" +agentkeepalive@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" + integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" @@ -7309,11 +7345,6 @@ array-filter@^1.0.0: resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - array-find@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8" @@ -8402,13 +8433,6 @@ blob-util@2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - bluebird-retry@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/bluebird-retry/-/bluebird-retry-0.11.0.tgz#1289ab22cbbc3a02587baad35595351dd0c1c047" @@ -9177,14 +9201,6 @@ camelcase-css@2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" @@ -9199,7 +9215,7 @@ camelcase@^1.0.2: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= -camelcase@^2.0.0, camelcase@^2.0.1: +camelcase@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= @@ -9710,15 +9726,6 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - clone-regexp@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" @@ -10507,7 +10514,7 @@ cross-env@^6.0.3: dependencies: cross-spawn "^7.0.0" -cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -10516,14 +10523,6 @@ cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -cross-spawn@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" - integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -10925,13 +10924,6 @@ cucumber@^4.2.1: util-arity "^1.0.2" verror "^1.9.0" -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - cwise-compiler@^1.0.0, cwise-compiler@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/cwise-compiler/-/cwise-compiler-1.1.3.tgz#f4d667410e850d3a313a7d2db7b1e505bb034cc5" @@ -11491,7 +11483,7 @@ decamelize-keys@^1.1.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -11762,7 +11754,7 @@ delete-empty@^2.0.0: relative "^3.0.2" rimraf "^2.6.2" -depd@~1.1.2: +depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -12513,6 +12505,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + dependencies: + iconv-lite "~0.4.13" + end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -12640,6 +12639,11 @@ enzyme@^3.11.0: rst-selector-parser "^2.2.3" string.prototype.trim "^1.2.1" +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + errlop@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.1.2.tgz#a99a48f37aa264d614e342ffdbbaa49eec9220e0" @@ -14379,16 +14383,6 @@ fsevents@^2.1.2, fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== -fstream@^1.0.0, fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - fsu@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/fsu/-/fsu-1.1.1.tgz#bd36d3579907c59d85b257a75b836aa9e0c31834" @@ -15017,11 +15011,16 @@ 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.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.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== +graceful-fs@^4.2.3, graceful-fs@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -15804,7 +15803,7 @@ htmlparser2@~3.3.0: domutils "1.1" readable-stream "1.0" -http-cache-semantics@^4.0.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== @@ -15855,6 +15854,15 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-proxy-middleware@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" @@ -16102,18 +16110,6 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -in-publish@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" - integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - indent-string@^3.0.0, indent-string@^3.1.0, indent-string@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" @@ -16147,7 +16143,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -16234,10 +16230,10 @@ insert-module-globals@^7.0.0: undeclared-identifiers "^1.1.2" xtend "^4.0.0" -install-artifact-from-github@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.0.2.tgz#e1e478dd29880b9112ecd684a84029603e234a9d" - integrity sha512-yuMFBSVIP3vD0SDBGUqeIpgOAIlFx8eQFknQObpkYEM5gsl9hy6R9Ms3aV+Vw9MMyYsoPMeex0XDnfgY7uzc+Q== +install-artifact-from-github@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz#adcbd123c16a4337ec44ea76d0ebf253cc16b074" + integrity sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA== internal-ip@^4.3.0: version "4.3.0" @@ -16649,6 +16645,11 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= + is-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" @@ -18237,6 +18238,11 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +klona@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + known-css-properties@^0.20.0: version "0.20.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.20.0.tgz#0570831661b47dd835293218381166090ff60e96" @@ -18926,14 +18932,6 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" @@ -18964,7 +18962,7 @@ lowlight@^1.14.0: fault "^1.0.0" highlight.js "~10.4.0" -lru-cache@^4.0.0, lru-cache@^4.0.1, lru-cache@^4.1.5: +lru-cache@^4.0.0, lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -19025,6 +19023,27 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: dependencies: semver "^6.0.0" +make-fetch-happen@^8.0.14: + version "8.0.14" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" + integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.0.5" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + promise-retry "^2.0.1" + socks-proxy-agent "^5.0.0" + ssri "^8.0.0" + make-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.0.tgz#57bef5dc85d23923ba23767324d8e8f8f3d9694b" @@ -19044,7 +19063,7 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0, map-obj@^1.0.1: +map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= @@ -19071,10 +19090,15 @@ mapbox-gl-draw-rectangle-mode@1.0.4: resolved "https://registry.yarnpkg.com/mapbox-gl-draw-rectangle-mode/-/mapbox-gl-draw-rectangle-mode-1.0.4.tgz#42987d68872a5fb5cc5d76d3375ee20cd8bab8f7" integrity sha512-BdF6nwEK2p8n9LQoMPzBO8LhddW1fe+d5vK8HQIei+4VcRnUbKNsEj7Z15FsJxCHzsc2BQKXbESx5GaE8x0imQ== -mapbox-gl@1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.13.1.tgz#322efe75ab4c764fc4c776da1506aad58d5a5b9d" - integrity sha512-GSyubcoSF5MyaP8z+DasLu5v7KmDK2pp4S5+VQ5WdVQUOaAqQY4jwl4JpcdNho3uWm2bIKs7x1l7q3ynGmW60g== +mapcap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mapcap/-/mapcap-1.0.0.tgz#e8e29d04a160eaf8c92ec4bcbd2c5d07ed037e5a" + integrity sha512-KcNlZSlFPx+r1jYZmxEbTVymG+dIctf10WmWkuhrhrblM+KMoF77HelwihL5cxYlORye79KoR4IlOOk99lUJ0g== + +maplibre-gl@1.15.2: + version "1.15.2" + resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-1.15.2.tgz#7fb47868b62455af916c090903f2154394450f9c" + integrity sha512-uPeV530apb4JfX3cRFfE+awFnbcJTOnCv2QvY4mw4huiInbybElWYkNzTs324YLSADq0f4bidRoYcR81ho3aLA== dependencies: "@mapbox/geojson-rewind" "^0.5.0" "@mapbox/geojson-types" "^1.0.2" @@ -19100,11 +19124,6 @@ mapbox-gl@1.13.1: tinyqueue "^2.0.3" vt-pbf "^3.1.1" -mapcap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mapcap/-/mapcap-1.0.0.tgz#e8e29d04a160eaf8c92ec4bcbd2c5d07ed037e5a" - integrity sha512-KcNlZSlFPx+r1jYZmxEbTVymG+dIctf10WmWkuhrhrblM+KMoF77HelwihL5cxYlORye79KoR4IlOOk99lUJ0g== - marge@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/marge/-/marge-1.0.1.tgz#52d6026911e62e1dd1cf60a07313dde285a8370c" @@ -19352,22 +19371,6 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - meow@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467" @@ -19421,6 +19424,24 @@ meow@^8.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" +meow@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" + integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize "^1.2.0" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + merge-descriptors@1.0.1, merge-descriptors@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -19615,6 +19636,17 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" +minipass-fetch@^1.3.2: + version "1.3.4" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.3.4.tgz#63f5af868a38746ca7b33b03393ddf8c291244fe" + integrity sha512-TielGogIzbUEtd1LsjZFs47RWuHHfhl6TiCx1InVxApBAmQ8bL0dL5ilkLGcRvuyW/A9nE+Lvn855Ewz8S0PnQ== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" @@ -19629,6 +19661,20 @@ minipass-pipeline@^1.2.2: dependencies: minipass "^3.0.0" +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + minipass@^2.2.1, minipass@^2.8.6, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -19644,6 +19690,13 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" +minipass@^3.1.0, minipass@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + minizlib@^1.2.1: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -19651,6 +19704,14 @@ minizlib@^1.2.1: dependencies: minipass "^2.9.0" +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + minizlib@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" @@ -19695,18 +19756,18 @@ mkdirp@0.5.1: dependencies: minimist "0.0.8" -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= + +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== dependencies: minimist "^1.2.5" -mkdirp@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= - mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -20033,7 +20094,7 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.13.2, nan@^2.14.0, nan@^2.14.1: +nan@^2.13.2, nan@^2.14.0: version "2.14.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== @@ -20148,6 +20209,11 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nested-error-stacks@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz#19f619591519f096769a5ba9a86e6eeec823c3cf" @@ -20250,38 +20316,36 @@ node-gyp-build@^4.2.3: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== -node-gyp@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" - integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== - dependencies: - fstream "^1.0.0" - glob "^7.0.3" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3 || 4" - osenv "0" - request "^2.87.0" - rimraf "2" - semver "~5.3.0" - tar "^2.0.0" - which "1" - -node-gyp@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.0.0.tgz#2e88425ce84e9b1a4433958ed55d74c70fffb6be" - integrity sha512-ZW34qA3CJSPKDz2SJBHKRvyNQN0yWO5EGKKksJc+jElu9VA468gwJTyTArC1iOXU7rN3Wtfg/CMt/dBAOFIjvg== +node-gyp@^7.1.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" + integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== dependencies: env-paths "^2.2.0" glob "^7.1.4" graceful-fs "^4.2.3" - nopt "^4.0.3" + nopt "^5.0.0" npmlog "^4.1.2" request "^2.88.2" - rimraf "^2.6.3" + rimraf "^3.0.2" semver "^7.3.2" - tar "^6.0.1" + tar "^6.0.2" + which "^2.0.2" + +node-gyp@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.2.0.tgz#ef509ccdf5cef3b4d93df0690b90aa55ff8c7977" + integrity sha512-KG8SdcoAnw2d6augGwl1kOayALUrXW/P2uOAm2J2+nmW/HjZo7y+8TDg7LejxbekOOSv3kzhq+NSUYkIDAX8eA== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^8.0.14" + nopt "^5.0.0" + npmlog "^4.1.2" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" which "^2.0.2" node-int64@^0.4.0: @@ -20388,23 +20452,21 @@ node-releases@^1.1.70: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== -node-sass@^4.14.1: - version "4.14.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" - integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== +node-sass@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-6.0.1.tgz#cad1ccd0ce63e35c7181f545d8b986f3a9a887fe" + integrity sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" - cross-spawn "^3.0.0" + cross-spawn "^7.0.3" gaze "^1.0.0" get-stdin "^4.0.1" glob "^7.0.3" - in-publish "^2.0.0" lodash "^4.17.15" - meow "^3.7.0" - mkdirp "^0.5.1" + meow "^9.0.0" nan "^2.13.2" - node-gyp "^3.8.0" + node-gyp "^7.1.0" npmlog "^4.0.0" request "^2.88.0" sass-graph "2.2.5" @@ -20444,13 +20506,6 @@ nodemon@^2.0.4: undefsafe "^2.0.3" update-notifier "^4.1.0" -"nopt@2 || 3", nopt@~3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= - dependencies: - abbrev "1" - nopt@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-2.2.1.tgz#2aa09b7d1768487b3b89a9c5aa52335bff0baea7" @@ -20458,13 +20513,12 @@ nopt@^2.2.0: dependencies: abbrev "1" -nopt@^4.0.3, nopt@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== dependencies: abbrev "1" - osenv "^0.1.4" nopt@~1.0.10: version "1.0.10" @@ -20473,7 +20527,22 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0: +nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= + dependencies: + abbrev "1" + +nopt@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -20551,7 +20620,7 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.1.2: +npmlog@^4.0.0, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -20996,7 +21065,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@0, osenv@^0.1.0, osenv@^0.1.4: +osenv@^0.1.0, osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -22413,6 +22482,14 @@ promise-polyfill@^8.1.3: resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promise.allsettled@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.2.tgz#d66f78fbb600e83e863d893e98b3d4376a9c47c9" @@ -22896,14 +22973,14 @@ re-resizable@^6.1.1: dependencies: fast-memoize "^2.5.1" -re2@^1.15.4: - version "1.15.4" - resolved "https://registry.yarnpkg.com/re2/-/re2-1.15.4.tgz#2ffc3e4894fb60430393459978197648be01a0a9" - integrity sha512-7w3K+Daq/JjbX/dz5voMt7B9wlprVBQnMiypyCojAZ99kcAL+3LiJ5uBoX/u47l8eFTVq3Wj+V0pmvU+CT8tOg== +re2@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/re2/-/re2-1.16.0.tgz#f311eb4865b1296123800ea8e013cec8dab25590" + integrity sha512-eizTZL2ZO0ZseLqfD4t3Qd0M3b3Nr0MBWpX81EbPMIud/1d/CSfUIx2GQK8fWiAeHoSekO5EOeFib2udTZLwYw== dependencies: - install-artifact-from-github "^1.0.2" - nan "^2.14.1" - node-gyp "^7.0.0" + install-artifact-from-github "^1.2.0" + nan "^2.14.2" + node-gyp "^8.0.0" react-ace@^7.0.5: version "7.0.5" @@ -23839,14 +23916,6 @@ recursive-readdir@2.2.2: dependencies: minimatch "3.0.4" -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -24647,7 +24716,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: +rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -24839,16 +24908,16 @@ sass-graph@2.2.5: scss-tokenizer "^0.2.3" yargs "^13.3.2" -sass-loader@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" - integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== +sass-loader@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.2.0.tgz#3d64c1590f911013b3fa48a0b22a83d5e1494716" + integrity sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw== dependencies: - clone-deep "^4.0.1" - loader-utils "^1.2.3" - neo-async "^2.6.1" - schema-utils "^2.6.1" - semver "^6.3.0" + klona "^2.0.4" + loader-utils "^2.0.0" + neo-async "^2.6.2" + schema-utils "^3.0.0" + semver "^7.3.2" sass-resources-loader@^2.0.1: version "2.0.1" @@ -24917,7 +24986,7 @@ schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" -schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: +schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== @@ -25214,13 +25283,6 @@ shallow-clone-shim@^2.0.0: resolved "https://registry.yarnpkg.com/shallow-clone-shim/-/shallow-clone-shim-2.0.0.tgz#b62bf55aed79f4c1430ea1dc4d293a193f52cf91" integrity sha512-YRNymdiL3KGOoS67d73TEmk4tdPTO9GSMCoiphQsTcC9EtC+AOmMPjkyBkRoCJfW9ASsaZw1craaiw1dPN2D3Q== -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - shallow-copy@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" @@ -25391,6 +25453,11 @@ slide@^1.1.5, slide@~1.1.3: resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= +smart-buffer@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + snap-shot-compare@2.8.3: version "2.8.3" resolved "https://registry.yarnpkg.com/snap-shot-compare/-/snap-shot-compare-2.8.3.tgz#b4982fb7b4e9cd4fa0b03a40a100b5f005b2d515" @@ -25466,6 +25533,23 @@ sockjs@0.3.20: uuid "^3.4.0" websocket-driver "0.6.5" +socks-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" + integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== + dependencies: + agent-base "^6.0.2" + debug "4" + socks "^2.3.3" + +socks@^2.3.3: + version "2.6.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" + integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== + dependencies: + ip "^1.1.5" + smart-buffer "^4.1.0" + sonic-boom@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-1.3.0.tgz#5c77c846ce6c395dddf2eb8e8e65f9cc576f2e76" @@ -26241,13 +26325,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -26738,7 +26815,7 @@ tar@4.4.13: safe-buffer "^5.1.2" yallist "^3.0.3" -tar@6.0.2, tar@^6.0.1, tar@^6.0.2: +tar@6.0.2, tar@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39" integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg== @@ -26750,14 +26827,17 @@ tar@6.0.2, tar@^6.0.1, tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" - integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== +tar@^6.1.2: + version "6.1.10" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.10.tgz#8a320a74475fba54398fa136cd9883aa8ad11175" + integrity sha512-kvvfiVvjGMxeUNB6MyYv5z7vhfFRwbwCXJAeL0/lnbrttBVqcMOnpHUf0X42LrPMR8mMpgapkJMchFH4FSHzNA== dependencies: - block-stream "*" - fstream "^1.0.12" - inherits "2" + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" tcomb-validation@^3.3.0: version "3.4.1" @@ -27219,7 +27299,7 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -topojson-client@3.1.0, topojson-client@^3.1.0: +topojson-client@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99" integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw== @@ -27302,11 +27382,6 @@ treeify@^1.0.1, treeify@^1.1.0: resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - trim-newlines@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" @@ -29295,13 +29370,6 @@ which-typed-array@^1.1.2: has-symbols "^1.0.1" is-typed-array "^1.1.3" -which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1, which@~1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - which@2.0.2, which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -29309,6 +29377,13 @@ which@2.0.2, which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +which@^1.2.14, which@^1.2.9, which@^1.3.1, which@~1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + wide-align@1.1.3, wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"